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
- 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.)
- Use with the bundled hls.js provider (defaults).
- 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.)
Environment
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
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:
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:
(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.)