Skip to content

AetherEngine 5.16.2

Choose a tag to compare

@superuser404notfound superuser404notfound released this 21 Jul 06:40

Two end-of-file fixes for long loopback-HLS VOD, both reported by rrgomes on a 4K Dolby Vision Profile 8.1 title over a slow tvOS WAN link (#169).

Fixed

A video-exhausted VOD tail hung instead of finishing (-12889)

A long DV title failed to finish at end-of-media: AVPlayer parked a fraction of a second from the end in WaitingToMinimizeStalls, never fired didPlayToEndTime, and after ~43s died with CoreMediaErrorDomain -12889.

The final segment is produced and served fine (playback reaches within ~0.1s of the end), so it is not missing. The defect is a duration mismatch: the final segment's advertised #EXTINF is derived from the container duration (sourceDurationSeconds), but the muxer writes the final video sample at EOF with only its one-frame duration. When the container duration overshoots the last real video sample (the audio track running a few frames longer, or a rounded-up MKV Duration), the video track underfills the advertised segment by ~0.1s. AVPlayer's video renderer then parks waiting for frames that never existed, while loadedTimeRanges (audio plus the segment map) reports full buffering, so didPlayToEndTime never fires.

The engine now detects this tail park from its existing 1 Hz native tick and synthesizes organic end-of-media, so the session finishes cleanly (.ended -> mark-watched / autoplay-next / dismiss) after a ~3s grace instead of hanging then erroring at ~43s. The discriminator is tight: VOD only, playhead within endOfMediaEpsilonSeconds of duration, the final segment loaded to the end (raw loadedTimeRanges end, so a resume/seek into the tail whose playhead runs ahead of loaded media does not qualify), WaitingToMinimizeStalls, and a frozen playhead held for a 3-tick grace so a transient near-end buffering blip that self-recovers is never cut short.

Padding the final video sample to fill the advertised #EXTINF was considered and rejected: the EOF-flushed packet is the last in decode order, not presentation order, so extending it would corrupt a B-frame tail. Completing at the honest video-exhaustion point is robust regardless of whether the video genuinely ends short or the tail dropped frames.

Covered by NearEndOfMediaParkTests.

Resuming into the tail of a DV title dropped Dolby Vision

Resuming at the last few seconds, the #35 readiness gate reported "master never produced tracks", reloaded, timed out again, and degraded master -> reduced master -> media playlist, needlessly dropping Dolby Vision (and stalling anyway while the final segment kept producing).

A tail resume anchors the readiness-gate master on the final segment, still being produced over the slow link, so awaitStartupReadiness times out with 0 tracks and no media loaded. The gate, written for the cold DV/HDCP decode failure, misread unstarted production as a decode failure and burned the master fallback chain before it had any segment data to decode.

The gate now splits a settle-window timeout by whether any media has loaded: no media means the first segment has not been served yet, so it keeps the DV master and re-awaits, bounded by maxDataWaitRounds (~24s of patience, comfortably longer than a slow-link final-segment production); media loaded but still 0 tracks stays the cold-decode park, with the reload/fallback chain unchanged. A genuinely wedged producer still falls through once the data-wait budget is spent, so it can never hang forever.

Covered by StartupReadinessGateTests.

Notes

Both fixes are host-transparent (no API change) and awaiting reporter device retest. The reporter's host-side mitigation for Symptom 1 (treating a stall within 15s of duration as end-of-media) can be removed once this ships. Diagnostics: Symptom 1 logs [AetherEngine] #169 tail park: playhead=... duration=... loadedEnd=...; Symptom 2 logs [AetherEngine] #35/#169 readiness gate: master's first segment not served yet ... keeping the DV master and waiting.

Full test suite: 872 tests. Swift 6 language mode (complete concurrency).