import { useState, useEffect, useRef } from 'react'; import { X, Settings } from 'lucide-react'; import { Caption, SourceData } from '@/lib/api'; import { createClient } from '@/lib/supabase/client'; import { savePlaybackProgress } from '@/lib/history-service'; interface VideoPlayerProps { url: string; captions?: Caption[]; sources?: SourceData[]; onQualityChange?: (source: SourceData) => void; currentResolution?: number; onClose: () => void; // History Props subjectId?: string; type?: 'movie' | 'series' | 'dracin'; title?: string; poster?: string; season?: number; episode?: number; startTime?: number; } export default function VideoPlayer({ url, captions = [], sources = [], onQualityChange, currentResolution, onClose, subjectId, type, title, poster, season, episode, startTime = 0 }: VideoPlayerProps) { const [processedCaptions, setProcessedCaptions] = useState<{ id: string; url: string; label: string; lang: string }[]>([]); const videoRef = useRef(null); const [showQualityMenu, setShowQualityMenu] = useState(false); const [savedTime, setSavedTime] = useState(startTime); const supabase = createClient(); // Initial load restoration useEffect(() => { if (videoRef.current && savedTime > 0) { videoRef.current.currentTime = savedTime; } }, [url]); // History Tracking useEffect(() => { if (!subjectId || !type || !title) return; const interval = setInterval(() => { saveProgress(); }, 15000); // Save every 15 seconds return () => { clearInterval(interval); saveProgress(); // Final save on unmount }; }, [subjectId, type, title, season, episode]); const saveProgress = async () => { if (!videoRef.current || !subjectId || !type || !title) return; const currentTime = videoRef.current.currentTime; const duration = videoRef.current.duration; if (currentTime === 0 && duration === 0) return; savePlaybackProgress({ subjectId, type, title, poster: poster || '', season, episode, lastPosition: currentTime, duration }); }; useEffect(() => { const processCaptions = async () => { if (!captions.length) return; const processed = await Promise.all( captions.map(async (cap) => { try { const response = await fetch(cap.url); if (!response.ok) return null; const srtText = await response.text(); // Simple SRT to VTT conversion: // 1. Replace commas in timestamps with dots // 2. Add WEBVTT header const vttText = "WEBVTT\n\n" + srtText.replace(/(^\d+)\s+$/gm, '$1').replace(/(\d{2}:\d{2}:\d{2}),(\d{3})/g, '$1.$2'); const blob = new Blob([vttText], { type: 'text/vtt' }); const blobUrl = URL.createObjectURL(blob); return { id: cap.id, url: blobUrl, label: cap.lanName, lang: cap.lan }; } catch (error) { console.error("Failed to process caption:", cap.lanName, error); return null; } }) ); const validCaptions = processed.filter((c): c is { id: string; url: string; label: string; lang: string } => c !== null); setProcessedCaptions(validCaptions); }; processCaptions(); // Cleanup blob URLs return () => { processedCaptions.forEach(c => URL.revokeObjectURL(c.url)); }; }, [captions]); const handleQualityChange = (source: SourceData) => { if (videoRef.current) { setSavedTime(videoRef.current.currentTime); } setShowQualityMenu(false); if (onQualityChange) { onQualityChange(source); } }; return (
{/* Quality Selector */} {sources.length > 0 && onQualityChange && (
{showQualityMenu && (
{sources.map((s) => ( ))}
)}
)}
); }