4.9.0 - PiP subtitles, restart timeline continuity, segment retention, subtitle pump tap
Device-verified on iPhone 17 Pro (iOS 26.5.1). All 517 tests green (228 XCTest + 289 Swift Testing), strict-concurrency clean.
Added
- Native WebVTT subtitle renditions: subtitles survive PiP, AirPlay, and external display (Sodalite#32, #55). Opt-in via
LoadOptions.prepareNativeSubtitles: every text subtitle track is served as a language-tagged HLSSUBTITLESrendition over the loopback master (windowed per-video-segment WebVTT), exposed through AVFoundation's legibleAVMediaSelectiongroup. Renditions shipDEFAULT=NO,AUTOSELECT=NOso a host overlay never double-renders; hosts select per surface viasetNativeSubtitleSelected(track:), which re-asserts automatically when AVFoundation drops a selection made during a stall recovery. Replaces the earlier mov_text/tx3g in-stream approach (in-band timed text is not HLS-conformant). - Subtitle pump tap: embedded text cues harvested from the producer's own read. The segment producer keeps text subtitle streams in its keep-set and hands their packets to a session-level decode tap (generalizing the #77 CC tap), filling the per-track cue stores at zero side-channel bandwidth with coverage equal to the produced region, across seeks and restarts. The host overlay is fed from the same stores, so enabling embedded text subtitles is instant even on remote sources; ASS markup is preserved for the styled overlay and stripped for the WebVTT renditions.
- Byte-budgeted VOD segment retention: seeks into watched content no longer restart the producer (#93 relevant, Sodalite#32).
SegmentCachekeeps already-produced segments beyond its hard sliding window resident under a byte budget (2 GiB, clamped to a quarter of the tmp volume's free capacity; farthest-from-target evicted first), so a backward seek into the retained span and the forward march after it are pure cache hits with zero producer teardowns. Live sessions keep window-only pruning.
Fixed
- A producer restart now continues the fMP4 media timeline instead of zero-basing it (Sodalite#32, #93). Every restart allocates a fresh mp4 muxer, and movenc zero-based each instance's timeline, so a restart-produced segment carried
tfdt=0while the VOD playlist placed it at its plan offset: an implicit timeline discontinuity on every seek-restart, papered over for plain playback but fatal to AVKit's legible renderer and matching the #93 playhead/loaded-range decoupling signature. The muxer now setsmovflags +frag_discontwithavoid_negative_ts=disabled, the restart audio gate inherits the session shift instead of snapping audio onto the video seam, and leading head-of-stream audio that would map below zero is dropped. A restarted segment is byte-identical to its continuously produced twin modulo themfhdsequence number, pinned by a witness test. - A teardown no longer caches a partial segment (VOD). Restart teardowns adopted the in-flight segment, caching content shorter than the playlist's EXTINF; with retention such a segment became replayable and produced ~2 s of A/V desync when seeked back into. VOD now adopts the in-flight segment only on a natural EOF; every other exit discards it and the next request re-produces it full-length.
- E-AC-3/AC-3/TrueHD no longer wedge the fragmented-mp4 muxer on an out-of-cache backward seek (#94, unchanged from the pre-release entry).
Notes
- The new witness fixtures are generated by
Scripts/fetch-fixtures.sh; the related tests skip when the clips are absent (e.g. CI). - Native subtitle renditions require the master playlist route: SDR sources on any panel, HDR/DV on HDR-ready panels. HDR-on-SDR-panel and DV Profile 5 on non-DV panels stay media-direct and keep host-overlay subtitles only.