hls.js icon indicating copy to clipboard operation
hls.js copied to clipboard

FetchLoader has missing implementation of error retry logic presented in XhrLoader

Open mstyura opened this issue 7 months ago • 4 comments

What version of Hls.js are you using?

1.6.3

What browser (including version) are you using?

Any

What OS (including version) are you using?

Any

Test stream

No response

Configuration

{
}

Additional player setup steps

Use hls.js with error network retries enabled, something like this:

const MANIFEST_LOAD_RETRY_POLICY: Partial<RetryConfig> = {
  maxNumRetry: 10,
  retryDelayMs: 250,
  backoff: "exponential",
  shouldRetry: (retryConfig, retryCount, isTimeout, loaderResponse) => {
    if (!retryConfig) {
      return false;
    }
    const retryForHttpStatus = (httpStatus: number | undefined) =>
      // Do not retry on status 4xx, status 0 (CORS error), or undefined (decrypt/gap/parse error)
      (httpStatus === 0 && navigator.onLine === false) ||
      (!!httpStatus &&
        (httpStatus < 400 || httpStatus > 499 || httpStatus === 401));

    const httpStatus = loaderResponse?.code;
    const retry =
      retryCount < retryConfig.maxNumRetry &&
      (retryForHttpStatus(httpStatus) || !!isTimeout);

    return retry;
  },
};

const mergedConfig = {
  ...Hls.DefaultConfig,
  loader: FetchLoader,
};
if (mergedConfig.manifestLoadPolicy.default.errorRetry) {
  Object.assign(
    mergedConfig.manifestLoadPolicy.default.errorRetry,
    MANIFEST_LOAD_RETRY_POLICY
  );
}

Checklist

  • [x] The issue observed is not already reported by searching on Github under https://github.com/video-dev/hls.js/issues
  • [x] The issue occurs in the stable client (latest release) on https://hlsjs.video-dev.org/demo and not just on my page
  • [x] The issue occurs in the latest client (main branch) on https://hlsjs-dev.video-dev.org/demo and not just on my page
  • [x] The stream has correct Access-Control-Allow-Origin headers (CORS)
  • [x] There are no network errors such as 404s in the browser console when trying to play the stream

Steps to reproduce

  1. Use hls.js to play stream;
  2. Receive transient server error during main manifest download;

Expected behaviour

Download of manifest is retried (including 401 error due to custom retry config provided).

What actually happened?

Download of manifest is not retried, receiving fatal error and the following error handler does not trigger re-download:

          case Hls.ErrorTypes.NETWORK_ERROR:
            hls.startLoad();
            break;

No retry implemented in FetchLoader: https://github.com/video-dev/hls.js/blame/2fb519b6456299b59c9c33f760accece03d2e2f6/src/utils/fetch-loader.ts#L139-L143 While XhrLoader has proper retry logic: https://github.com/video-dev/hls.js/blame/2fb519b6456299b59c9c33f760accece03d2e2f6/src/utils/xhr-loader.ts#L237-L256

Console output

[log] > Debug logs enabled for "Hls instance" in hls.js version 1.6.3
HLSPlayer.tsx:230 effective hls.js config: {autoStartLoad: true, startPosition: -1, defaultAudioCodec: undefined, debug: true, capLevelOnFPSDrop: false, …}
HLSPlayer.tsx:426 [log] > stopLoad
HLSPlayer.tsx:426 [log] > loadSource:https://server/master.m3u8?tech=hls
HLSPlayer.tsx:426 [log] > [stream-controller]: Trigger BUFFER_RESET
HLSPlayer.tsx:427 [log] > attachMedia
HLSPlayer.tsx:427 [log] > [buffer-controller]: created media source: MediaSource
HLSPlayer.tsx:441 [log] > startLoad(-1)
HLSPlayer.tsx:441 [log] > [subtitle-stream-controller]: STOPPED->IDLE
hls.mjs:18235 [log] > [buffer-controller]: Media source opened
hls.mjs:19292 [log] > [buffer-controller]: checkPendingTracks (pending: 0 codec events expected: 0) {}
HLSPlayer.tsx:426  GET https://server/master.m3u8?tech=hls 401 (Unauthorized)
HLSPlayer.tsx:426 [warn] > [playlist-loader]: A network error (status 401) occurred while loading manifest
hls.mjs:9860 [log] > [stream-controller]: STOPPED->ERROR
hls.mjs:9860 [log] > [audio-stream-controller]: STOPPED->ERROR
hls.mjs:34774 [log] > stopLoad
hls.mjs:9860 [log] > [stream-controller]: ERROR->STOPPED
hls.mjs:9860 [log] > [audio-stream-controller]: ERROR->STOPPED
hls.mjs:9860 [log] > [subtitle-stream-controller]: IDLE->STOPPED
HLSPlayer.tsx:304 HLS.js error: fatal=true networkError, manifestLoadError
HLSPlayer.tsx:312 [log] > startLoad(-1)
HLSPlayer.tsx:312 [log] > [subtitle-stream-controller]: STOPPED->IDLE

Chrome media internals output


mstyura avatar Jul 30 '25 20:07 mstyura

Manifest load errors are fatal and therefore not retried. I'm not sure this is an issue with the FetchLoader specifically when the example is manifest load failure.

robwalch avatar Jul 30 '25 20:07 robwalch

No retry implemented in FetchLoader: https://github.com/video-dev/hls.js/blame/2fb519b6456299b59c9c33f760accece03d2e2f6/src/utils/fetch-loader.ts#L139-L143 While XhrLoader has proper retry logic: https://github.com/video-dev/hls.js/blame/2fb519b6456299b59c9c33f760accece03d2e2f6/src/utils/xhr-loader.ts#L237-L256

Would you like to contribute a fix for the issue?

robwalch avatar Jul 30 '25 20:07 robwalch

Would you like to contribute a fix for the issue?

I can try to do so, but can't promise I'll complete it quickly, because it looks like control flow within FetchLoader is somewhat complex and I'll need to dive deep to find a way to introduce retries and not change implementation too much. In case you already see how it should be fixed I don't mind you fix it :)

mstyura avatar Jul 30 '25 20:07 mstyura

Priority is lower for me. It doesn't impact the default loader/setup. It isn't a regression. And, it does not impact fragment retry handled by stream controllers.

robwalch avatar Jul 31 '25 03:07 robwalch