AetherEngine 3.11.4
Patch release. One fix, completing the #50 spurious-failure work from 3.11.3.
Fixed
Spurious terminal .failed published while the AVPlayer kept playing (#50)
On engine-native loopback-HLS playback the engine could publish a terminal failure while the player was demonstrably still advancing (clock and subtitle cues moving, segments flowing, the title playing through to the end), aborting a session that had already self-healed.
AVPlayer flips item.status to .failed on transient errors it then recovers from, an in-range loopback 404 or an AVIOReader range-read reconnect ("The network connection was lost."). The .failed KVO is not synchronized with the timeControlStatus KVO, so the 3.11.3 gate that checked the instantaneous transport state at the failure instant still let a transient slip through whenever .failed fired during a brief .waitingToPlayAtSpecifiedRate blip while the clock never stopped. That surfaced as a terminal failure with no log, which read on the host side as a second, independent failure path.
The publish now discriminates on whether playback was ever established (a latch set on the first .playing transition) rather than on an instantaneous sample:
- Before playback establishes, a
.failedsurfaces promptly (a genuine startup failure has no recovery to wait for). - After it establishes, every
.failedis deferred and only surfaced if, after a short settle, the player is both stopped and has not advanced its clock. A recovered player (still.playing, or whose clock moved past the failure point) is treated as the transient it is.
Net: no transient that keeps the clock moving can publish a terminal failure anymore, regardless of what timeControlStatus reads at any single instant. Genuine startup failures still surface fast; a genuine mid-playback fatal stops the clock and surfaces on confirmation.
Thanks to @rrgomes for the on-device captures that isolated the unlogged immediate-surface branch.
Full changelog: https://github.com/superuser404notfound/AetherEngine/blob/main/CHANGELOG.md