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

Configure live polling update tolerance

Open qiushaocloud opened this issue 11 months ago • 5 comments

What version of Hls.js are you using?

v1.5.20

What browser (including version) are you using?

safari

What OS (including version) are you using?

iphone, ios

Test stream

No response

Configuration

{}

Additional player setup steps

No response

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. Open the HLS.js player in Safari on an iPhone and start playing an HLS stream.
  2. Background the Safari browser, allowing the device to automatically lock and turn off the screen.
  3. Stop the OBS stream and wait for a few minutes.
  4. Reopen Safari and return to the HLS.js player.

Expected behaviour

After reopening Safari, the player should detect that the stream has stopped and throw an error event.

What actually happened?

The player remains in a loading state indefinitely without throwing any error event, even though the stream has stopped.

Console output

not error event

Chrome media internals output


qiushaocloud avatar Feb 14 '25 10:02 qiushaocloud

Please include debug logs, preferably with timestamps to show the activity before and after locking the device.

robwalch avatar Feb 14 '25 13:02 robwalch

Are the HLS playlists for the live stream still served after stopping OBS?

If so the current behavior is to keep reloading in case it comes back online. Serving an empty playlist or returning an HTTP status error code would produce a fatal error.

From the client, you can use HLS.js level event "misses" (no playlist change) and stall error events to detect that the live source is no updating.

Would you like a feature that stops HLS.js with an error after a certain period or number retries on a stalled live playlist?

robwalch avatar Feb 15 '25 16:02 robwalch

Not OP but I think a config for max retries on a stalled playlist would be great. I'm facing similar where HLS.js will continue to fetch the same manifest despite no changes as the stream has ended.

daniemo2 avatar Feb 16 '25 16:02 daniemo2

I'm facing the same issue using Cloudflare streams, but on desktop. I pause the player, leave it idle for a while, when I come back and try to play again, I get HLS error. I didn't modify the config, I'm using HLS through Vidstack.

Error data:

Image

Error stack:

