What version of HLS.js are you using?
v1.7.0-rc.1 — also reproduced with the latest dev build (v1.7.0-rc.1.0.canary.11888 from hlsjs-dev.video-dev.org). The relevant code in preloadAssets() is the same on current master.
What browser (including version) are you using?
Chromium 148.0.7778.280 (Electron 42.6.0, offscreen/headless harness used to capture the attached logs)
What OS (including version) are you using?
Linux (Amazon Linux 2023, kernel 6.12, aarch64)
Test stream
A live LL-HLS stream (CMAF, 4.8s segments, ~10 minute window) carrying HLS Interstitials dateranges:
- one preroll daterange:
CUE="ONCE,PRE" with X-ASSET-LIST (32s of assets), no X-RESUME-OFFSET, so the player resumes at the live edge when it ends
- recurring midroll dateranges:
X-ASSET-LIST, 24s duration, one every 48s, X-RESUME-OFFSET=24
The endpoint is session-bound so I can't share it publicly, but full debug:true console logs of one failing and one succeeding join (identical player page and config, hostnames/session ids redacted) are here:
https://gist.github.com/philhar88/caf08dec5946534c75768132153adca4
Configuration
{
"debug": true,
"lowLatencyMode": true,
"backBufferLength": 90
}
Additional player setup steps
None. Default interstitials handling (no custom handlers, no seeks issued by the page).
Checklist
Steps to reproduce
- Play a live stream with an interstitial preroll daterange (
CUE="ONCE,PRE", no X-RESUME-OFFSET, so the player resumes at the live edge after the preroll) and recurring midroll X-ASSET-LIST dateranges.
- Join the stream such that the live resume point at preroll end falls inside a midroll daterange's window (i.e. the live edge advances into a break while the preroll is playing).
- When the preroll ends and the player enters the in-progress midroll, inspect the query string of the
X-ASSET-LIST HTTP request.
The bug is intermittent: under the same join conditions, some joins carry _HLS_start_offset and some don't — in repeated attempts roughly half miss it. The frequency appears to depend on buffering timing during the preroll rather than on any specific config value (see below); it reproduced across different buffer/latency tunings.
Expected behaviour
Whenever the player enters an interstitial partway through its scheduled window on a live join, the asset-list request should carry _HLS_start_offset=<seconds into the break>, so the asset-list server can respond with an asset list trimmed to the remaining break duration.
Expected (and observed on the succeeding joins):
[interstitials]: [live join (preroll)] Advancing timeline position to 673.0860000000001
[interstitials]: Load interstitial asset 1/0 ["midroll-1784227059200" 662.40-686.40] live-start: 673.0870000000001 start-offset: 10.687 (timeline-start: 662.4000000000001)
→ the request URL ends with ..._HLS_primary_id=<id>&_HLS_start_offset=10.687, and the server returns an asset list covering the remaining ~13s of the break, ending flush with the break's resume point.
What actually happened?
On the failing joins, the same code path computes start-offset: undefined and the asset-list request goes out with no _HLS_start_offset parameter, even though the player is joining ~10.8 seconds into the 24-second break:
[interstitials]: [live join (preroll)] Advancing timeline position to 673.2280000000001
[interstitials]: INTERSTITIALS_BUFFERED_TO_BOUNDARY ["midroll-1784227395200": 662.40-686.40] (-10.83 remaining)
[interstitials]: Load interstitial asset 1/0 ["midroll-1784227395200" 662.40-686.40] live-start: 659.2 start-offset: undefined (timeline-start: 662.4000000000001)
The server then returns the full 24s asset list instead of the remainder. The player partially compensates by skipping into the pod — the very next line shows it knows the elapsed time it failed to send:
[interstitials]: Set HlsAssetPlayer: ["midroll-1784227395200-1-1554257213" 662.40-686.40] <uuid> append-in-place startPosition: 10.827999999999975
But the asset durations no longer match the break window: assets whose playout window has already elapsed get marked started/ended almost instantly (their tracking fires for content that never renders), and after the pod the primary can resume before the break's resume point, leaving playback several seconds further behind live than on the succeeding joins.
Where the divergence appears to come from (from comparing the failing and succeeding logs):
In preloadAssets() (src/controller/interstitials-controller.ts), when the preroll has just ended playingItem is null, so the offset is derived from this.hls.startPosition:
liveStartPosition = playingItem
? playingItem.end + playingItem.event.duration
: this.hls.startPosition;
if (liveStartPosition === -1) {
liveStartPosition = this.hls.liveSyncPosition || 0;
}
...
const startOffset = liveStartPosition - timelineStart;
if (startOffset > 0) {
hlsStartOffset = Math.round(startOffset * 1000) / 1000;
}
-
On succeeding joins, hls.startPosition still reports the live-edge value at this instant, e.g. live-start: 673.087 → offset 10.687. ✔
-
On failing joins, live-start instead equals a position from ~17s earlier: while the preroll was still playing, the controller pre-buffered primary content at the pre-break resumption point and called startLoad() with it:
[interstitials]: find next item index @672.1779999999997
[interstitials]: Start loading primary @659.2 bufferedPos: 659.2 instructToSeek: false startPosition: 655.2760000000001 loadingEnabled: false bufferingEnabled: true
[interstitials]: Start loading primary @659.2 bufferedPos: 659.2 instructToSeek: false startPosition: 656.0010000000001 loadingEnabled: false bufferingEnabled: false
startLoad(659.2)
... preroll plays out, live edge advances into the midroll ...
[interstitials]: Load interstitial asset 1/0 ["midroll-1784227395200" 662.40-686.40] live-start: 659.2 start-offset: undefined (timeline-start: 662.4000000000001)
That stale 659.2 predates the break start (662.4), so startOffset computes negative and the startOffset > 0 guard drops the parameter entirely — silently.
In every failing log the live-start: value matches the argument of that earlier startLoad() call exactly; in every succeeding log no such pre-break startLoad() fired before preroll end (the pre-buffer position was already past the live edge, so loading was deferred instead) and live-start: matches the live sync position. So whether the parameter is sent appears to race with whether primary pre-buffering started (at a then-still-valid pre-break position) during the preroll — which would explain the intermittency, and why buffer-related config only changes how often it happens.
It's also notable that one log line earlier the controller has already computed the correct join position ([live join (preroll)] Advancing timeline position to 673.228, i.e. timelinePos), but the offset calculation doesn't consult it.
Console output
# ---- failing join (excerpt; full log in the gist) ----
[stream-controller]: Setting startPosition to -1 to start at live edge 640.0009999999996
[interstitials]: Load interstitial asset 1/0 ["preroll-1" <pre>0.00-0.00] live-start: 640.0029999999997 start-offset: undefined (timeline-start: 0)
[interstitials]: INTERSTITIAL_STARTED ["preroll-1": 0.00-640.00]
[interstitials]: Waiting for ASSET-LIST to complete loading ["preroll-1" <pre>0.00-0.00]
...
[interstitials]: find next item index @672.1779999999997
[interstitials]: Start loading primary @659.2 bufferedPos: 659.2 instructToSeek: false startPosition: 655.2760000000001 loadingEnabled: false bufferingEnabled: true
[interstitials]: Start loading primary @659.2 bufferedPos: 659.2 instructToSeek: false startPosition: 656.0010000000001 loadingEnabled: false bufferingEnabled: false
startLoad(659.2)
...
[interstitials]: INTERSTITIAL_ASSET_ENDED 1/1 ["preroll-1-1-2353001250" 0.00-32.00]
[interstitials]: INTERSTITIAL_ENDED ["preroll-1" <pre>0.00-32.00] ["preroll-1": 0.00-659.20]
[interstitials]: find next item index @673.2280000000001
[interstitials]: [live join (preroll)] Advancing timeline position to 673.2280000000001
[interstitials]: INTERSTITIALS_BUFFERED_TO_BOUNDARY ["midroll-1784227395200": 662.40-686.40] (-10.83 remaining)
[interstitials]: Load interstitial asset 1/0 ["midroll-1784227395200" 662.40-686.40] live-start: 659.2 start-offset: undefined (timeline-start: 662.4000000000001)
[interstitials]: INTERSTITIAL_STARTED ["midroll-1784227395200": 662.40-686.40] append in place
[interstitials]: Waiting for ASSET-LIST to complete loading ["midroll-1784227395200" 662.40-686.40]
[interstitials]: Set HlsAssetPlayer: ["midroll-1784227395200-1-1554257213" 662.40-686.40] <uuid> append-in-place startPosition: 10.827999999999975
# ---- succeeding join (same stream, same scenario, for contrast) ----
[interstitials]: INTERSTITIAL_ASSET_ENDED 2/2 ["preroll-1-2-154295737" 24.00-32.00]
[interstitials]: INTERSTITIAL_ENDED ["preroll-1" <pre>0.00-32.00] ["preroll-1": 0.00-657.60]
[interstitials]: find next item index @673.0860000000001
[interstitials]: [live join (preroll)] Advancing timeline position to 673.0860000000001
[interstitials]: INTERSTITIALS_BUFFERED_TO_BOUNDARY ["midroll-1784227059200": 662.40-686.40] (Infinity remaining)
[interstitials]: Load interstitial asset 1/0 ["midroll-1784227059200" 662.40-686.40] live-start: 673.0870000000001 start-offset: 10.687 (timeline-start: 662.4000000000001)
[interstitials]: INTERSTITIAL_STARTED ["midroll-1784227059200": 662.40-686.40] append in place
[interstitials]: Waiting for ASSET-LIST to complete loading ["midroll-1784227059200" 662.40-686.40]
[interstitials]: Set HlsAssetPlayer: ["midroll-1784227059200-1-2449755350" 662.40-686.40] <uuid> append-in-place startPosition: 10.686000000000035
Chrome media internals output
No media errors — playback continues; the issue is the missing query parameter on the asset-list request and the resulting oversized asset list.
What version of HLS.js are you using?
v1.7.0-rc.1 — also reproduced with the latest dev build (v1.7.0-rc.1.0.canary.11888 from hlsjs-dev.video-dev.org). The relevant code in
preloadAssets()is the same on currentmaster.What browser (including version) are you using?
Chromium 148.0.7778.280 (Electron 42.6.0, offscreen/headless harness used to capture the attached logs)
What OS (including version) are you using?
Linux (Amazon Linux 2023, kernel 6.12, aarch64)
Test stream
A live LL-HLS stream (CMAF, 4.8s segments, ~10 minute window) carrying HLS Interstitials dateranges:
CUE="ONCE,PRE"withX-ASSET-LIST(32s of assets), noX-RESUME-OFFSET, so the player resumes at the live edge when it endsX-ASSET-LIST, 24s duration, one every 48s,X-RESUME-OFFSET=24The endpoint is session-bound so I can't share it publicly, but full
debug:trueconsole logs of one failing and one succeeding join (identical player page and config, hostnames/session ids redacted) are here:https://gist.github.com/philhar88/caf08dec5946534c75768132153adca4
Configuration
Additional player setup steps
None. Default interstitials handling (no custom handlers, no seeks issued by the page).
Checklist
_HLS_start_offsetnot appended to asset-list fetch when preroll resume point lands inside a midroll daterange #7845, fixed by Fix Live join following Interstitial preroll #7846 — this is a different, intermittent case in the same area: an offset is computed, but from a stale position, rather than the resume point landing inside the daterange window)_HLS_start_offset, with the same log signature as belowSteps to reproduce
CUE="ONCE,PRE", noX-RESUME-OFFSET, so the player resumes at the live edge after the preroll) and recurring midrollX-ASSET-LISTdateranges.X-ASSET-LISTHTTP request.The bug is intermittent: under the same join conditions, some joins carry
_HLS_start_offsetand some don't — in repeated attempts roughly half miss it. The frequency appears to depend on buffering timing during the preroll rather than on any specific config value (see below); it reproduced across different buffer/latency tunings.Expected behaviour
Whenever the player enters an interstitial partway through its scheduled window on a live join, the asset-list request should carry
_HLS_start_offset=<seconds into the break>, so the asset-list server can respond with an asset list trimmed to the remaining break duration.Expected (and observed on the succeeding joins):
→ the request URL ends with
..._HLS_primary_id=<id>&_HLS_start_offset=10.687, and the server returns an asset list covering the remaining ~13s of the break, ending flush with the break's resume point.What actually happened?
On the failing joins, the same code path computes
start-offset: undefinedand the asset-list request goes out with no_HLS_start_offsetparameter, even though the player is joining ~10.8 seconds into the 24-second break:The server then returns the full 24s asset list instead of the remainder. The player partially compensates by skipping into the pod — the very next line shows it knows the elapsed time it failed to send:
But the asset durations no longer match the break window: assets whose playout window has already elapsed get marked started/ended almost instantly (their tracking fires for content that never renders), and after the pod the primary can resume before the break's resume point, leaving playback several seconds further behind live than on the succeeding joins.
Where the divergence appears to come from (from comparing the failing and succeeding logs):
In
preloadAssets()(src/controller/interstitials-controller.ts), when the preroll has just endedplayingItemis null, so the offset is derived fromthis.hls.startPosition:On succeeding joins,
hls.startPositionstill reports the live-edge value at this instant, e.g.live-start: 673.087→ offset10.687. ✔On failing joins,
live-startinstead equals a position from ~17s earlier: while the preroll was still playing, the controller pre-buffered primary content at the pre-break resumption point and calledstartLoad()with it:That stale
659.2predates the break start (662.4), sostartOffsetcomputes negative and thestartOffset > 0guard drops the parameter entirely — silently.In every failing log the
live-start:value matches the argument of that earlierstartLoad()call exactly; in every succeeding log no such pre-breakstartLoad()fired before preroll end (the pre-buffer position was already past the live edge, so loading was deferred instead) andlive-start:matches the live sync position. So whether the parameter is sent appears to race with whether primary pre-buffering started (at a then-still-valid pre-break position) during the preroll — which would explain the intermittency, and why buffer-related config only changes how often it happens.It's also notable that one log line earlier the controller has already computed the correct join position (
[live join (preroll)] Advancing timeline position to 673.228, i.e.timelinePos), but the offset calculation doesn't consult it.Console output
Chrome media internals output
No media errors — playback continues; the issue is the missing query parameter on the asset-list request and the resulting oversized asset list.