6.4.6 - Live backpressure/blocking-reload deadlock
Live sessions no longer freeze for 6-8 s at a time on a repeating cadence. The producer's backpressure park and the LL-HLS blocking reload were each waiting on the other.
Fixed
-
The live advance park no longer deadlocks against a held blocking reload.
HLSSegmentProducer.advanceMuxerparked once the head ran past the consumer's fetch high-water, and that park released only on a client segment GET. Meanwhile AVPlayer's blocking reload asked for_HLS_msn = head, a segment only a producer cut can satisfy, and the held reload occupies the serialized HTTP/1.1 keep-alive connection, so the segment GET that would have released the park queued behind it. The 18 s hold expired into503 blocking reload msn=N unsatisfiablelong after AVPlayer's ~4 s forward buffer had drained intoplaybackStalled, leaving the generic stall watchdog (nudge at +6 s, item reload at +12 s) as the only recovery. Nothing on the producer side ever said so: every breaker on that park is VOD-gated, and the live park logged nothing below 12 s.Live production is source-paced, so the advance and versioned-init parks are now VOD-only. Overproduction on live is bounded by the origin's real-time delivery; the unbounded case is a consumer that stopped polling entirely, which a new resident-segment runaway guard catches. The guard is deliberately set far above the steady-state window, so reaching it means the consumer is already dead and the existing 12 s watchdogs are the real recovery. It logs from the first cycle.
-
The sliding live window is sized by the observed segment cadence, not the cut target.
targetSegmentDurationSecondsis a lower bound on the real GOP-quantized duration, so under.fastZapa 0.5 s target against ~2 s GOPs inflated a "60 s" window to 120 segments (~240 s).EXT-X-MEDIA-SEQUENCEstayed pinned at 0 andevictBelownever fired in short sessions. The divisor is nowmax(cutTarget, observed mean EXTINF), so a burst of sub-target keyframes cannot widen the window andminSafeSegmentsstill floors it. -
The blocking-reload hold is bounded by
3 xthe sealed TARGETDURATION, the depth the playlist already advertises as HOLD-BACK, instead of a hardcoded 18 s..standardstays at exactly 18 s; a.fastZapsession (TD=2) drops to 6 s. A hold that outlives the consumer's forward buffer guarantees the stall it exists to prevent. -
reloadStalledConsumerItemhonorsLiveReloadPolicy. A live stall recovery is a live rejoin: no stale-clock resume, no zero-tolerance initial seek into a window that may have slid underneath it. Same contract as the #98 media fallback and the reload paths inAetherEngine+Loading.
Reported, diagnosed and fixed by @tschuegy in #280, with a device repro on Apple TV 4K (tvOS 26, Xtream MPEG-TS 1080i/p25 H.264).
Changed
- A live playlist whose sliding window overtakes the consumer's fetch point now says so. The removed advance park capped the producer 10 segments ahead of that point, so the window could never pass it. A source-paced origin cannot get there, but one that hands over more than a full window of backlog faster than the consumer drains it can, and the consumer then asks for a segment
evictBelowhas already deleted. Downstream that reads as a cache miss or a live-edge jump with nothing naming the cause, so the playlist builder now logslive window slid past the consumeronce per excursion.
Compatibility
No public API change. VOD paths are byte-identical: both parks, the wedge breaker and the VOD window math are untouched. HLSSegmentProvider is internal and its new liveBlockingReloadHoldSeconds member has a default implementation that keeps the previous 18 s.
Hosts that were forcing LoadOptions.liveBlockingReload = false to work around the freezes can drop that override: the blocking-reload contract is satisfiable again.
Upgrading
.package(url: "https://github.com/superuser404notfound/AetherEngine", from: "6.4.6")