fix(hls): retry on playback failure with exponential backoff

This commit is contained in:
Zac Gaetano 2026-05-24 16:52:04 -04:00
parent ce31a45124
commit 60e306d1db

View file

@ -387,23 +387,61 @@ function HlsPreview({ assetId, muted = true, controls = false, className }) {
if (!assetId || !videoRef.current) return;
const url = '/live/' + assetId + '/index.m3u8';
const v = videoRef.current;
let destroyed = false;
let retryTimer = 0;
let retryCount = 0;
const MAX_RETRIES = 8;
const clearRetry = () => { if (retryTimer) { clearTimeout(retryTimer); retryTimer = 0; } };
// Safari can play HLS natively; everything else needs hls.js.
if (v.canPlayType('application/vnd.apple.mpegurl')) {
v.src = url;
const onErr = () => setErr('playback failed');
const tryLoad = () => {
if (destroyed) return;
v.removeAttribute('src');
v.load();
v.src = url;
v.play().catch(() => {});
};
const onErr = () => {
if (destroyed || retryCount >= MAX_RETRIES) { setErr('playback failed'); return; }
retryCount++;
clearRetry();
retryTimer = setTimeout(tryLoad, Math.min(500 * Math.pow(2, retryCount - 1), 8000));
setErr('connecting…');
};
v.addEventListener('error', onErr);
return () => v.removeEventListener('error', onErr);
v.addEventListener('playing', () => { retryCount = 0; setErr(null); }, { once: false });
tryLoad();
return () => { destroyed = true; clearRetry(); v.removeEventListener('error', onErr); };
}
if (!window.Hls) { setErr('hls.js missing'); return; }
const hls = new window.Hls({ liveSyncDurationCount: 2, lowLatencyMode: true });
hls.loadSource(url);
hls.attachMedia(v);
hls.on(window.Hls.Events.ERROR, (_e, data) => {
if (data.fatal) setErr(data.details || 'hls error');
});
return () => { try { hls.destroy(); } catch (_) {} };
let hls = null;
const startHls = () => {
if (destroyed) return;
hls = new window.Hls({ liveSyncDurationCount: 2, lowLatencyMode: true });
hls.loadSource(url);
hls.attachMedia(v);
hls.on(window.Hls.Events.ERROR, (_e, data) => {
if (data.fatal) {
if (retryCount >= MAX_RETRIES) { setErr(data.details || 'hls error'); return; }
retryCount++;
clearRetry();
try { hls.destroy(); } catch (_) {}
hls = null;
setErr('connecting…');
retryTimer = setTimeout(startHls, Math.min(500 * Math.pow(2, retryCount - 1), 8000));
}
});
hls.on(window.Hls.Events.MANIFEST_PARSED, () => { retryCount = 0; setErr(null); });
};
startHls();
v.play().catch(() => {});
return () => { destroyed = true; clearRetry(); if (hls) { try { hls.destroy(); } catch (_) {} } };
}, [assetId]);
return (