Skip to content

7.54.0 - seek(to:exact:), FLAC seeks, rate-switch relock, subtitle and live fixes

Latest

Choose a tag to compare

@superuser404notfound superuser404notfound released this 01 Nov 16:24
· 24 commits to main since this release

Added

  • seek(to:exact:), for a seek that must land on its target (a chapter mark, the end of an intro) where seek(to:)
    may now snap AV1 on the software host to a keyframe (PR #943, details under Fixed).

Fixed

  • The low-resolution upscale route stays off on any AirPlay route, and a stage that switched itself
    off hands the session back to the native path
    (#846). The route refused only an AirPlay receiver that
    shows the video, so with AirPlay speakers holding the sound and the picture on HDMI it moved a natively
    playable SD episode onto the software path, where a seek left the speakers silent (7.53.1, tvOS 27.2).
    Any wireless AirPlay route now keeps the usual route. A stage that disabled itself (there: the
    VideoToolbox super-resolution pass failing three frames in a row) left the session on software with no
    upscaling; its plan is now remembered for the process at that frame rate or faster, and a URL VOD session
    that only the stage moved to software reloads at the current position onto the native path once it is
    playing or paused. Custom IOReader sources and live sessions keep the software path.

  • An SDR start that changes the panel's refresh rate waits for the switch to end before playing
    (#950). The #449 exit released the play gate as soon as the display link read the requested rate, and a
    display link reads the new cadence once the compositor runs at it, long before the HDMI sink has
    re-locked: on an LG C2, a 23.976 start from the 50 Hz home screen released 50 to 135 ms after the switch
    started, tvOS reported such switches ending after ~2.5 s, and three of six starts flashed pink noise over
    the black. The engine now reads the panel's cadence across the probe, before it writes the criteria.
    When the panel was already at the rate (the #449 case, a start that never sees an end) the exit stays;
    when it had to switch, the gate waits for the end notification, the in-progress flag or the cap (6 s
    for VOD; live keeps its 2 s cap, so a live zap still releases before a ~2.5 s re-lock). SET: names
    the pre-write reading, and a gate that declines the reading says so.

  • A persistent HTTP/1.1 request no longer fails its response-head deadline on the client's own
    scheduling delay
    (#732 follow-up). The 45 s head deadline was checked before each read, so a task
    that got its thread back only after the deadline threw headDeadlineExceeded without looking at the
    socket, and an HLS VOD ingest whose origin had answered in milliseconds was declined. When the
    deadline has run out by the time the task resumes, one 0.5 s read now takes what has already arrived
    before the deadline is called. Found on CI, where a 70 s stall of the one-thread cooperative pool
    declined PersistentHLSSegmentAdmissionTests; reproduced by blocking that thread for 50 s.

  • An audio-only FLAC file seeks to the position it reports (#946). CoreMedia's parser for the raw
    FLAC container ignores the SEEKTABLE and places a seek by an estimated byte rate, then stamps the
    requested time on whatever frame it reached. On a 203.5 s 96 kHz / 24-bit track with a quiet intro, a seek
    to 120 s played from 100.3 s while reporting 120 s (10 s from 2.1 s, 200 s from 186.1 s), so the clock ran
    ahead of the sound: QuickTime stopped at 3:24 with the song still playing, AVPlayer on iOS ran its clock
    to 3:56. Playback from zero is exact, a flac-encoded copy and one without a seek table land the same
    way, and the same audio as ALAC or as FLAC in MP4 lands exactly (measured with AVAssetReader against
    libavcodec's decode on macOS 27). Every audio-only FLAC in the raw flac container now goes to the FFmpeg
    host, which seeks by the frames themselves; the dispatch log line says so. Host-visible: like every
    source the FFmpeg host plays, such a session has no audioNowPlayingSession, so a host drives Now
    Playing through the shared centers for it. FLAC inside the video path's fMP4 is unaffected.

  • An audio-only FLAC whose frames are smaller than its STREAMINFO declares plays at its real length
    (#946). AVPlayer times every fixed-blocksize frame by STREAMINFO's max_blocksize, so a file declaring
    4608 over real 4096-sample frames ran its clock past the end and played on (the 3:50 of 3:23 reported in
    #939, a ratio of 4608/4096), and one declaring 4096/4608 over the same frames failed with -12842.
    Measured on macOS 26 with flac-encoded 96 kHz / 24-bit files whose STREAMINFO was patched. The
    audio-only dispatch now peeks the first audio packet (without consuming it), reads the block size from
    its frame header, and sends such a stream to the FFmpeg host, which reads the size from every frame;
    the dispatch log line names both sizes. Frames larger than the declared maximum break the
    specification and libavcodec refuses them as well, so they keep their route. Covered by
    Issue946FLACFrameBlockSizeTests.

  • preferredDecodePath = .software now reaches an audio-only session. The audio-only dispatch
    read only the codec whitelist, the custom-source flag and the FLAC block floor (AE#764), so the
    preference was silently dropped and every whitelisted codec still went to AudioAVPlayerHost. A
    music host that had seen AVPlayer mistime a stream had no way onto AudioPlaybackHost except
    wrapping the URL in a custom IOReader. Seen on an iPhone 18 Pro Max (iOS 27.0.1) with 24-bit
    FLAC streamed over HTTP from a Plex server (about 3,000 kbps; 203.5 s by its STREAMINFO and by
    Plex): through AVPlayer the reported time ran past the track's length and kept going while
    audio played on (3:50 of 3:23, even in another AVPlayer-based app on the same file), on the engine's
    own AudioAVPlayerHost as well as on a bare AVQueuePlayer. The same files on the FFmpeg host
    end at their length. .software on an audioOnly load now routes there, and the audio dispatch
    log line says when the host's preference decided it. Opt-in: preferredDecodePath defaults to
    .automatic, which routes exactly as before, so a host that does not set it sees no change.

  • A one-request origin no longer sees a second reader (#937). With maxConcurrentSourceRequests = 1 (or a
    learned limit of one), the keyframe still ladder (7.51.0) still opened its side reader, because its eligibility
    passed originIsSerial: false; a rung read overcommitted once the pump queued behind it. It now uses the same
    serial-origin check as the I-frame rendition and the compositor preview, and re-checks it before every rung, so a
    limit learned mid-session stops it too. The native subtitle readers (PiP / AirPlay with a text subtitle, eager
    readers) stay off such an origin as well; the pump tap still fills the produced region. A restart that replaces
    a demuxer wedged in a read aborts that read before the fresh open there, instead of letting the new demuxer wait
    out the held slot and overcommit. Scrub stills beyond the buffer are therefore unavailable on a one-request origin.

  • H.264 with in-band-only SPS/PPS shows a picture on the native path (#938). An avcC with zero SPS or zero
    PPS (mkvmerge writes the 7-byte 01 64 00 20 ff e0 00 for such a track) went into init.mp4 as it was, and
    AVPlayer played the audio with a running clock and a black picture, with no error for a fallback to act on. The
    HEVC in-band rebuild now has an H.264 counterpart: the head of the stream is scanned for the first SPS and PPS
    and the record is rebuilt before the init segment is written, keeping the source's NAL length size.
    aetherctl play now exits 2 when a native session with video never has a frame ready for display.

  • A seek into AV1 on the software host lands on a keyframe, so the picture comes back in well under a second instead of seconds.
    dav1d decodes from a keyframe up to a seek's target, and AV1 encodes run long GOPs (6.7 s from SvtAv1EncApp's default
    keyint at 23.976 fps). A seek the packet cache served also started on the keyframe before that one, so dav1d decoded up
    to two GOPs first: on an Apple TV 4K (A15), +30 s seeks into a 4K AV1 encode held the clock 2.5 to 4.6 s. A session that
    decodes AV1 through dav1d now lands a seek on the keyframe nearest its target, within 10 s and never on the far side of
    the playhead, and a cache hit starts on that keyframe itself, since an AV1 keyframe needs no picture before it. The same
    seeks now hold the clock 0.13 to 0.18 s from the cache and 0.5 s from a fresh reposition. SeekEvent.landed reports the
    keyframe's position, as its contract allows. The keyframes are the packet cache's retained ones (from the packets' own
    keyframe flags) and the container's index; without one at or after the target the seek stays exact. The index alone is
    not enough on Matroska, whose Cues are read only when the demuxer repositions, which a seek the cache serves never does.
    It is read off the main actor, at open and after every landing, since its lock is held across a slow remote read (#254).
    AetherEngine.snapsAV1SeeksToKeyframes (@_spi(Diagnostics)) switches both off for comparisons.
    Contributed by chrws (PR #943).

  • seek(to:exact:) lands exactly on that route too, for a chapter mark or the end of an intro, where a landing
    seconds early replays the intro's tail and one seconds late skips the episode's first line. seek(to:) keeps the
    keyframe landing. The engine now decides the landing before it notes the subtitle harvest anchor and publishes the
    clock, so both name the keyframe the pump restarts from instead of the requested position. A snap moves a seek by at
    most half its own distance, so +10 s from just before a keyframe with the next one far off no longer lands 0.1 s
    ahead, and a keyframe both the packet cache and the index list keeps the cache's value: the index converts ticks in
    another order and sat one bit lower for about one keyframe in five at a 1/24000 time base, which made the cache hit
    restore to the keyframe before it.

  • A backward seek into a stretch a forward seek skipped no longer loses its bitmap subtitles (#941).
    A forward seek past the producer's front followed by a backward seek into the skipped stretch lost every PGS set
    between the landing and the forward run's island on a link near the media bitrate. The harvest-gap hold now also
    consults the coverage ledger, so a span nobody read counts as a hole even when the harvest order ascends across it;
    the pump's reach claim is skipped while a seek is in flight, so it no longer stretches the re-anchored run across the
    skipped ground; and the hold releases when the playhead closes on the far side of the hole instead of the last packet
    before it. A 300 s fixture with a set every 3 s behind a 13.5 Mbit/s origin (play 30 to 40, seek 70, seek 40) lost
    the sets at 51 and 54 s in 3 of 3 runs; now all 18 sets in the played range appear, each 2 to 4 s ahead of its start.

  • A read error latched by a cut side read no longer poisons every later read (#944). A read the positioning
    deadline cut left eof_reached and error set on the AVIO context; avio_seek's short forward path gave up on the
    stale flag, the byte-estimate fallback reported success anyway, and every following read replayed the stored error
    ("Operation not permitted") until the forward prefetcher gave up for the session. Every byte-space reposition now
    clears both fields first, the deadline-bounded ones clear them again afterwards, and the landing probe tells a read
    failure from EOF, so such a fallback reports failed instead of applied.

  • A fastZap live start ends its grace when no further segment can finish in it (#936). On a long GOP after an
    upstream burst the bounded start waited up to 2 s for a segment that could not come and then served the same window.
    The gate now ends the grace once the open segment cannot reach the shortest segment seen so far at the measured
    delivery rate. aetherctl live --fast-zap --realtime on a 5 s GOP after a 20 s burst: serve 2.0 s down to 0.7 s,
    first picture 2.4 to 2.6 s down to 1.1 to 1.2 s, same window; 1 s GOPs unchanged.

Thanks

@chrws for the AV1 keyframe seek (#943) and @ChattyRS for the audio-only .software route (#939).