CameraFeed: recover from HLS blips, retry video instead of sticking on the still
Any fatal hls.js error set videoFailed permanently, so a brief 511NY feed drop left the tile on a 60s snapshot until it was reopened. Now fatal media/network errors get up to 3 in-place recoveries, and a tile that did fall back retries video every 30s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
e2364096f1
commit
fb299008f8
@@ -6,11 +6,17 @@ 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;
|
||||
// A failed stream is not given up on for good: 511NY feeds drop out briefly,
|
||||
// and a permanent fallback left pinned tiles showing a stale still image.
|
||||
const VIDEO_RETRY_MS = 30_000;
|
||||
// In-place recoveries hls.js gets per session before we fall back to the snapshot.
|
||||
const MAX_HLS_RECOVERIES = 3;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* a timer. A stream that fails falls back to the snapshot rather than a black box,
|
||||
* and video is retried every VIDEO_RETRY_MS while it stays down.
|
||||
*/
|
||||
export function CameraFeed({ camera, className }: { camera: Ny511Camera; className?: string }) {
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
@@ -24,6 +30,12 @@ export function CameraFeed({ camera, className }: { camera: Ny511Camera; classNa
|
||||
return () => clearInterval(id);
|
||||
}, [useVideo]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!videoFailed || !camera.video_url) return;
|
||||
const id = setTimeout(() => setVideoFailed(false), VIDEO_RETRY_MS);
|
||||
return () => clearTimeout(id);
|
||||
}, [videoFailed, camera.video_url]);
|
||||
|
||||
useEffect(() => {
|
||||
const video = videoRef.current;
|
||||
const src = camera.video_url;
|
||||
@@ -39,7 +51,15 @@ export function CameraFeed({ camera, className }: { camera: Ny511Camera; classNa
|
||||
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); });
|
||||
let recoveries = 0;
|
||||
hls.on(Hls.Events.ERROR, (_evt, data) => {
|
||||
if (!data.fatal || !hls) return;
|
||||
if (recoveries++ < MAX_HLS_RECOVERIES) {
|
||||
if (data.type === Hls.ErrorTypes.MEDIA_ERROR) { hls.recoverMediaError(); return; }
|
||||
if (data.type === Hls.ErrorTypes.NETWORK_ERROR) { hls.startLoad(); return; }
|
||||
}
|
||||
setVideoFailed(true);
|
||||
});
|
||||
hls.loadSource(src);
|
||||
hls.attachMedia(video);
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user