import React, { useEffect, useState, useRef } from 'react'; import { getEpisodeLinkByTitle, getTvShowCard } from '../lib/api'; import { useToast } from '@/hooks/use-toast'; import { Film, GalleryVerticalEnd, Loader2, Play } from 'lucide-react'; import VideoPlayer from './VideoPlayer'; interface ProgressData { status: string; progress: number; downloaded: number; total: number; } interface ContentRating { country: string; name: string; description: string; } interface TVShowPlayerProps { videoTitle: string; season: string; episode: string; movieTitle: string; contentRatings?: ContentRating[]; thumbnail?: string; poster?: string; startTime?: number; onClosePlayer?: () => void; onProgressUpdate?: (currentTime: number, duration: number) => void; onVideoEnded?: () => void; onShowEpisodes?: () => void; } const TVShowPlayer: React.FC = ({ videoTitle, season, episode, movieTitle, contentRatings, thumbnail, poster, startTime = 0, onClosePlayer, onProgressUpdate, onVideoEnded, onShowEpisodes }) => { const [videoUrl, setVideoUrl] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [progress, setProgress] = useState(null); const [videoFetched, setVideoFetched] = useState(false); const { toast } = useToast(); const pollingInterval = useRef(null); const timeoutRef = useRef(null); const videoFetchedRef = useRef(false); const [ratingInfo, setRatingInfo] = useState<{ rating: string, description: string } | null>(null); const containerRef = useRef(null); const [isFullscreen, setIsFullscreen] = useState(false); const videoRef = useRef(null); // Parse episode info const getEpisodeInfo = () => { if (!episode) return { number: '1', title: 'Unknown Episode' }; const episodeMatch = episode.match(/E(\d+)\s*-\s*(.+?)(?=\.\w+$)/i); const number = episodeMatch ? episodeMatch[1] : '1'; const title = episodeMatch ? episodeMatch[2].trim() : 'Unknown Episode'; return { number, title }; }; const { number: episodeNumber, title: episodeTitle } = getEpisodeInfo(); // --- Link Fetching & Polling --- const fetchMovieLink = async () => { if (videoFetchedRef.current) return; try { const response = await getEpisodeLinkByTitle(videoTitle, season, episode); if (response && response.url) { // Stop any polling if running if (pollingInterval.current) { clearInterval(pollingInterval.current); pollingInterval.current = null; } setVideoUrl(response.url); setVideoFetched(true); videoFetchedRef.current = true; setLoading(false); console.log('Video URL fetched:', response.url); } else if (response && response.progress_url) { startPolling(response.progress_url); } else { console.error('No video URL or progress URL found in response:', response); setError('Video URL not available'); } } catch (error) { console.error('Error fetching episode link:', error); setError('Failed to load episode'); toast({ title: "Error", description: "Could not load the episode", variant: "destructive" }); } finally { if (!videoFetchedRef.current && !videoUrl) { setLoading(false); } } }; // Fetch content ratings if not provided useEffect(() => { const fetchRatingInfo = async () => { if (contentRatings && contentRatings.length > 0) { const usRating = contentRatings.find(r => r.country === 'usa') || contentRatings[0]; setRatingInfo({ rating: usRating.name || 'NR', description: usRating.description || '' }); return; } try { const showData = await getTvShowCard(videoTitle); if (showData && showData.data && showData.data.contentRatings) { const ratings = showData.data.contentRatings; const usRating = ratings.find((r: any) => r.country === 'US') || ratings[0]; setRatingInfo({ rating: usRating?.name || 'TV-14', description: usRating?.description || '' }); } } catch (error) { console.error('Failed to fetch show ratings:', error); } }; fetchRatingInfo(); }, [videoTitle, contentRatings]); const pollProgress = async (progressUrl: string) => { try { const res = await fetch(progressUrl); const data = await res.json(); setProgress(data.progress); if (data.progress.progress >= 100) { if (pollingInterval.current) { clearInterval(pollingInterval.current); pollingInterval.current = null; } if (!videoFetchedRef.current) { timeoutRef.current = setTimeout(fetchMovieLink, 5000); } } } catch (error) { console.error('Error polling progress:', error); } }; const startPolling = (progressUrl: string) => { if (!pollingInterval.current) { const interval = setInterval(() => pollProgress(progressUrl), 2000); pollingInterval.current = interval; } }; useEffect(() => { if (!videoTitle || !season || !episode) { setError('Missing required video information'); setLoading(false); return; } // Reset state for new episode setVideoUrl(null); setVideoFetched(false); videoFetchedRef.current = false; setLoading(true); setProgress(null); setError(null); // Start fetching fetchMovieLink(); return () => { if (pollingInterval.current) clearInterval(pollingInterval.current); if (timeoutRef.current) clearTimeout(timeoutRef.current); }; }, [videoTitle, season, episode]); // Add effect to update loading state when videoUrl changes useEffect(() => { if (videoUrl) { setLoading(false); } }, [videoUrl]); if (error) { return (
😢

Error Playing Episode

{error}

); } if (loading || !videoFetched || !videoUrl) { return (
{poster ? ( {movieTitle} ) : (
)}

{progress && progress.progress < 100 ? `Preparing "${episodeTitle}"` : `Loading "${episodeTitle}"` }

{progress ? ( <>

{progress.progress < 5 ? 'Initializing your stream...' : progress.progress < 100 ? 'Your stream is being prepared.' : 'Almost ready! Starting playback soon...'}

{Math.round(progress.progress)}% complete

) : (
)}
); } // TV Show specific overlay elements that will be passed to VideoPlayer const tvShowOverlay = ( <> {/* Top info bar */}
{videoTitle} • {season} • Episode {episodeNumber}

{episodeTitle}

{/* Episodes button */}
); return (
); }; export default TVShowPlayer;