import { useEffect, useRef, useState } from "react"; import type { MediaSyncState } from "../hooks/useMediaSync"; interface VideoPlayerProps { src: string | null; startTime?: number; mediaSync: MediaSyncState; } /** * Video player with HLS support (lazy-loaded via dynamic import), * Safari native HLS fallback, and direct .mp4 playback. * Shows a placeholder when src is null. */ export default function VideoPlayer({ src, startTime, mediaSync }: VideoPlayerProps) { const { videoRef } = mediaSync; const hlsRef = useRef(null); const [error, setError] = useState(null); const startTimeSeeked = useRef(false); // Clamp startTime: NaN / negative → 0 const safeStart = startTime != null && Number.isFinite(startTime) && startTime > 0 ? startTime : 0; // Seek to startTime once after metadata loads useEffect(() => { const el = videoRef.current; if (!el || safeStart === 0) return; startTimeSeeked.current = false; const onLoaded = () => { if (!startTimeSeeked.current) { el.currentTime = safeStart; startTimeSeeked.current = true; } }; if (el.readyState >= 1) { onLoaded(); } else { el.addEventListener("loadedmetadata", onLoaded, { once: true }); return () => el.removeEventListener("loadedmetadata", onLoaded); } }, [videoRef, safeStart]); // HLS / native source attachment useEffect(() => { const el = videoRef.current; if (!el || !src) return; setError(null); let destroyed = false; const isHlsUrl = src.endsWith(".m3u8"); // Check native HLS support (Safari) const canPlayHlsNatively = el.canPlayType("application/vnd.apple.mpegurl") !== ""; if (isHlsUrl && !canPlayHlsNatively) { // Lazy-load hls.js void import("hls.js").then(({ default: Hls }) => { if (destroyed) return; if (!Hls.isSupported()) { setError("HLS playback is not supported in this browser."); return; } const hls = new Hls({ enableWorker: true, startPosition: safeStart > 0 ? safeStart : -1, }); hlsRef.current = hls; hls.on(Hls.Events.ERROR, (_event, data) => { if (data.fatal) { console.error("[VideoPlayer] Fatal HLS error:", data.type, data.details); setError(`Playback error: ${data.details}`); hls.destroy(); hlsRef.current = null; } }); hls.loadSource(src); hls.attachMedia(el); }); } else { // Native HLS (Safari) or plain .mp4 el.src = src; } return () => { destroyed = true; if (hlsRef.current) { hlsRef.current.destroy(); hlsRef.current = null; } }; }, [src, videoRef, safeStart]); if (!src) { return (

Video not available

The source video for this content has not been linked yet.
); } if (error) { return (

Playback Error

{error}
); } return (
); }