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'; import { TvShowCardData } from './ContentCard'; 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[]; 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, 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 [showData, setShowData] = useState(null); const [selectedImage, setSelectedImage] = useState(); const [imageLoaded, setImageLoaded] = useState(false); const [ratingInfo, setRatingInfo] = useState<{ rating: string; description: string } | null>(null); const containerRef = useRef(null); const videoRef = useRef(null); const { toast } = useToast(); const pollingInterval = useRef(null); const timeoutRef = useRef(null); const videoFetchedRef = useRef(false); // Reset imageLoaded whenever we pick a new image useEffect(() => { setImageLoaded(false); }, [selectedImage]); // Parse episode info const getEpisodeInfo = () => { if (!episode) return { number: '1', title: 'Unknown Episode' }; const match = episode.match(/E(\d+)\s*-\s*(.+?)(?=\.\w+$)/i); return { number: match ? match[1] : '1', title: match ? match[2].trim() : 'Unknown Episode' }; }; const { number: episodeNumber, title: episodeTitle } = getEpisodeInfo(); // Random image selector with fallback const selectRandomImage = (cardData: TvShowCardData) => { if (cardData.banner?.length) { return cardData.banner[Math.floor(Math.random() * cardData.banner.length)].image; } if (cardData.portrait?.length) { return cardData.portrait[Math.floor(Math.random() * cardData.portrait.length)].image; } return cardData.image; }; // Fetch or poll for the video URL const fetchMovieLink = async () => { if (videoFetchedRef.current) return; try { const response = await getEpisodeLinkByTitle(videoTitle, season, episode); if (response.url) { pollingInterval.current && clearInterval(pollingInterval.current); setVideoUrl(response.url); setVideoFetched(true); videoFetchedRef.current = true; setLoading(false); } else if (response.progress_url) { const poll = async () => { try { const res = await fetch(response.progress_url); const data = await res.json(); setProgress(data.progress); if (data.progress.progress >= 100) { pollingInterval.current && clearInterval(pollingInterval.current); timeoutRef.current = setTimeout(fetchMovieLink, 5000); } } catch (e) { console.error(e); } }; pollingInterval.current = setInterval(poll, 2000); } else { throw new Error('No URL or progress URL'); } } catch (e) { console.error(e); setError('Failed to load episode'); toast({ title: 'Error', description: 'Could not load the episode', variant: 'destructive' }); setLoading(false); } }; // Main init: fetch TV show card, then fetch link useEffect(() => { if (!videoTitle || !season || !episode) { setError('Missing required video information'); setLoading(false); return; } setLoading(true); setError(null); setVideoUrl(null); setVideoFetched(false); videoFetchedRef.current = false; setProgress(null); const init = async () => { try { const data = await getTvShowCard(videoTitle); setShowData(data); const img = selectRandomImage(data); setSelectedImage(img); const ratings = data.data?.contentRatings || contentRatings || []; if (ratings.length) { const us = ratings.find(r => r.country === 'usa') || ratings[0]; setRatingInfo({ rating: us.name || 'NR', description: us.description || '' }); } } catch (e) { console.error('Show card fetch error:', e); setError('Failed to load show data'); toast({ title: 'Error', description: 'Could not load show data', variant: 'destructive' }); setLoading(false); return; } await fetchMovieLink(); }; init(); return () => { pollingInterval.current && clearInterval(pollingInterval.current); timeoutRef.current && clearTimeout(timeoutRef.current); }; }, [videoTitle, season, episode]); useEffect(() => { if (videoUrl) setLoading(false); }, [videoUrl]); if (error) { return (
😢

Error Playing Episode

{error}

); } if (loading || !videoFetched || !videoUrl) { return ( <> {/* Hero backdrop with fade-in */}
setImageLoaded(true)} onError={(e) => { const target = e.target as HTMLImageElement; target.src = '/placeholder.svg'; }} className={`w-full h-full object-cover transition-opacity duration-700 ease-in-out ${ imageLoaded ? 'opacity-100' : 'opacity-0' }`} />
{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

) : (
)}
); } const tvShowOverlay = ( <>
{videoTitle} • {season} • Episode {episodeNumber}

{episodeTitle}

); return (
); }; export default TVShowPlayer;