Skip to content

Fix permanent stall loading fragment-hint parts of encrypted low-latency streams (v1.6.x) - #7976

Merged
robwalch merged 1 commit into
video-dev:patch/v1.6.xfrom
zaki699-blip:fix/v1.6.x-ll-hint-part-key-context
Aug 14, 2026
Merged

Fix permanent stall loading fragment-hint parts of encrypted low-latency streams (v1.6.x)#7976
robwalch merged 1 commit into
video-dev:patch/v1.6.xfrom
zaki699-blip:fix/v1.6.x-ll-hint-part-key-context

Conversation

@zaki699-blip

@zaki699-blip zaki699-blip commented Aug 13, 2026

Copy link
Copy Markdown

This PR will...

Fix a permanent video stall on the v1.6.x line when an encrypted LL-HLS load advances onto the fragment hint while its key-loading promise is in flight. Fixes #7975 for patch/v1.6.x.

Why is this Pull Request needed?

_doFragLoad armed keyLoadingPromise with the fragment passed in, before the LL-HLS parts branch could advance the load onto the fragment hint (frag = this.fragCurrent = part.fragment). Both resolution-time context checks then compared the stale pre-swap fragment against the new fragCurrent, read the mismatch as an abort, and resolved silently — leaving the stream-controller in FRAG_LOADING with no loader in flight, no reachable fragLoadPolicy timeout, and no other escape in doTick. Video fragment loading stopped permanently while the media playlist kept polling and alt audio kept playing (full debug logs in #7975).

Per review feedback, this now follows the approach of #7874 rather than patching the context checks: key loading moves into a loadKeyFor() helper called only once fragment selection is finalized — after the part/hint selection in the parts branch, and after the loading-parts fallback in the whole-fragment path — with a fragContextChanged(frag) bail after each call site, mirroring master.

Are there any points in the code the reviewer needs to double check?

  • Validated against a live Widevine (cbcs) LL-HLS stream with a build of this branch: a resize storm (capLevelToPlayerSize flapping) that froze the unpatched 1.6.18 within 1–3 toggles now survives 20 toggles with no stall — including the KEY_LOADING-parked sibling shape, which the earlier check-only patch did not cover. Only transient non-fatal bufferStalledError + nudge during the storm.
  • The whole-fragment tail now bails (Context changed in KEY_LOADING log, as on master) when frag was hint-swapped without a part selection; the next tick reloads cleanly with loadingParts off.
  • tsc, eslint, prettier, and npm run test:unit (767 tests) pass on this branch.

Resolves issues:

#7975 (v1.6.x line)

Checklist

  • changes have been done against master branch, and PR does not conflict — targeted at patch/v1.6.x deliberately; master already has this ordering via Load key for fragment of selected LL-HLS part #7874
  • new unit / functional tests have been added (whenever applicable) — no new test; existing unit suite passes. Happy to add one if you can point at a preferred harness for the LL parts pipeline.
  • API or design changes are documented in API.md — not applicable

@robwalch robwalch left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a complete fix. Claude comments smell.

Comment on lines +918 to +920
// Same stale-reference hazard as above: `keyLoadedData.frag`
// is the pre-swap fragment when this load advanced onto the
// fragment hint; compare the current context instead (#7975).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not add multi-line comments like this.

// Same stale-reference hazard as above: `keyLoadedData.frag`
// is the pre-swap fragment when this load advanced onto the
// fragment hint; compare the current context instead (#7975).
if (!keyLoadedData || this.fragContextChanged(frag)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is OK since we only want to exit if frag != this.fragCurrent.

Comment on lines +844 to +855
keyLoadingPromise = this.keyLoader.load(frag).then((keyLoadedData) => {
if (!this.fragContextChanged(keyLoadedData.frag)) {
// Check the context against the live `frag` binding rather than
// `keyLoadedData.frag`: when the LL-HLS parts branch below advances
// this load onto the fragment hint (`frag = this.fragCurrent =
// part.fragment`), the key was requested with the pre-swap fragment,
// and comparing that stale reference reads as an abort. The load is
// still current — dropping it here parks the stream-controller in
// FRAG_LOADING with no loader in flight, so no load-policy timeout
// can ever fire and playback of encrypted low-latency streams stalls
// permanently (#7975). master is unaffected: it selects the part
// before arming the key promise.
if (!this.fragContextChanged(frag)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not correct. The problem is we're loading the key based on fragment selection that has not been finalized in the case of LL-HLS.
Changing fragContextChanged here only changes whether or not KEY_LOADED is emitted and state changed. The correct fix is to call keyLoader.load only once we have finalized fragment selection. I don't think we can, or want to, cherry-pick #7874, but the fix should involve a similar change.

Backports the ordering of video-dev#7874 to v1.6.x: extract key loading from the top
of _doFragLoad into loadKeyFor() and call it only once fragment selection
is final - after the LL-HLS part/hint swap in the parts branch, and after
the loading-parts fallback in the whole-fragment path.

Previously the key promise was armed with the fragment passed in, so when
the parts branch advanced the load onto the fragment hint, both
resolution-time context checks compared the stale pre-swap fragment against
the new fragCurrent, read the mismatch as an abort, and resolved silently -
leaving the stream-controller in FRAG_LOADING with no loader in flight and
no reachable timeout. Video fragment loading stopped permanently on
encrypted low-latency streams while the playlist kept polling.

Fixes video-dev#7975 on the v1.6.x line.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@zaki699-blip
zaki699-blip force-pushed the fix/v1.6.x-ll-hint-part-key-context branch from 37b6149 to cdd71a3 Compare August 13, 2026 21:38
@zaki699-blip

Copy link
Copy Markdown
Author

Thanks for the review @robwalch — reworked as suggested. The context-check patch is gone; key loading now moves into a loadKeyFor() helper called only after fragment selection is finalized (after the part/hint selection in the parts branch, and after the loading-parts fallback in the whole-fragment path), following the #7874 approach. The multi-line comments are removed as well.

Re-validated a build of this branch against the same live Widevine LL-HLS stream: the resize storm that froze unpatched 1.6.18 within 1–3 toggles now survives 20 toggles with no stall — and unlike the previous check-only patch, this also eliminates the KEY_LOADING-parked sibling shape mentioned in #7975. tsc / eslint / prettier / 767 unit tests pass.

@robwalch
robwalch merged commit dac96b5 into video-dev:patch/v1.6.x Aug 14, 2026
10 checks passed
@github-project-automation github-project-automation Bot moved this from Top priorities to Done in HLS.js Release Planning and Backlog Aug 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants