Skip to content

4.11.0

Choose a tag to compare

@superuser404notfound superuser404notfound released this 03 Jul 09:08

Highlights

Post-recovery video judder fixed (#93 residual). The wedged-restart fresh reopen skipped avformat_find_stream_info, so the reopened demuxer never resolved the video stream's B-frame reorder depth and delivered matroska B-frame packets with NOPTS or presentation-ordered, non-monotonic dts. The producer's dts repair then telescoped sample durations or dropped roughly a quarter of all frames, so every region produced after a wedge recovery played with heavy sustained judder while stream-copied audio stayed clean. The reopen now keeps find_stream_info under a bounded probe budget (4 MB / 5 s), resolving the reorder depth from the first packets at a small bounded read cost. Device-verified on 4K DV MKV over WAN.

Pre-first-frame loader death now recovers (#93 startup). A CoreMedia -15628 before playback ever started never posts playbackStalled, so the dead-consumer watchdog never armed and the session sat in an endless spinner. The -15628 errorLog now surfaces as a stall signal, the backpressure-wedge re-anchor path arms its own stage-2 item-reload escalation, and the extractor yield gate gained hysteresis so a single post-load buffer spike can no longer let a multi-megabyte thumbnail pull kill the loader during the critical first-segment window.

PiP survives the stage-2 recovery reload (#93 residual). The reload's default teardown paused the player and dropped the item to nil before the fresh one existed; during Picture in Picture that nil-item gap invalidated AVKit's content source and dismissed the PiP window. The recovery reload now swaps items atomically: transport intent, clocks, and the old item stay alive until replaceCurrentItem hands AVPlayer the fresh one.

Active subtitles survive the recovery reload (#93 residual). Legible selection is per-item, so the reload silently dropped an active native subtitle rendition (worst in PiP, where the rendition is the only subtitle path). The engine now remembers the last requested rendition and replays it onto the fresh item; a deselect clears the memory so a reload never resurrects subtitles the user turned off.

Subtitle readers follow AVKit-side seeks. PiP's skip buttons seek the AVPlayer directly and never pass through the engine's seek API, so after a far PiP back-skip the native subtitle readers kept reading forward from the old region and AVKit permanently cached empty subtitle windows for the new one. A far rendered-time jump now schedules a debounced re-anchor: readers outside the playhead's coverage restart at the new position and the remembered selection replays, busting the cached empty windows.

Still extraction yields to a starved pipeline (#93 startup). Session-coupled scrub-preview and chapter-thumbnail extraction pulls megabytes over the same link the segment producer needs; at playback start on a marginal link that contention could tip the first segment past CoreMedia's media timeout. Extractors vended by makeFrameExtractor() now yield elective thumbnail decodes while a producer restart is in flight or the forward buffer is under 3 s. Snapshots and cache hits are never gated; standalone FrameExtractor(url:) instances are unaffected.

Added

aetherctl pktdump. Raw video packet timing (dts / pts / duration, NOPTS and monotonicity stats, delta histograms) as delivered by the demuxer under a selectable open profile (--profile playback|restartReopen|stillExtraction). The differential between profiles is what isolated the judder root cause; backed by the public PacketTimingProbe.

Compatibility

No breaking API changes. New API: makeFrameExtractor(url:httpHeaders:) (session-coupled extractor for a host-chosen still URL, e.g. playing originals during a transcode), FrameExtractor init yieldWhile: parameter, PacketTimingProbe.

Thanks to @rrgomes for the detailed #93 retests and traces that drove this cluster.