fix(hls): retry on playback failure with exponential backoff
This commit is contained in:
parent
ce31a45124
commit
60e306d1db
1 changed files with 48 additions and 10 deletions
|
|
@ -387,23 +387,61 @@ function HlsPreview({ assetId, muted = true, controls = false, className }) {
|
||||||
if (!assetId || !videoRef.current) return;
|
if (!assetId || !videoRef.current) return;
|
||||||
const url = '/live/' + assetId + '/index.m3u8';
|
const url = '/live/' + assetId + '/index.m3u8';
|
||||||
const v = videoRef.current;
|
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.
|
// Safari can play HLS natively; everything else needs hls.js.
|
||||||
if (v.canPlayType('application/vnd.apple.mpegurl')) {
|
if (v.canPlayType('application/vnd.apple.mpegurl')) {
|
||||||
v.src = url;
|
const tryLoad = () => {
|
||||||
const onErr = () => setErr('playback failed');
|
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);
|
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; }
|
if (!window.Hls) { setErr('hls.js missing'); return; }
|
||||||
|
|
||||||
const hls = new window.Hls({ liveSyncDurationCount: 2, lowLatencyMode: true });
|
let hls = null;
|
||||||
hls.loadSource(url);
|
|
||||||
hls.attachMedia(v);
|
const startHls = () => {
|
||||||
hls.on(window.Hls.Events.ERROR, (_e, data) => {
|
if (destroyed) return;
|
||||||
if (data.fatal) setErr(data.details || 'hls error');
|
hls = new window.Hls({ liveSyncDurationCount: 2, lowLatencyMode: true });
|
||||||
});
|
hls.loadSource(url);
|
||||||
return () => { try { hls.destroy(); } catch (_) {} };
|
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]);
|
}, [assetId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue