Repository navigation
Added
seek(to:exact:), for a seek that must land on its target (a chapter mark, the end of an intro) whereseek(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. CustomIOReadersources 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 threwheadDeadlineExceededwithout 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
declinedPersistentHLSSegmentAdmissionTests; 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 theSEEKTABLEand 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, aflac-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 withAVAssetReaderagainst
libavcodec's decode on macOS 27). Every audio-only FLAC in the rawflaccontainer 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 noaudioNowPlayingSession, 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
STREAMINFOdeclares plays at its real length
(#946). AVPlayer times every fixed-blocksize frame bySTREAMINFO'smax_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 withflac-encoded 96 kHz / 24-bit files whoseSTREAMINFOwas 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 = .softwarenow 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 toAudioAVPlayerHost. A
music host that had seen AVPlayer mistime a stream had no way ontoAudioPlaybackHostexcept
wrapping the URL in a customIOReader. 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
ownAudioAVPlayerHostas well as on a bareAVQueuePlayer. The same files on the FFmpeg host
end at their length..softwareon anaudioOnlyload now routes there, and the audio dispatch
log line says when the host's preference decided it. Opt-in:preferredDecodePathdefaults 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
passedoriginIsSerial: 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
avcCwith zero SPS or zero
PPS (mkvmerge writes the 7-byte01 64 00 20 ff e0 00for such a track) went intoinit.mp4as 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 playnow 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.landedreports 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 lefteof_reachedanderrorset 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 --realtimeon 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).