Skip to content

HLS in-band CEA‑608 closed captions never render — cues dropped in onCuesParsed #1849

Description

@maddox

Environment

  • @vidstack/react 1.15.6
  • hls.js 1.6.16 (bundled provider, renderTextTracksNatively: false — the default)
  • Chrome/desktop, DefaultVideoLayout

Description

For an HLS stream whose captions are in-band CEA‑608 embedded in the video (i.e. there is no separate #EXT-X-MEDIA:TYPE=SUBTITLES WebVTT rendition), the captions track is created (the CC button appears in the layout and the track is listed in the Captions menu), but no caption text ever renders — the track stays empty.

This is the common case for broadcast recordings (TS from an antenna), where 608 rides in the H.264 SEI (A53) rather than as a separate subtitle rendition.

I've included the workaround that I'm using, rather than opening a PR, as I'm not that familiar with this project.

Steps to reproduce

  1. Load an HLS source that carries in-band CEA‑608 captions and has no TYPE=SUBTITLES media in the master playlist. (An MPEG‑2/ATSC recording transcoded to H.264 HLS with the A53 caption data preserved is a reliable source.)
  2. Use with the bundled hls.js provider (defaults).
  3. Play, enable captions via the CC button.

Expected

The embedded 608 caption cues render.

Actual

A caption track exists and is selectable, but no cue text is ever displayed.

Root cause

hls.js extracts the in-band 608 captions and emits them via CUES_PARSED, keyed by the CC channel:

{ type: 'captions', track: 'textTrack1', cues: [...] }

The track itself is registered from NON_NATIVE_TEXT_TRACKS_FOUND with id hls-captions-0.

But the HLS provider's CUES_PARSED handler resolves the destination track using the current subtitle-track index:

// HLSProvider #onCuesParsed (1.15.6)
const index = instance.subtitleTrack;                       // -1 when there is no WebVTT subtitle track
const track = this.#ctx.textTracks.getById(`hls-${data.type}-${index}`);  // → getById('hls-captions--1')
if (!track) return;                                          // ← bails, dropping every 608 cue

For in-band 608 with no separate subtitle rendition, instance.subtitleTrack is -1, so it looks up hls-captions--1, finds nothing, and discards all cues. data.track ('textTrack1') — the actual channel the cues belong to — is never consulted, so the cues never reach the hls-captions-0 track that was registered.

#onTracksFound (registration) and #onCuesParsed (cue delivery) therefore key tracks by different schemes: registration by the track's index within the NON_NATIVE_TEXT_TRACKS_FOUND payload, delivery by instance.subtitleTrack. They only line up when the captions happen to correspond to the selected WebVTT subtitle track; for in-band 608 they don't.

Suggested fix

Resolve the target track in onCuesParsed from data.track (the CC channel that the cues belong to) mapped to the registered hls-captions-* track, rather than from instance.subtitleTrack.

Workaround (for others hitting this)

Listen for CUES_PARSED directly and feed the caption cues into the player's captions track yourself:

hls.on(Hls.Events.CUES_PARSED, (_e, data) => {
  if (data.type !== 'captions') return;
  const track = [...player.textTracks].find(t => t.kind === 'captions' || t.kind === 'subtitles');
  for (const cue of data.cues) track?.addCue(cue);
});

(Vidstack's own handler bails on the -1 lookup, so this doesn't double-add — but it will once the bug is fixed, so gate/remove it then.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions