From d60524815f8a280b0cdd825a21d109608cd7bdea Mon Sep 17 00:00:00 2001 From: Josue Zamudio Date: Mon, 31 Aug 2026 01:22:22 +0000 Subject: [PATCH] hls.js: put retry budget where 1.5.x actually reads it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit maxNumRetry was set at the top of the load policy, which hls.js 1.5.x ignores — it reads the budget from the errorRetry/timeoutRetry sub-objects. Net effect before: a fragment-load *timeout* (a CDN that starts sending then stalls) got only the default 1 retry before fatal fragLoadTimeOut. Now (verified resolved in-browser): maxTimeToFirstByteMs 15s first byte maxLoadTimeMs 30s total load (finite -> stalls time out) timeoutRetry.maxNumRetry 6 was 1 errorRetry.maxNumRetry 8 our proxy's 502 path --- static/app.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/static/app.js b/static/app.js index 4edcaca..a40b443 100644 --- a/static/app.js +++ b/static/app.js @@ -798,7 +798,21 @@ async function play(ep_id, ep_no) { // before our proxy's 12s upstream attempt (+300ms retry) can answer with // its 502 — keep TTFT past the proxy timeout so slow-but-alive segments // finish, and the proxy's retry path gets a chance - hls = new Hls({ fragLoadPolicy: { default: { maxTimeToFirstByteMs: 15000, maxNumRetry: 8 } } }); + // hls.js 1.5.x reads the retry budget from the errorRetry / timeoutRetry + // sub-objects (a top-level maxNumRetry is ignored — that was the old bug). + // maxTimeToFirstByteMs first byte within 15s + // maxLoadTimeMs total load within 30s — MUST be finite, else a CDN + // that starts sending then stalls hangs us forever + // timeoutRetry retries for a load *timeout* (was default 1!) + // errorRetry retries for HTTP errors like our proxy's 502 + // Together: a slow/stalled CDN gets retried with backoff instead of one + // shot -> fatal fragLoadTimeOut. + hls = new Hls({ fragLoadPolicy: { default: { + maxTimeToFirstByteMs: 15000, + maxLoadTimeMs: 30000, + timeoutRetry: { maxNumRetry: 6, retryDelayMs: 1000, maxRetryDelayMs: 20000, backoff: "linear" }, + errorRetry: { maxNumRetry: 8, retryDelayMs: 1000, maxRetryDelayMs: 20000, backoff: "linear" }, + } } }); hls.on(Hls.Events.ERROR, (_, data) => { if (data.fatal) { stopHls();