Skip to content

6.1.1 - Off-main demuxer reposition

Choose a tag to compare

@superuser404notfound superuser404notfound released this 30 Jul 06:12

A main-thread fix on the software playback path. Anyone shipping the software path against remote sources wants this one.

Fixed

  • A seek on the software path no longer blocks the main thread for the length of a network read. SoftwarePlaybackHost is @MainActor and ran the demuxer reposition inline, with no suspension point between the generation bump and the FFmpeg call. Demuxer.readPacket holds the demuxer's access lock across the whole of av_read_frame, so a seek issued while the demux loop sat in a slow remote read waited that read out on the main thread, up to the 20 s connection-stall timeout. The field saw two App Hangs, "Fully Blocked", 4.4 s and 5.2 s, on a WAN source over a scrub burst. Reported by rrgomes from two production crash-reporter events (#254).

    Worth stating because it inverts the obvious fix: the existing seekBounded deadline could not have prevented either hang, since it is armed after the lock, on the far side of the wait that actually blocked. Off-main is the fix; the deadline is a second, latent hazard that now also closes.

    The reposition runs on a caller-owned serial queue under an 8 s read deadline, and its supersede check is evaluated on that queue immediately before the FFmpeg call, so a burst of scrubs collapses onto its last target instead of paying one lock wait per seek. The same inline call sat in both hosts' startPosition resume, so this covers every resume into a remote source, not only scrubbing. AudioPlaybackHost carried the identical shape and is fixed with it.

    HLSVideoEngine and the #232 interlace probe share the unbounded-seek property but not the main thread (@unchecked Sendable and an already-detached task respectively) and are unchanged.

Changed

  • A software-path seek that spends its budget now reports .stalled instead of a landing. The seekEvents stream added in 6.1.0 closed these tickets as .landed unconditionally. A reposition that exhausted its read deadline left the read position undefined, and nothing on that path re-issues it, unlike the native recovery loop whose ticket deliberately stays open because AVPlayer keeps aiming at the target. So this one is terminal.

  • The software and audio hosts hold their published position across a reposition. Now that the seek suspends, the 0.25 s time tick would otherwise republish the pre-seek synchronizer anchor for the duration and then snap, trading a frozen scrubber for a drifting one.

Upgrading

.package(url: "https://github.com/superuser404notfound/AetherEngine", from: "6.1.1")

No API change and no source change for consumers. AetherEngine.seek(to:) is unchanged in signature and behaviour; the only observable difference is that a software-path seek can now end its seekEvents ticket as .stalled, which a consumer already has to handle for the native path.