Skip to content

fix: NativeHLSTextTracks adopts dash.js text tracks a second time (stack overflow / hang with DASH subtitles) - #1856

Open
digwa-ing wants to merge 1 commit into
vidstack:mainfrom
digwa-ing:fix/native-hls-text-tracks-dash-duplicate
Open

fix: NativeHLSTextTracks adopts dash.js text tracks a second time (stack overflow / hang with DASH subtitles)#1856
digwa-ing wants to merge 1 commit into
vidstack:mainfrom
digwa-ing:fix/native-hls-text-tracks-dash-duplicate

Conversation

@digwa-ing

@digwa-ing digwa-ing commented Aug 26, 2026

Copy link
Copy Markdown

Related:

Fixes #1604 (likely also the unreproduced #1231).

Description:

NativeHLSTextTracks exists to discover text tracks that the native playback engine created (Safari + embedded HLS). It is constructed whenever canPlayType('application/vnd.apple.mpegurl') is non-empty — which is "maybe" on Chromium/Chrome and Safari (Firefox answers "") — and it adopts every native track that has no <track> element.

dash.js creates its text tracks the same way (addTextTrack(), marked with manualMode), and the DASH provider already registers those from TEXT_TRACKS_ADDED. So on Chromium/Safari every DASH subtitle track ends up as two TextTracks in the model (dash-captions-0 and one with id "") bound to the same native track. TextTrackList's single-showing rule then disables one while NativeTextRenderer#onChange re-derives "showing" from the shared native track — a synchronous flip-flop:

#onTrackModeChange → TextTrack.setMode → #onChange (NativeTextRenderer)
  → setDisplay → #update → #onTrackModeChange → … RangeError: Maximum call stack size exceeded

With native controls this surfaces as a burst of thousands of Maximum call stack size exceeded errors (playback recovers); with the default custom layout (<Captions />) the page hangs instead.

This PR makes NativeHLSTextTracks skip native tracks that a JS engine owns (manualMode marker) or that are already referenced by a registered TextTrack.

Ready?

Yes.

Anything Else?

Minimal reproduction, no application code — a static page using the CDN build and a public reference stream:

<script src="https://cdn.vidstack.io/player" type="module"></script>
<media-player src="https://dash.akamaized.net/akamai/test/caption_test/ElephantsDream/elephants_dream_480p_heaac5_1.mpd" controls muted autoplay playsinline>
  <media-provider></media-provider>
</media-player>
<script>
  customElements.whenDefined('media-player').then(() => {
    const p = document.querySelector('media-player');
    p.addEventListener('can-play', () => { p.textTracks[0].mode = 'showing'; });
  });
</script>

Open in Chrome → RangeError: Maximum call stack size exceeded in the console; p.textTracks lists the single English subtitle twice. Replace controls with <media-video-layout> → the page freezes. The same page with an HLS source (one TextTrack per subtitle) is clean, and so is Firefox (no NativeHLSTextTracks there because canPlayType returns "").

Verified the fix against the built package (1.15.6 with this change applied to the dist chunks) on a real DASH stream with 12 subtitle languages: Chromium/Chrome go from ~6000 errors to 0 with correct track selection, Firefox unchanged, and the <Captions /> layout no longer hangs and renders cues.

Review Process:

  • Confirm manualMode is the intended marker for dash.js-created tracks (it is the same check the DASH provider uses in #onTextTracksAdded).
  • Load the repro above with Chrome DevTools open before / after the change.
  • Sanity-check Safari native HLS still adopts embedded tracks (they carry no manualMode and are not pre-registered, so they still pass).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Maximum call stack size exceeded

1 participant