"use client"; import { useEffect, useRef, useState } from "react"; import type HlsType from "hls.js"; import type { Ny511Camera } from "@/lib/types"; // 511NY stills are served with max-age=60, so refreshing faster buys nothing. const SNAPSHOT_REFRESH_MS = 60_000; /** * One 511NY camera: the live HLS stream when the camera has one (NYSDOT's * skyvdn hosts send Access-Control-Allow-Origin: *), otherwise its snapshot on * a timer. A stream that fails falls back to the snapshot rather than a black box. */ export function CameraFeed({ camera, className }: { camera: Ny511Camera; className?: string }) { const videoRef = useRef(null); const [videoFailed, setVideoFailed] = useState(false); const [tick, setTick] = useState(() => Date.now()); const useVideo = !!camera.video_url && !videoFailed; useEffect(() => { if (useVideo) return; const id = setInterval(() => setTick(Date.now()), SNAPSHOT_REFRESH_MS); return () => clearInterval(id); }, [useVideo]); useEffect(() => { const video = videoRef.current; const src = camera.video_url; if (!useVideo || !video || !src) return; let hls: HlsType | null = null; let cancelled = false; if (video.canPlayType("application/vnd.apple.mpegurl")) { video.src = src; // Safari/iOS play HLS natively } else { import("hls.js") .then(({ default: Hls }) => { if (cancelled) return; if (!Hls.isSupported()) { setVideoFailed(true); return; } hls = new Hls({ maxBufferLength: 10, backBufferLength: 0 }); hls.on(Hls.Events.ERROR, (_evt, data) => { if (data.fatal) setVideoFailed(true); }); hls.loadSource(src); hls.attachMedia(video); }) .catch(() => setVideoFailed(true)); } return () => { cancelled = true; hls?.destroy(); video.removeAttribute("src"); video.load(); // stop the download, not just the playback }; }, [useVideo, camera.video_url]); if (useVideo) { return