diff --git a/components/player/hooks/useHLSPreloader.ts b/components/player/hooks/useHLSPreloader.ts index 8500a3f..65603c1 100644 --- a/components/player/hooks/useHLSPreloader.ts +++ b/components/player/hooks/useHLSPreloader.ts @@ -12,29 +12,22 @@ export function useHLSPreloader({ src, currentTime }: UseHLSPreloaderProps) { const segmentsRef = useRef([]); const [isManifestLoaded, setIsManifestLoaded] = useState(false); const lastStartIndexRef = useRef(-1); + const isInitializedRef = useRef(false); + const downloadTimeoutRef = useRef(null); // Fetch and parse manifest when src changes useEffect(() => { if (!src || !src.endsWith('.m3u8')) return; + // Reset initialization flag when src changes + isInitializedRef.current = false; + lastStartIndexRef.current = -1; + const fetchManifest = async () => { try { console.log('[Preloader] Fetching manifest:', src); - // Clear cache for this video on mount (fresh start each time) - if ('caches' in window) { - const cache = await caches.open('video-cache-v1'); - const requests = await cache.keys(); - const baseUrl = src.substring(0, src.lastIndexOf('/') + 1); - - for (const request of requests) { - if (request.url.startsWith(baseUrl)) { - await cache.delete(request); - } - } - console.log('[Preloader] Cleared previous cache for this video'); - } - + // Parse manifest but keep existing cache (for faster playback on revisit) const segments = await parseHLSManifest(src); segmentsRef.current = segments; setIsManifestLoaded(true); @@ -48,43 +41,60 @@ export function useHLSPreloader({ src, currentTime }: UseHLSPreloaderProps) { fetchManifest(); }, [src]); - // Manage downloads based on currentTime + // Manage downloads based on currentTime with debounce useEffect(() => { if (!isManifestLoaded || segmentsRef.current.length === 0) return; - // Find segment index for currentTime - let startIndex = 0; - for (let i = 0; i < segmentsRef.current.length; i++) { - if (currentTime < segmentsRef.current[i].startTime + segmentsRef.current[i].duration) { - startIndex = i; - break; + // Clear any pending download timeout + if (downloadTimeoutRef.current) { + clearTimeout(downloadTimeoutRef.current); + } + + // Debounce to avoid rapid restarts during initialization + downloadTimeoutRef.current = setTimeout(() => { + // Find segment index for currentTime + let startIndex = 0; + for (let i = 0; i < segmentsRef.current.length; i++) { + if (currentTime < segmentsRef.current[i].startTime + segmentsRef.current[i].duration) { + startIndex = i; + break; + } } - } - if (startIndex >= segmentsRef.current.length) return; + if (startIndex >= segmentsRef.current.length) return; - // Check if this is sequential playback or a seek - const diff = startIndex - lastStartIndexRef.current; - const isSequential = diff >= 0 && diff < 3; + // Check if this is sequential playback or a seek + const diff = startIndex - lastStartIndexRef.current; + const isSequential = diff >= 0 && diff < 3; - if (isSequential && abortControllerRef.current) { - return; // Continue current download - } + // Skip if already downloading sequentially + if (isSequential && isInitializedRef.current && abortControllerRef.current) { + return; // Continue current download + } - console.log(`[Preloader] Seek detected. Current Time: ${currentTime.toFixed(2)}s. Starting from segment ${startIndex}.`); - lastStartIndexRef.current = startIndex; + // Only log on significant seeks or initial start + if (!isInitializedRef.current) { + console.log(`[Preloader] Initial start at segment ${startIndex} (${currentTime.toFixed(2)}s)`); + isInitializedRef.current = true; + } else if (!isSequential) { + console.log(`[Preloader] Seek detected. Current Time: ${currentTime.toFixed(2)}s. Starting from segment ${startIndex}.`); + } - // Abort previous queue and start new one - if (abortControllerRef.current) { - abortControllerRef.current.abort(); - } - abortControllerRef.current = new AbortController(); + lastStartIndexRef.current = startIndex; + + // Abort previous queue and start new one + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + abortControllerRef.current = new AbortController(); + + downloadSegmentQueue({ + segments: segmentsRef.current, + startIndex, + signal: abortControllerRef.current.signal + }); + }, isInitializedRef.current ? 0 : 100); // 100ms debounce on initial load only - downloadSegmentQueue({ - segments: segmentsRef.current, - startIndex, - signal: abortControllerRef.current.signal - }); }, [isManifestLoaded, currentTime]); // Cleanup on unmount