'use client'; import { Suspense, useEffect, useMemo, useState, useCallback } from 'react'; import { useSearchParams, useRouter } from 'next/navigation'; import { Button } from '@/components/ui/Button'; import { VideoPlayer } from '@/components/player/VideoPlayer'; import { VideoMetadata } from '@/components/player/VideoMetadata'; import { EpisodeList } from '@/components/player/EpisodeList'; import { PlayerError } from '@/components/player/PlayerError'; import { SourceSelector, SourceInfo } from '@/components/player/SourceSelector'; import { useVideoPlayer } from '@/lib/hooks/useVideoPlayer'; import { useHistory } from '@/lib/store/history-store'; import { FavoritesSidebar } from '@/components/favorites/FavoritesSidebar'; import { FavoriteButton } from '@/components/favorites/FavoriteButton'; import { PlayerNavbar } from '@/components/player/PlayerNavbar'; import { settingsStore } from '@/lib/store/settings-store'; import { premiumModeSettingsStore } from '@/lib/store/premium-mode-settings'; import { SegmentedControl } from '@/components/ui/SegmentedControl'; import Image from 'next/image'; function PlayerContent() { const searchParams = useSearchParams(); const router = useRouter(); const isPremium = searchParams.get('premium') === '1'; const { addToHistory } = useHistory(isPremium); const videoId = searchParams.get('id'); const source = searchParams.get('source'); const title = searchParams.get('title'); const episodeParam = searchParams.get('episode'); const groupedSourcesParam = searchParams.get('groupedSources'); // Track settings - use mode-specific store const modeStore = isPremium ? premiumModeSettingsStore : settingsStore; const [isReversed, setIsReversed] = useState(() => typeof window !== 'undefined' ? modeStore.getSettings().episodeReverseOrder : false ); // Mobile tab state const [activeTab, setActiveTab] = useState<'episodes' | 'info' | 'sources'>('episodes'); // Sync with store changes if any (though usually it's one-way from UI to store) useEffect(() => { setIsReversed(modeStore.getSettings().episodeReverseOrder); }, []); // Redirect if no video ID or source if (!videoId || !source) { router.push('/'); return null; } const { videoData, loading, videoError, currentEpisode, playUrl, setCurrentEpisode, setPlayUrl, setVideoError, fetchVideoDetails, } = useVideoPlayer(videoId, source, episodeParam, isReversed); // Parse grouped sources if available const groupedSources = useMemo(() => { let sources: SourceInfo[] = []; if (groupedSourcesParam) { try { sources = JSON.parse(groupedSourcesParam); } catch { sources = []; } } // Always ensure the current source is in the list if (source && !sources.find(s => s.source === source)) { sources.unshift({ id: videoId || '', source: source, sourceName: source, pic: videoData?.vod_pic }); } return sources; }, [groupedSourcesParam, source, videoId, videoData?.vod_pic]); // Track current source for switching const [currentSourceId, setCurrentSourceId] = useState(source); // Add initial history entry when video data is loaded useEffect(() => { if (videoData && playUrl && videoId) { // Map episodes to include index const mappedEpisodes = videoData.episodes?.map((ep, idx) => ({ name: ep.name || `第${idx + 1}集`, url: ep.url, index: idx, })) || []; addToHistory( videoId, videoData.vod_name || title || '未知视频', playUrl, currentEpisode, source, 0, // Initial playback position 0, // Will be updated by VideoPlayer videoData.vod_pic, mappedEpisodes, { vod_actor: videoData.vod_actor, type_name: videoData.type_name, vod_area: videoData.vod_area } ); } }, [videoData, playUrl, videoId, currentEpisode, source, title, addToHistory]); const handleEpisodeClick = useCallback((episode: any, index: number) => { setCurrentEpisode(index); setPlayUrl(episode.url); setVideoError(''); // Update URL to reflect current episode const params = new URLSearchParams(searchParams.toString()); params.set('episode', index.toString()); router.replace(`/player?${params.toString()}`, { scroll: false }); }, [searchParams, router, setCurrentEpisode, setPlayUrl, setVideoError]); const handleToggleReverse = (reversed: boolean) => { setIsReversed(reversed); const settings = modeStore.getSettings(); modeStore.saveSettings({ ...settings, episodeReverseOrder: reversed }); }; // Handle auto-next episode const handleNextEpisode = useCallback(() => { const episodes = videoData?.episodes; if (!episodes) return; let nextIndex; if (!isReversed) { if (currentEpisode >= episodes.length - 1) return; nextIndex = currentEpisode + 1; } else { if (currentEpisode <= 0) return; nextIndex = currentEpisode - 1; } const nextEpisode = episodes[nextIndex]; if (nextEpisode) { handleEpisodeClick(nextEpisode, nextIndex); // handleEpisodeClick relies on state setters, which are stable } }, [videoData, currentEpisode, isReversed, router, searchParams]); // handleEpisodeClick is not memoized, but uses stable hooks setters. wait, handleEpisodeClick is inline too! return (
{/* Glass Navbar */}
{loading ? (

正在加载视频详情...

) : videoError && !videoData ? ( router.back()} onRetry={fetchVideoDetails} /> ) : (
{/* Video Player Section */}
router.back()} totalEpisodes={videoData?.episodes?.length || 0} onNextEpisode={handleNextEpisode} isReversed={isReversed} isPremium={isPremium} videoTitle={videoData?.vod_name || title || ''} episodeName={videoData?.episodes?.[currentEpisode]?.name || ''} />
{/* Favorite Button for current video */} {videoData && videoId && (
收藏这个视频
)}
{/* Sidebar with sticky wrapper */}
{/* Mobile Tabs */} {groupedSources.length > 0 && ( 1 ? [{ label: '来源', value: 'sources' as const }] : []), ]} value={activeTab} onChange={setActiveTab} className="lg:hidden mb-4" /> )} {/* Info Tab Content - Mobile Only */}
{/* Episode List - Visible if desktop OR active mobile tab */}
{/* Source Selector - Visible if (desktop AND grouped sources) OR (active mobile tab AND grouped sources) */} {groupedSources.length > 0 && (
{ // Navigate to same video with different source const params = new URLSearchParams(); params.set('id', String(newSource.id)); params.set('source', newSource.source); params.set('title', title || ''); if (groupedSourcesParam) { params.set('groupedSources', groupedSourcesParam); } setCurrentSourceId(newSource.source); router.replace(`/player?${params.toString()}`, { scroll: false }); }} />
)}
)}
{/* Favorites Sidebar - Left */}
); } export default function PlayerPage() { return (
}>
); }