Error: Playback stalling at @91.270427 due to low buffer ({"len":68.489572,"start":84.261333,"end":159.759999})
    at GapController._reportStall (https://cdn.jsdelivr.net/npm/hls.js@%5E1.5.0/dist/hls.js:27253:21)
    at GapController.poll (https://cdn.jsdelivr.net/npm/hls.js@%5E1.5.0/dist/hls.js:27192:14)
    at StreamController.checkBuffer (https://cdn.jsdelivr.net/npm/hls.js@%5E1.5.0/dist/hls.js:28078:23)
    at StreamController.onTickEnd (https://cdn.jsdelivr.net/npm/hls.js@%5E1.5.0/dist/hls.js:27525:12)
    at StreamController.doTick (https://cdn.jsdelivr.net/npm/hls.js@%5E1.5.0/dist/hls.js:27521:12)
    at StreamController.tick (https://cdn.jsdelivr.net/npm/hls.js@%5E1.5.0/dist/hls.js:7964:14)

Config of HLS.js instance:

{
    "autoStartLoad": true,
    "startPosition": -1,
    "debug": false,
    "capLevelOnFPSDrop": false,
    "capLevelToPlayerSize": false,
    "ignoreDevicePixelRatio": false,
    "preferManagedMediaSource": true,
    "initialLiveManifestSize": 1,
    "maxBufferLength": 30,
    "frontBufferFlushThreshold": null,
    "maxBufferSize": 60000000,
    "maxBufferHole": 0.1,
    "highBufferWatchdogPeriod": 2,
    "nudgeOffset": 0.1,
    "nudgeMaxRetry": 3,
    "maxFragLookUpTolerance": 0.25,
    "liveSyncDurationCount": 3,
    "liveMaxLatencyDurationCount": null,
    "maxLiveSyncPlaybackRate": 1,
    "liveDurationInfinity": false,
    "liveBackBufferLength": null,
    "maxMaxBufferLength": 600,
    "enableWorker": true,
    "workerPath": null,
    "enableSoftwareAES": true,
    "startFragPrefetch": false,
    "fpsDroppedMonitoringPeriod": 5000,
    "fpsDroppedMonitoringThreshold": 0.2,
    "appendErrorMaxRetry": 3,
    "stretchShortVideoTrack": false,
    "maxAudioFramesDrift": 1,
    "forceKeyFrameOnDiscontinuity": true,
    "abrEwmaFastLive": 3,
    "abrEwmaSlowLive": 9,
    "abrEwmaFastVoD": 3,
    "abrEwmaSlowVoD": 9,
    "abrEwmaDefaultEstimate": 500000,
    "abrEwmaDefaultEstimateMax": 5000000,
    "abrBandWidthFactor": 0.95,
    "abrBandWidthUpFactor": 0.7,
    "abrMaxWithRealBitrate": false,
    "maxStarvationDelay": 4,
    "maxLoadingDelay": 4,
    "minAutoBitrate": 0,
    "emeEnabled": false,
    "drmSystems": {},
    "drmSystemOptions": {},
    "testBandwidth": true,
    "progressive": false,
    "lowLatencyMode": false,
    "enableDateRangeMetadataCues": true,
    "enableEmsgMetadataCues": true,
    "enableID3MetadataCues": true,
    "useMediaCapabilities": true,
    "certLoadPolicy": {
        "default": {
            "maxTimeToFirstByteMs": 8000,
            "maxLoadTimeMs": 20000,
            "timeoutRetry": null,
            "errorRetry": null
        }
    },
    "keyLoadPolicy": {
        "default": {
            "maxTimeToFirstByteMs": 8000,
            "maxLoadTimeMs": 20000,
            "timeoutRetry": {
                "maxNumRetry": 1,
                "retryDelayMs": 1000,
                "maxRetryDelayMs": 20000,
                "backoff": "linear"
            },
            "errorRetry": {
                "maxNumRetry": 8,
                "retryDelayMs": 1000,
                "maxRetryDelayMs": 20000,
                "backoff": "linear"
            }
        }
    },
    "manifestLoadPolicy": {
        "default": {
            "maxTimeToFirstByteMs": null,
            "maxLoadTimeMs": 20000,
            "timeoutRetry": {
                "maxNumRetry": 2,
                "retryDelayMs": 0,
                "maxRetryDelayMs": 0
            },
            "errorRetry": {
                "maxNumRetry": 1,
                "retryDelayMs": 1000,
                "maxRetryDelayMs": 8000
            }
        }
    },
    "playlistLoadPolicy": {
        "default": {
            "maxTimeToFirstByteMs": 10000,
            "maxLoadTimeMs": 20000,
            "timeoutRetry": {
                "maxNumRetry": 2,
                "retryDelayMs": 0,
                "maxRetryDelayMs": 0
            },
            "errorRetry": {
                "maxNumRetry": 2,
                "retryDelayMs": 1000,
                "maxRetryDelayMs": 8000
            }
        }
    },
    "fragLoadPolicy": {
        "default": {
            "maxTimeToFirstByteMs": 10000,
            "maxLoadTimeMs": 120000,
            "timeoutRetry": {
                "maxNumRetry": 4,
                "retryDelayMs": 0,
                "maxRetryDelayMs": 0
            },
            "errorRetry": {
                "maxNumRetry": 6,
                "retryDelayMs": 1000,
                "maxRetryDelayMs": 8000
            }
        }
    },
    "steeringManifestLoadPolicy": {
        "default": {
            "maxTimeToFirstByteMs": 10000,
            "maxLoadTimeMs": 20000,
            "timeoutRetry": {
                "maxNumRetry": 2,
                "retryDelayMs": 0,
                "maxRetryDelayMs": 0
            },
            "errorRetry": {
                "maxNumRetry": 1,
                "retryDelayMs": 1000,
                "maxRetryDelayMs": 8000
            }
        }
    },
    "manifestLoadingTimeOut": 10000,
    "manifestLoadingMaxRetry": 1,
    "manifestLoadingRetryDelay": 1000,
    "manifestLoadingMaxRetryTimeout": 64000,
    "levelLoadingTimeOut": 10000,
    "levelLoadingMaxRetry": 4,
    "levelLoadingRetryDelay": 1000,
    "levelLoadingMaxRetryTimeout": 64000,
    "fragLoadingTimeOut": 20000,
    "fragLoadingMaxRetry": 6,
    "fragLoadingRetryDelay": 1000,
    "fragLoadingMaxRetryTimeout": 64000,
    "cueHandler": {},
    "enableWebVTT": true,
    "enableIMSC1": true,
    "enableCEA708Captions": true,
    "captionsTextTrack1Label": "English",
    "captionsTextTrack1LanguageCode": "en",
    "captionsTextTrack2Label": "Spanish",
    "captionsTextTrack2LanguageCode": "es",
    "captionsTextTrack3Label": "Unknown CC",
    "captionsTextTrack3LanguageCode": "",
    "captionsTextTrack4Label": "Unknown CC",
    "captionsTextTrack4LanguageCode": "",
    "renderTextTracksNatively": false
}

wintercounter avatar Mar 10 '25 13:03 wintercounter

Changing to an enhancement request to stop poling live playlists that do not update after a certain amount (of time or number of refreshes) defined in the config.

robwalch avatar Jun 09 '25 22:06 robwalch