6.21.0 - Live HLS subtitle renditions
Additive from 6.20.2. A live channel that offers subtitles as an HLS rendition can finally show them.
The gap
The live ingest modelled a master playlist's variants, its audio group and that group's renditions, and dropped EXT-X-MEDIA:TYPE=SUBTITLES on the floor. HLSLiveIngestReader contained no occurrence of the word "subtitle". A public broadcaster whose every variant declares SUBTITLES="subs" therefore played like this:
[HLSIngest] demuxed audio rendition (group "program_audio", default=true): starting companion reader
[Demuxer] Opened: 1 streams
[Demuxer] stream[0] type=video codec=h264 1920x1080
[AetherEngine] native subtitles: prepare=false eager=false textTracks=0 bitmapOCR=0 enable=false
The audio side of exactly this shape was already handled; subtitles had no equivalent. A consequence worth stating: a host's Teletext page preference was inert on such channels, because the page only reaches a decoder that is built when a track is selected, and there was never a track to select.
What ships
The picked variant's SUBTITLES group now surfaces as TrackInfo entries under AetherEngine.liveSubtitleRenditionTrackIDBase (300_000), alongside the existing external (100_000) and remote-HLS (200_000) spaces. Selecting one starts a poll of that rendition's playlist and publishes its cues on the same overlay surface the closed-caption tap uses. Nothing is fetched before a host asks: a channel watched without subtitles pays no second HTTP loop.
Three things the real stream corrected
X-TIMESTAMP-MAP does not place these cues. The obvious mechanism, and the one this was designed around, does not carry. Measured against MDR the rendition writes one constant map, identical in every segment, whose MPEGTS value sits two hours off the video rendition's PTS; a cue placed through it landed 7185 s from the picture. What renditions of a program genuinely share is the playlist geometry, identical EXT-X-MEDIA-SEQUENCE and identical EXT-X-PROGRAM-DATE-TIME, byte for byte. A cue is placed by its segment's wall time plus its offset inside that segment, against the wall time the video ingest itself joined at. A segment with no map is refused rather than placed at face value.
An open-ended id range annexes every range added above it. RemoteHLSMediaSelection.ordinal tested id >= subtitleTrackIDBase with no upper bound, so the new renditions were routed into the AVMediaSelection path and their selection never reached its own handler. The symptom of that class is not an error but a feature that quietly does nothing. Bounded now, with a test.
A rendition playlist is not a handful of segments. The backfill assumed it approximates the DVR window. MDR publishes its entire two hour window, 3600 entries, so the loop started two hours behind the picture and everything it produced was pruned as stale on arrival. It anchors at the playhead now and marks anything older than the backfill span as seen without fetching it.
Also
aetherctl play --live-ingest loads a URL through HLSLiveIngestReader as a custom source, the shape a host uses for a live channel it ingests and re-serves itself. The live ingest had no CLI harness against a real channel at all, which is the reason the gap above went unnoticed for as long as it did. Verified with it against a live broadcast: the track surfaces, cues arrive ahead of the playhead, no line is published twice.
Media playlists now carry EXT-X-PROGRAM-DATE-TIME through the parser, including segments that inherit it from an earlier tag and a segment rebuilt to mark a discontinuity.