fix(subtitle): decrypt LL-HLS VTT AES parts per-part#7881
Conversation
SubtitleStreamController only decrypts AES-encrypted VTT data at full-segment boundaries via _handleFragmentLoadComplete. When low-latency parts are in use, each part arrives through the progress callback (_handleFragmentLoadProgress), which the base class leaves as an empty no-op for subtitles. Encrypted parts therefore never fire FRAG_DECRYPTED on the part path, leaving subtitle parsing stuck and adding a segment-duration latency to encrypted VTT captions. This change overrides _handleFragmentLoadProgress in SubtitleStreamController to decrypt each encrypted VTT part as an independent AES-CBC stream using the segment-level IV, and emits FRAG_DECRYPTED with the part reference so the timeline-controller can anchor cues at part.start when appropriate. The full- segment path is reorganised to share the same decryptPayload helper. A 'part: Part | null' field is added to FragDecryptedData and to the two existing FRAG_DECRYPTED emit sites (base-stream-controller init-segment path and subtitle-stream-controller full-segment path) so consumers can distinguish part-level decryption from segment-level decryption.
54ee47e to
6679509
Compare
| // A new Decrypter is constructed per call so concurrent part decryptions do | ||
| // not race on the software-decrypter's shared remainder state. |
There was a problem hiding this comment.
How is it possible to have concurrent part decryption?
Parts are loaded serially. This has never come up as an issue for encrypted audio or video parts handled in transmuxer.push.
A new
Decrypterinstance is constructed per call to avoid racing on the software-decrypter's shared remainder state when concurrent parts decrypt.
The remainder state may be shared, but software decryption is synchronous. If there is remainder data then you have other problems - you are not using it and you are not resetting/reinitializing the decrypter like the transmuxer is in other stream controllers on continuity change. I would expect that neither is the case, since you parts would need to be encrypted whole to be delivered before the segment is complete.
There was a problem hiding this comment.
Confirmed — parts load serially (doFragPartsLoad recurses inside the prior part's .then(), and loadPart fires onProgress once per part), so there is no concurrent decryption and no shared-state race. Reverted to this.decrypter and removed the per-call instance and its rationale comment in 6a55fab.
On "encrypted whole": verified against the target stream — each part is a separate INDEPENDENT=YES resource with a static IV (no BYTERANGE), and the video renditions use byte-identical packaging and already decrypt per-part via transmuxer.push. Decrypting each part with the key's IV is correct here and mirrors the existing media-part path.
| private shouldDecrypt(frag: Fragment): boolean { | ||
| const d = frag.decryptdata; | ||
| return !!( | ||
| frag.encrypted && |
There was a problem hiding this comment.
Checking frag.encrypted is redundant.
There was a problem hiding this comment.
Agreed — decryptdata.key && decryptdata.iv && isFullSegmentEncryption(method) already implies the fragment is encrypted, so the frag.encrypted check is redundant. Removed in 6a55fab.
| } | ||
| } | ||
| }) | ||
| .finally(() => { |
There was a problem hiding this comment.
Promise.finally is not available in the minimum browser requirements.
Use this.decrypter. The base controller cleans up on destroy.
There was a problem hiding this comment.
Both addressed in 6a55fab. Promise.prototype.finally is ES2018 — below the documented minimum (Chrome 39+/Safari 9+) and the es2015 lib target — so I dropped it and reverted to this.decrypter, letting the base controller's destroy() handle teardown.
…nt encrypted check
Problem
`SubtitleStreamController` only decrypts AES-encrypted VTT data at full-segment boundaries via `_handleFragmentLoadComplete`. When low-latency parts are in use, each part arrives through the progress callback (`_handleFragmentLoadProgress`), which the base class leaves as an empty no-op for subtitles. Concretely:
Net effect: encrypted VTT captions over LL-HLS either stay stuck in `FRAG_LOADING` for the tail parts of a partially-loaded segment, or — at best — only surface after a whole segment is assembled, losing the low-latency benefit.
#7626 covered the part-loading + segment-finding unification but the encrypted-VTT case was out of scope.
Changes
Test plan