44 * IPTV Page - Live TV channel viewer with M3U source management
55 */
66
7- import { useState , useEffect } from 'react' ;
7+ import { useState , useEffect , useMemo } from 'react' ;
88import { useIPTVStore } from '@/lib/store/iptv-store' ;
99import { IPTVSourceManager } from '@/components/iptv/IPTVSourceManager' ;
1010import { IPTVChannelGrid } from '@/components/iptv/IPTVChannelGrid' ;
@@ -15,12 +15,35 @@ import Link from 'next/link';
1515import type { M3UChannel } from '@/lib/utils/m3u-parser' ;
1616
1717export default function IPTVPage ( ) {
18- const { sources, cachedChannels, cachedGroups , cachedChannelsBySource, refreshSources, isLoading, lastRefreshed } = useIPTVStore ( ) ;
18+ const { sources, cachedChannels, cachedChannelsBySource, refreshSources, isLoading, lastRefreshed } = useIPTVStore ( ) ;
1919 const [ activeChannel , setActiveChannel ] = useState < M3UChannel | null > ( null ) ;
2020 const [ showManager , setShowManager ] = useState ( false ) ;
2121
22- const canManageSources = hasPermission ( 'source_management ' ) ;
22+ const canManageSources = hasPermission ( 'iptv_source_management ' ) ;
2323 const canAccessIPTV = hasPermission ( 'iptv_access' ) ;
24+ const canUseBuiltinSources = hasPermission ( 'iptv_builtin_sources' ) ;
25+ const visibleSources = useMemo (
26+ ( ) => sources . filter ( ( source ) => canUseBuiltinSources || source . kind !== 'builtin' ) ,
27+ [ sources , canUseBuiltinSources ]
28+ ) ;
29+ const visibleSourceIds = useMemo ( ( ) => new Set ( visibleSources . map ( ( source ) => source . id ) ) , [ visibleSources ] ) ;
30+ const visibleChannels = useMemo (
31+ ( ) => cachedChannels . filter ( ( channel ) => ! channel . sourceId || visibleSourceIds . has ( channel . sourceId ) ) ,
32+ [ cachedChannels , visibleSourceIds ]
33+ ) ;
34+ const visibleGroups = useMemo (
35+ ( ) => Array . from ( new Set ( visibleChannels . map ( ( channel ) => channel . group ) . filter ( Boolean ) ) ) . sort ( ) as string [ ] ,
36+ [ visibleChannels ]
37+ ) ;
38+ const visibleChannelsBySource = useMemo (
39+ ( ) =>
40+ Object . fromEntries (
41+ visibleSources
42+ . map ( ( source ) => [ source . id , cachedChannelsBySource [ source . id ] ] )
43+ . filter ( ( [ , data ] ) => ! ! data )
44+ ) ,
45+ [ visibleSources , cachedChannelsBySource ]
46+ ) ;
2447
2548 // If auth is configured and user doesn't have iptv_access, show access denied
2649 if ( ! canAccessIPTV && getSession ( ) ) {
@@ -65,7 +88,7 @@ export default function IPTVPage() {
6588 直播
6689 </ h1 >
6790 < p className = "text-sm text-[var(--text-color-secondary)]" >
68- { cachedChannels . length > 0 ? `${ cachedChannels . length } 个频道` : 'IPTV 直播频道' }
91+ { visibleChannels . length > 0 ? `${ visibleChannels . length } 个频道` : 'IPTV 直播频道' }
6992 </ p >
7093 </ div >
7194 </ div >
@@ -101,12 +124,12 @@ export default function IPTVPage() {
101124 { ! isLoading && (
102125 < div className = "bg-[var(--glass-bg)] border border-[var(--glass-border)] rounded-[var(--radius-2xl)] shadow-[var(--shadow-sm)] p-6" >
103126 < IPTVChannelGrid
104- channels = { cachedChannels }
105- groups = { cachedGroups }
127+ channels = { visibleChannels }
128+ groups = { visibleGroups }
106129 onSelect = { setActiveChannel }
107130 activeChannel = { activeChannel }
108- channelsBySource = { cachedChannelsBySource }
109- sources = { sources }
131+ channelsBySource = { visibleChannelsBySource }
132+ sources = { visibleSources }
110133 />
111134 </ div >
112135 ) }
@@ -117,10 +140,10 @@ export default function IPTVPage() {
117140 < IPTVPlayer
118141 channel = { activeChannel }
119142 onClose = { ( ) => setActiveChannel ( null ) }
120- channels = { cachedChannels }
143+ channels = { visibleChannels }
121144 onChannelChange = { setActiveChannel }
122- channelsBySource = { cachedChannelsBySource }
123- sources = { sources }
145+ channelsBySource = { visibleChannelsBySource }
146+ sources = { visibleSources }
124147 />
125148 ) }
126149 </ div >
0 commit comments