import { useQuery } from "@tanstack/react-query"; import { useEffect, useState } from "react"; import { SCRUB_MANIFEST, scrubManifestSchema, videoUrl, type ScrubManifest } from "~/lib/video"; /** Attaches the HLS master to `video`: hls.js where MSE exists, native HLS (Safari/iOS) otherwise. */ export function useHlsSource(video: HTMLVideoElement | null, src: string): string | null { const [error, setError] = useState(null); useEffect(() => { if (!video) return; let disposed = false; let destroy: (() => void) | undefined; setError(null); void import("hls.js").then(({ default: Hls }) => { if (disposed) return; if (!Hls.isSupported()) { if (video.canPlayType("application/vnd.apple.mpegurl")) video.src = src; else setError("This browser cannot play HLS video"); return; } const hls = new Hls({ capLevelToPlayerSize: true, startLevel: -1 }); hls.on(Hls.Events.ERROR, (_event, data) => { if (!data.fatal) return; if (data.type === Hls.ErrorTypes.NETWORK_ERROR) hls.startLoad(); else if (data.type === Hls.ErrorTypes.MEDIA_ERROR) hls.recoverMediaError(); else setError("Video playback failed"); }); hls.loadSource(src); hls.attachMedia(video); destroy = () => hls.destroy(); }); return () => { disposed = true; destroy?.(); }; }, [video, src]); return error; } /** Loads the scrub sprite manifest; a video without one still plays, it just scrubs without thumbnails. */ export function useScrubManifest(id: string, version: string): ScrubManifest | null { const query = useQuery({ queryKey: ["video", id, version, "scrub"], staleTime: Infinity, retry: false, queryFn: async ({ signal }) => { const response = await fetch(videoUrl(id, version, SCRUB_MANIFEST), { signal }); if (!response.ok) return null; const parsed = scrubManifestSchema.safeParse(await response.json()); return parsed.success ? parsed.data : null; }, }); const manifest = query.data ?? null; useEffect(() => { // Warm the sprite sheets so the first drag already has thumbnails. for (const sheet of manifest?.sheets ?? []) new Image().src = videoUrl(id, version, sheet); }, [manifest, id, version]); return manifest; }