Skip to content

Merge upstream AetherEngine 7.36.0 into the Silo fork - #21

Merged
Quick104 merged 166 commits into
mainfrom
sync/upstream-7.36.0
Oct 13, 2026
Merged

Quick104 merged 166 commits into
mainfrom
sync/upstream-7.36.0

Conversation

@Quick104

Copy link
Copy Markdown

Summary

Merges upstream AetherEngine main at 74c81b3c (7.29.0 to 7.36.0, plus the unreleased live-playlist renewal superuser404notfound#748) into the fork. The previous sync was #16 (7.28.3). This is a merge commit, so the PR must be merged with a merge commit, not squashed: squashing would lose upstream's history and the merge base the next sync depends on.

Fork changes upstream now covers (removed from the fork's [Unreleased] changelog):

Fork changes kept and fitted onto upstream's code:

  • Audio-switch tracking (audioSwitchInFlight), placed next to upstream's held-picture state.
  • httpRequestAuthorization stays in the reload field list.
  • stopInternal still cancels remote-HLS preparation and stops the subtitle relay.
  • The TLS live suite layout, the accurate APAC CODECS levels in cli.md, and the paused-seek sentence in api.md.

New in this merge:

  • Two fork Task.detached call sites (HTTPAuthorizationWait, the remote-HLS timestamp-anchor probe) now use BlockingWork.detached, as upstream's BlockingWorkTests lint requires.
  • The objectAudioRendering row in api.md names ObjectAudioRendering again. PublicAPIDocumentationTests requires this, and upstream main currently fails that test.

The merge commit message lists every conflict resolution.

Verification

Run on MacDev:

  • swift test (CI split): 4475 Swift Testing tests pass, plus XCTest with 0 failures. The authorization suites pass 56 of 56.
  • Strict one-thread cooperative pool: 4531 tests pass in two of three full runs. The third run failed once in "An uncooperative resolver cannot outlive its request budget" (10.5 s against 1 s). Run alone, that test passes three times with this change and three times without it, so it is load-dependent and not a regression.
  • tvOS and iOS Simulator build-for-testing both succeed, and the six packet-cache regression scripts pass.

Evidence: https://evidence.siloserver.org/r/aetherengine/upstream-7.36.0-sync/

🤖 Generated with Claude Code

yucelokan and others added 30 commits October 7, 2026 12:59
LiveTelemetry.demuxerBytesFetched read 0 for every custom-reader source,
because CustomIOReaderBridge reported a constant 0. A disc image played over
HTTP therefore showed no origin bytes and, on the software path, no
networkThroughputMbps, although HTTPDiscIOReader pulled the whole title.

HTTPDiscIOReader now counts the response bodies it receives, ConcatIOReader
passes its base reader's count through, and the bridge reports it. A host's
own IOReader is not asked and still reports 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LoadOptions.progressiveSegmentDelivery (off by default) lets AVPlayer use a
segment's first fragments instead of waiting for its cut, which on a slow
link is the time to first frame.

- The producer registers each VOD segment's staging file with the cache as
  it opens it, and the muxer flushes a fragment every 0.5 s instead of
  holding up to 8 s.
- SegmentCache.fetchSource answers a segment being written with a
  ProgressiveSegmentReader over its staging file; the file is only appended
  to and renamed as it stands on adoption, so the reader sees a growing
  prefix of the final segment.
- HLSLocalServer sends each read as a chunk and ends at the seal; an
  abandoned segment closes the connection without the final chunk so
  AVPlayer asks for it again.
- Each chunk sent counts as fetch activity for the superuser404notfound#65 wedge watchdog,
  which otherwise reads one long transfer as AVPlayer no longer fetching.
- aetherctl play --progressive-segments drives the option.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the temporary directory's volume ran out of space, the session
directory, the segments or the muxer's staging files could not be written,
every muxer revive failed the same way, and the give-up arm surfaced
vodSourceFailed ("Source audio cannot be muxed"), or
audioBridgeProducedNoOutput on a bridged track. Both send a host's fallback
ladder the wrong way for a file that plays once space is freed.

SegmentCache now latches an out-of-space write from the session directory,
a stored segment or a muxer staging file (reported through a new
MP4SegmentMuxer callback), and handleVODMuxerFailure surfaces the new
PlaybackErrorKind.storageExhausted ahead of the audio verdicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
superuser404notfound#711 keeps the outgoing item mounted while the source reopens, but
replaceCurrentItem still drops the AVPlayerLayer to black until the new
item decodes its first frame, and on device that read as a flash on every
switch. The rebuild now reads the frame on screen from the paused item
(NativeAVPlayerHost.captureDisplayedFrame, ~20 ms paused; a playing item
needs ~300 ms before a fresh output sees anything, which is why it reads
after the pause) and lays it over the hosted layer on the bound
AetherPlayerView as a one-sample AVSampleBufferDisplayLayer, so an HDR
frame goes through its colour attachments as the software path's frames
do. It comes down when the next host session reports isVideoReadyForDisplay,
on stop, load, a failed or superseded rebuild, unbind, or after 8 s.
Skipped for Dolby Vision (the output vends the base layer, which has no
displayable colour of its own for Profile 5), PiP and external playback.

Covered on a generated 4 s H.264 + AAC file: a playing and a paused item
hand over a frame, an audio rebuild holds the picture and releases it on
the next item's first frame (red with the hold disabled), stop takes it
down, and the skip rules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…endered host

The first device run of the held picture logged no `held picture` line at
all: Sodalite renders the native path through AVPlayerViewController and
binds AetherPlayerView only for the software path, so boundView was nil and
the hold returned without a trace. A host shaped like that now binds a
transparent, input-free AetherStillView (bindStillView / unbindStillView),
placed above the video and below its chrome, and sets its videoGravity to its
own player's fill. The engine holds on that view first and on the bound
AetherPlayerView otherwise. Both share one HeldStillPresenter. "No surface
bound" and a frame that cannot be wrapped are now logged skips.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…re too

Software path: an audio-switch rebuild replaces host and layer, and the old
host's stop() flushed its layer to black for the whole startup of the new one.
The renderer's picture is read back first (displayedPixelBuffer, or the newest
frame enqueued, which SampleBufferRenderer now keeps until a flush) and held
until the next host's first frame (armed where loadSoftware installs it).

Dolby Vision: profiles with a displayable base layer (8.x, 10.1+) hold it from
the native output as before, now shown as HDR. Profile 5 and AV1 Profile 10
have none; the frame is decoded from the resident segment through the scrub
stills' RPU reshaping (DolbyVisionStillConverter), frame-accurately via a new
internal FrameExtractor.snapshot(afterFirstFrameBy:) that measures from the
segment's first frame (its tfdt may be absolute or zero-based). Measured on an
M1 Pro, Profile 5 UHD: 1.53 s at 1920 wide, 0.40 s at 960, 0.11 s at 480,
plus ~0.24 s of decode per second into the segment, so the still is 960 wide
and the switch waits up to 3 s for it with the outgoing picture paused.

A guard that dropped a read frame now logs why, and the native capture allows
500 ms (paused it answers in ~20 ms; one suite run under the Profile 5 decode
load missed 250).

Tests: frame-accurate relative snapshot, software rebuild hold and release,
the reshaping rule, and, against the local Dolby fixtures (skipped without
them), Profile 5 reshaped inside the budget and Profile 8.1 from the output.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…nOS builds

CI on a385289: the macOS runner's AVPlayerItemVideoOutput vends nothing for a
paused item (a playing one it does; a Mac and an Apple TV answer paused in
~20 ms), so every native hold test failed there. After 100 ms without a frame
the paused item is asked to render its own frame again with a zero-tolerance
seek to where it stands; the item is about to be swapped out anyway.

visionOS has no isExternalPlaybackActive, and displayedPixelBuffer needs 1.1;
the tvOS + visionOS job failed on both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
… only

The zero-tolerance re-render did not change it (CI on superuser404notfound#722): the runner's
AVPlayerItemVideoOutput vends no frame for a paused item at all, while a Mac
and an Apple TV answer in ~20 ms. The seek is removed again, since it fixed
nothing measured. The five tests that need a paused read are disabled under
GITHUB_ACTIONS with that finding as the reason; the playing read, the software
path, the frame-accurate snapshot and the rules still run there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…/fix/held-still-ci

fix(held-picture): re-render a paused item that vends no frame; visionOS builds
…ded-source-snapshots

# Conflicts:
#	CHANGELOG.md
#	Sources/AetherEngine/FrameExtractor/FrameDecodeContext.swift
#	Sources/AetherEngine/FrameExtractor/FrameExtractor.swift
#	docs/api.md
…t-live-replay-guard

# Conflicts:
#	CHANGELOG.md
Every live video and audio packet on the stream-copy path is signed, so the
Data copy per packet and the [UInt8] digest box were an allocation pair per
packet for nothing. The digest is now SHA256.Digest over the packet's own
buffer. The side-audio branch in liveReplaySample was unreachable behind
its own sideAudioDemuxer == nil guard and is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…snapshots, exact live replay guard (yucelokan's superuser404notfound#715-superuser404notfound#717)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…and LoadOptions tables

seek(to:) names the forward-only rejection and the 0.75 s landing rule,
canSeek and isSourceSeekable sit beside it, and sourceOpenPolicy is a
LoadOptions row with its defaults, instead of two prose sections under
"Public but not host API".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…a thread, not a starved async poll

CI run 37722262358 failed three SourceOpenRecoveryTests while every other
suite on the runner ran 50 to 80 times slower than locally. Each test drove
the reader into a short window (a slot wait of at most 4 s, a body stall,
the alternate data request) and then acted on it from an async test task
polling every 20 ms. On a starved cooperative pool that poll came back too
late: the teardown test's slot wait had already given up, so its
waiting == 1 never recurred and it hung into the 2-minute limit; the data
retry and the held connection hit an engine timeout of their own and failed
on an error they were not about (noResponse, a restart from byte 0).

ProbeTestTrigger runs the step on its own thread, which is where
ProbeTestJob already runs the engine half, and a missed window now fails at
once with a message instead of hanging.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…surface lives in the Live and LoadOptions tables

seekToLiveEdge(offsetSeconds:), currentItemLiveEdgeTime, liveTargetDurationSeconds
and the two DVR lease calls are rows of the Live and DVR table, the timestamp-reset
contract sits beside them, and the two startup options plus softwareDVRRetention
are LoadOptions rows, instead of three prose sections under "Public but not host API".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
Vincent Herbst and others added 19 commits October 13, 2026 09:54
Review follow-ups on superuser404notfound#755:

- On a resume the verdict peeked the file head, and step 8's seek then
  dropped those packets: one wasted range read per start onto any 48 kHz
  DTS-HD MA track. The demuxer is now put at the first producer's start
  before the peek (`seekToInitialProducerStart`, step 8's seek moved into
  a helper), and step 8 skips its seek when that already happened, so
  the peeked packets are the producer's first ones. LibDTSX decides
  mid-stream as well (aetherctl serve --start-position 40: verdict 1 ms,
  audio gate gapMs=0.0; plain DTS-HD MA refused at 8 s, channel bridge
  opens on target).
- The peek holds every stream's packets until the producer reads them,
  so it is capped at 24 MiB as well as 512 packets.
- A stream that decodes at a rate other than 48 kHz is refused on its
  first block instead of failing every block in the bridge.
- TFC sits on the front-top row (0.5, 0.25, 1) as in the TrueHD map
  since superuser404notfound#742; LSD/RSD map to the side speakers like TrueHD's.
- Docs: the angular figures were measured before superuser404notfound#742, the 21 ms after
  a start or seek are skipped rather than filled with silence, the
  1200-packet silence report is 12.8 s of DTS, and the OS < 26 pick
  change for TrueHD Atmos gets a Changed line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…y a budget a starved run spends

The 5 s pin still failed once on main (cee2ccc, run 38362554779): the
budget counts from the prefetch's issue, and that run was starved for
about 25 s (every test in the superuser404notfound#281 suite took 24 to 29 s, including the
one with no origin delay). The budget ran out before the read arrived
while the body that had already landed was still queued for its
delegate, so the reader reconnected at the read position.

The test's subject is whether the reader waits, not for how long (the
calibration has its own test), so the budget is now 90 s and the suite's
two-minute limit is the bound. The failure message carries the reader's
own log lines, so a future red run names the prefetch outcome instead
of only the ranges.

Checked both ways: with the budget forced to 0 it fails with exactly
the CI range list; with the fix the full suite passes (4345 tests).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
… to twelve rounds

Failed once in the one-thread cooperative pool job (run 38364557181):
bounded 1.11 s vs plain 0.074 s. Measured locally in the strict-pool
suite, a loaded run stalls EITHER fetch at random for up to 2.7 s
against 16 ms unloaded, so three samples can all land in a stall; the
delegate path is not slower than the plain one.

A stall only ever adds time, so the best sample converges on the real
cost: the loop now stops as soon as the bound holds and takes up to 12
rounds before failing. The per-byte accumulation the test guards is
slower in every sample and still fails (checked by swapping it back in:
0.84 s vs 16 ms after all 12 rounds). Both full suites pass, default
and LIBDISPATCH_COOPERATIVE_POOL_STRICT=1 (4345 tests each).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…ang from a starved run

Failed once in the one-thread cooperative pool job (run 38373967029):
the capture returned nil after its 2 s timeout while the item was still
playing, in a run that stretched this test to 22.7 s (about 1 s alone).
A playing item vends a frame continuously, so the timeout only has to
catch an output that never does; 15 s is inside the suite's one-minute
limit. The failure message now carries the engine state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…ard-only open (AE#733)

A playlist endpoint answering 200 with no Content-Length (chunked, typical
for a dynamic /api/playlist?id= handler) never reached the AE#268 ingest.
Audit DMX-101 hangs up the ranged open at the head and the open goes
forward-only on one unranged GET, but the AE#140/superuser404notfound#154 #EXTM3U check only
read the persistent window, which that path leaves empty. FFmpeg was then
handed the m3u8 and the load ended in "Invalid data found when processing
input", the exact error the reporter saw on device.

The same classification now runs on the forward-only stream's first bytes.
Measured with aetherctl against a loopback origin serving the long-GOP
HEVC fixture through /api/ts?url=...: chunked and length-less playlists
failed before and now route through AE#268 and seek to segment 57; the
Content-Length variant is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…found#752, superuser404notfound#755)

The repository moved from kristofferR to superuser404notfound. 1.0.1
exports rust_eh_personality weak, so a host with Objective-C and C++
links next to LibDovi and LibTrueHD again (superuser404notfound#752), and stops repeated
damaged sections from pushing the input timeline ahead of the source,
which delayed the audio after a second gap until a reset (found by
@andreencar). Package URL, docs and changelog follow the move.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…uilds

Starlight parses ":X" as a remark text directive, so its link validator
slugs the heading as "dts-with-heights" and rejected every link to
#dtsx-with-heights. The escape renders the same heading on GitHub and on
the site.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…-source-probe-error-cause

fix(diagnostics): preserve the custom-source probe failure cause
…under Unreleased

The PR branched before 7.35.0 was cut, so the merge put its line under
that release's Fixed section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…ck-origin-callback-priority

fix(demux): align origin-slot completion with playback priority
…uperuser404notfound#746)

superuser404notfound#746 moved the 429 accounting into syncRequest's completion callback so
the slot is not handed on before the refusal is on the books, but the
detour still charged the same answer again afterwards. Each charge halves
the origin's concurrency and doubles its quiet period, so one 429 on a
detour block quartered the budget. The detour now only reads the
Retry-After it hands its caller.

The new callbacks also passed an absent Retry-After as 0, which the budget
reads as a zero quiet period instead of the exponential ladder; the old
detour path mapped it to nil. One helper charges the response for all
three finite request paths and keeps that mapping.

The CHANGELOG entry moves under Unreleased: the branch predated the
7.35.0 cut.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…SourceReset (superuser404notfound#747)

The verified-replay exhaustion from superuser404notfound#747 published only the new
liveSourceReplayExhausted subject. A host that never subscribed to it (every
existing integrator, Sodalite on a Jellyfin transcode respawn among them) held
the last frame for good, where it used to retune on liveSourceReset. The new
subject now fires only under LoadOptions.hostRetriesExhaustedLiveReplay; by
default the session reports the exhaustion through onLiveSourceReset.

The layer-unready presentation repair was wired for every native session,
VOD included, and an in-place swap that joins audio-first while video waits
for its IDR could earn it a nudge and an item replacement. It now runs only
under nativeVideoFrameRecovery, like the decoded-output half.

CHANGELOG: the PR's entries collapsed into one under Unreleased; three of them
had landed under 7.35.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…e source plays once, video returns after a screen lock

Also files the missing CHANGELOG entries for superuser404notfound#739 and superuser404notfound#733.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…': renew the live playlist when a GOP outgrows the seal

Conflicts with superuser404notfound#747 resolved by keeping both: the reopen/exhaustion handlers and the renewal handler
are registered side by side, and reloadStalledConsumerItem takes both preserveLivePosition (superuser404notfound#747
presentation repair) and replacementURL (renewal); preserveLivePosition still wins the rejoin.
The CHANGELOG line moved from 7.35.0 to Unreleased.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
Takes upstream main through 74c81b3: 7.29.0 to 7.36.0 plus the
unreleased live-playlist renewal (superuser404notfound#748).

Retired fork changes, now covered upstream:
- TrueHD Atmos to APAC (d903760, 455bcd6, fa70ab5): upstream superuser404notfound#731
  adopted the fork's bridge, encoder, sample entry and renderer, with
  priming kept, and builds on it (encode pipeline, metadata hold, peak
  limiter, Atmos output activator, DTS:X). LibTrueHD moves from 2.0.0
  to upstream's 2.3.0 pin; LibDTSX 1.0.1 is new.
- TrueHD Atmos diagnostics (fork #17: 8ff23f8, 59d8406, 703f790,
  18729d1, d6cb090): BedLevelMeter, the route description and the
  1200-packet AE#641 report shipped in upstream 7.34.0 unchanged.
- Sidecar decode off the cooperative pool (241dff5): now upstream's
  BlockingWork.detached, still calling the fork's authorized
  decodeBlocking.
- The fork's CHANGELOG entries for the above.

Kept, and fitted onto upstream's code:
- Audio-switch tracking (5cda1c1, ba895a5, d6cb090):
  audioSwitchInFlight beside upstream's held-picture state, and
  pendingAudioSelection stays internal for BackgroundTeardownSelection.
- httpRequestAuthorization stays a known LoadOptions field beside
  upstream's three new ones.
- stopInternal cancels the remote-HLS preparation and stops the
  subtitle relay, and resets the scrub-preview generation.
- The superuser404notfound#694 park suite stays serialized and also runs off the
  cooperative pool.
- The TLS live suites keep the fork's structure; the accepted-read loop
  drops its wall-clock deadline as upstream's does.
- DocumentedConstants keeps the 8- and 10-channel APAC CODECS checks in
  upstream's combined test, and cli.md keeps the accurate apac.31.02 /
  31.03 / 31.04 levels.
- docs/api.md keeps the paused software-video seek sentence, and the
  objectAudioRendering row names `ObjectAudioRendering` again, which
  PublicAPIDocumentationTests requires (upstream main fails it).

New:
- HTTPAuthorizationWait and the remote-HLS timestamp-anchor probe start
  their tasks with BlockingWork.detached, which upstream's lint
  (BlockingWorkTests) now requires.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-13T23:59:02.943592Z 4ab3e3c PR opened
🔒 Security Review ✅ Completed 2026-10-14T00:01:41.814887Z 4ab3e3c PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Oct 13, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 268 files, which is 168 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 69ef6dc6-fd1e-40eb-afb1-c87a7a56aa7d
📥 Commits

Reviewing files that changed from the base of the PR and between 4c57d95 and 4ab3e3c.

⛔ Files ignored due to path filters: 2
  • Package.resolved is excluded by !**/Package.resolved
  • Sources/AetherEngine/Resources/silent-atmos.mp4 is excluded by !**/*.mp4
📒 Files selected for processing: 268
  • .github/workflows/ci.yml
  • CHANGELOG.md
  • Examples/README.md
  • Package.swift
  • README.md
  • Scripts/make-silent-atmos.swift
  • Scripts/test-long-gop-startup.sh
  • Scripts/test-vod-open-seek.sh
  • Sources/AetherEngine/AetherEngine+AudibleReadback.swift
  • Sources/AetherEngine/AetherEngine+BackgroundAudioTestHooks.swift
  • Sources/AetherEngine/AetherEngine+HeldPicture.swift
  • Sources/AetherEngine/AetherEngine+Live.swift
  • Sources/AetherEngine/AetherEngine+LiveSubtitleRenditions.swift
  • Sources/AetherEngine/AetherEngine+Loading.swift
  • Sources/AetherEngine/AetherEngine+Probe.swift
  • Sources/AetherEngine/AetherEngine+Recording.swift
  • Sources/AetherEngine/AetherEngine+ReloadWithOptions.swift
  • Sources/AetherEngine/AetherEngine+ScrubThumbnail.swift
  • Sources/AetherEngine/AetherEngine+SubtitleOCR.swift
  • Sources/AetherEngine/AetherEngine+Subtitles.swift
  • Sources/AetherEngine/AetherEngine+Version.swift
  • Sources/AetherEngine/AetherEngine.swift
  • Sources/AetherEngine/Audio/AtmosConfirmationWorker.swift
  • Sources/AetherEngine/Audio/AudioOutput.swift
  • Sources/AetherEngine/Audio/AudioTranscodingBridge.swift
  • Sources/AetherEngine/Audio/RendererTimeline.swift
  • Sources/AetherEngine/Audio/Spatial/APACEncodePipeline.swift
  • Sources/AetherEngine/Audio/Spatial/APACEncoder.swift
  • Sources/AetherEngine/Audio/Spatial/APACSampleEntry.swift
  • Sources/AetherEngine/Audio/Spatial/AtmosOutputActivator.swift
  • Sources/AetherEngine/Audio/Spatial/DTSXObjectAudioDecoder.swift
  • Sources/AetherEngine/Audio/Spatial/ObjectAudioDecoding.swift
  • Sources/AetherEngine/Audio/Spatial/ObjectAudioRenderer.swift
  • Sources/AetherEngine/Audio/Spatial/ObjectAudioRendering.swift
  • Sources/AetherEngine/Audio/Spatial/ObjectMetadataHold.swift
  • Sources/AetherEngine/Audio/Spatial/PeakLimiter.swift
  • Sources/AetherEngine/Audio/Spatial/SpatialAudioBridge.swift
  • Sources/AetherEngine/Audio/Spatial/SpatialSpeakerLayout.swift
  • Sources/AetherEngine/Audio/Spatial/TrueHDObjectAudioDecoder.swift
  • Sources/AetherEngine/Audio/Tap/AudioTapHLSReader.swift
  • Sources/AetherEngine/BlockingWork.swift
  • Sources/AetherEngine/Decoder/DeinterlaceHardwareWarmup.swift
  • Sources/AetherEngine/Decoder/HardwareVideoDecoder.swift
  • Sources/AetherEngine/Decoder/SubtitleDecoder.swift
  • Sources/AetherEngine/Demuxer/AVIOReader.swift
  • Sources/AetherEngine/Demuxer/CustomIOReaderBridge.swift
  • Sources/AetherEngine/Demuxer/Demuxer.swift
  • Sources/AetherEngine/Demuxer/HeldSourceConnection.swift
  • Sources/AetherEngine/Demuxer/OriginRequestBudget.swift
  • Sources/AetherEngine/Demuxer/SourcePrewarmFetcher.swift
  • Sources/AetherEngine/Demuxer/SourceSizeProbeScope.swift
  • Sources/AetherEngine/Diagnostics/LiveTelemetrySampler.swift
  • Sources/AetherEngine/Diagnostics/VideoFrameProgress.swift
  • Sources/AetherEngine/Disc/BDTitleSelector.swift
  • Sources/AetherEngine/Disc/ConcatIOReader.swift
  • Sources/AetherEngine/Disc/DVDIFOParser.swift
  • Sources/AetherEngine/Disc/DiscDirectory.swift
  • Sources/AetherEngine/Disc/DiscInspector.swift
  • Sources/AetherEngine/Disc/DiscMetadata.swift
  • Sources/AetherEngine/Disc/DiscReader.swift
  • Sources/AetherEngine/Disc/DiscRecognitionCache.swift
  • Sources/AetherEngine/Disc/DiscSeekTable.swift
  • Sources/AetherEngine/FFmpegErrorConstants.swift
  • Sources/AetherEngine/FrameExtractor/FrameDecodeContext.swift
  • Sources/AetherEngine/FrameExtractor/FrameExtractor.swift
  • Sources/AetherEngine/FrameExtractor/FrameTypes.swift
  • Sources/AetherEngine/IO/HLSIngest/HLSLiveIngestReader.swift
  • Sources/AetherEngine/IO/HLSIngest/HLSVODIngestReader.swift
  • Sources/AetherEngine/IO/HLSIngest/PersistentHTTP1Transport.swift
  • Sources/AetherEngine/IO/HTTPDiscIOReader.swift
  • Sources/AetherEngine/IO/IOReader.swift
  • Sources/AetherEngine/IO/ProbeIOReader.swift
  • Sources/AetherEngine/Native/AudibleSelectionReadback.swift
  • Sources/AetherEngine/Native/MasterFallbackDecision.swift
  • Sources/AetherEngine/Native/NativeAVPlayerHost.swift
  • Sources/AetherEngine/Native/RemoteHLSSubtitleProvider.swift
  • Sources/AetherEngine/Native/SWClockAnchorPolicy.swift
  • Sources/AetherEngine/Native/SoftwarePacketReadAhead.swift
  • Sources/AetherEngine/Native/SoftwarePlaybackHost.swift
  • Sources/AetherEngine/Native/SoftwareStillExtractor.swift
  • Sources/AetherEngine/Network/HLSLocalServer.swift
  • Sources/AetherEngine/Network/HTTPRequestAuthorization.swift
  • Sources/AetherEngine/PlaybackClock.swift
  • Sources/AetherEngine/PlaybackErrorInfo.swift
  • Sources/AetherEngine/PlayerState.swift
  • Sources/AetherEngine/Renderer/SampleBufferRenderer.swift
  • Sources/AetherEngine/SeekEvent.swift
  • Sources/AetherEngine/SharedOutputCoordinator.swift
  • Sources/AetherEngine/SourceOpenPolicy.swift
  • Sources/AetherEngine/Video/CodecRoutePolicy.swift
  • Sources/AetherEngine/Video/HLSSegmentProducer.swift
  • Sources/AetherEngine/Video/HLSVideoEngine+AudioRoute.swift
  • Sources/AetherEngine/Video/HLSVideoEngine+LiveReopen.swift
  • Sources/AetherEngine/Video/HLSVideoEngine+SpatialAudio.swift
  • Sources/AetherEngine/Video/HLSVideoEngine.swift
  • Sources/AetherEngine/Video/LiveDVRLimits.swift
  • Sources/AetherEngine/Video/LivePacketReplayGuard.swift
  • Sources/AetherEngine/Video/LiveReplayPacketReader.swift
  • Sources/AetherEngine/Video/LiveWindow.swift
  • Sources/AetherEngine/Video/MP4SegmentMuxer.swift
  • Sources/AetherEngine/Video/NoCutStallWatchdog.swift
  • Sources/AetherEngine/Video/PacketRingBuffer.swift
  • Sources/AetherEngine/Video/SegmentCache.swift
  • Sources/AetherEngine/Video/SoftwareDVRRetentionOptions.swift
  • Sources/AetherEngine/Video/VODSegmentCutter.swift
  • Sources/AetherEngine/Video/VideoSegmentProvider.swift
  • Sources/AetherEngine/View/AetherPlayerView.swift
  • Sources/AetherEngine/View/AetherStillView.swift
  • Sources/AetherEngine/View/HeldStill.swift
  • Sources/aetherctl/BackgroundAudioCmd.swift
  • Sources/aetherctl/PlaybackCmd.swift
  • Sources/aetherctl/Serve.swift
  • Sources/aetherctl/main.swift
  • Tests/AetherEngineTests/APACEncodePipelineTests.swift
  • Tests/AetherEngineTests/APACEncoderTests.swift
  • Tests/AetherEngineTests/APACSampleEntryTests.swift
  • Tests/AetherEngineTests/AVFoundationOffMainTests.swift
  • Tests/AetherEngineTests/AVIOReaderAbortableWaitTests.swift
  • Tests/AetherEngineTests/AudioBridgeLiveBytesTests.swift
  • Tests/AetherEngineTests/AudioLanguageSelectionTests.swift
  • Tests/AetherEngineTests/AudioOutputFlushEpochTests.swift
  • Tests/AetherEngineTests/BackgroundKeepaliveTests.swift
  • Tests/AetherEngineTests/BlockingWorkTests.swift
  • Tests/AetherEngineTests/BluRayEPMapSeekTests.swift
  • Tests/AetherEngineTests/BluRayFolderTests.swift
  • Tests/AetherEngineTests/BoundedFetchTests.swift
  • Tests/AetherEngineTests/BoundedFrameExtractorTests.swift
  • Tests/AetherEngineTests/ByteFIFOTests.swift
  • Tests/AetherEngineTests/CancellableLoadTests.swift
  • Tests/AetherEngineTests/ClosedCaptionTapA53Tests.swift
  • Tests/AetherEngineTests/DTSXObjectMetadataMappingTests.swift
  • Tests/AetherEngineTests/DTSXSampleTests.swift
  • Tests/AetherEngineTests/DVDCellTimelineTests.swift
  • Tests/AetherEngineTests/DemuxerStreamLifetimeTests.swift
  • Tests/AetherEngineTests/DetourOriginTicketTests.swift
  • Tests/AetherEngineTests/DiscReaderRangeAnswerTests.swift
  • Tests/AetherEngineTests/DocumentedConstantsTests.swift
  • Tests/AetherEngineTests/DolbyVisionRecordAuditTests.swift
  • Tests/AetherEngineTests/EngineTLSHandshakeTests.swift
  • Tests/AetherEngineTests/ErrorEndedReconnectTests.swift
  • Tests/AetherEngineTests/FrameDecodeContextHardwareTests.swift
  • Tests/AetherEngineTests/HLSLiveIngestDecryptIntegrationTests.swift
  • Tests/AetherEngineTests/HLSLocalServerSlowPeerTests.swift
  • Tests/AetherEngineTests/HLSOriginRelayStalledConsumerTests.swift
  • Tests/AetherEngineTests/HTTPDiscIOReaderTests.swift
  • Tests/AetherEngineTests/IFrameRenditionTests.swift
  • Tests/AetherEngineTests/IFrameSessionTests.swift
  • Tests/AetherEngineTests/IFrameSideReaderTests.swift
  • Tests/AetherEngineTests/Issue141FarForwardSeekTests.swift
  • Tests/AetherEngineTests/Issue151SubtitleForwardPrefetchTests.swift
  • Tests/AetherEngineTests/Issue169DeadProducerEscalationTests.swift
  • Tests/AetherEngineTests/Issue174PersistentReadBackpressureTests.swift
  • Tests/AetherEngineTests/Issue177IngestPrefetchTests.swift
  • Tests/AetherEngineTests/Issue208FastZapDegradedStartTests.swift
  • Tests/AetherEngineTests/Issue209LiveTargetDurationStabilityTests.swift
  • Tests/AetherEngineTests/Issue220BoundedRangeTests.swift
  • Tests/AetherEngineTests/Issue243ReaderRetentionTests.swift
  • Tests/AetherEngineTests/Issue254OffMainRepositionTests.swift
  • Tests/AetherEngineTests/Issue255BodyReserveTests.swift
  • Tests/AetherEngineTests/Issue259A53CaptionAxisTests.swift
  • Tests/AetherEngineTests/Issue260FrameTimeAxisTests.swift
  • Tests/AetherEngineTests/Issue268HLSVODIngestTests.swift
  • Tests/AetherEngineTests/Issue281ColdStartRoundTripTests.swift
  • Tests/AetherEngineTests/Issue295RangeBoundaryRefetchTests.swift
  • Tests/AetherEngineTests/Issue306SoftwareTelemetryTests.swift
  • Tests/AetherEngineTests/Issue309SilentTransportDeathTests.swift
  • Tests/AetherEngineTests/Issue314FrameTimeSequenceTests.swift
  • Tests/AetherEngineTests/Issue374FirstServeAccountTests.swift
  • Tests/AetherEngineTests/Issue377HeldConnectionTests.swift
  • Tests/AetherEngineTests/Issue378SourceRefusedTests.swift
  • Tests/AetherEngineTests/Issue388RedirectChainBudgetTests.swift
  • Tests/AetherEngineTests/Issue395RendererTimelineTests.swift
  • Tests/AetherEngineTests/Issue406NoCutWatchdogTests.swift
  • Tests/AetherEngineTests/Issue410SourceStallVisibilityTests.swift
  • Tests/AetherEngineTests/Issue436ResumeRateTests.swift
  • Tests/AetherEngineTests/Issue440LiveJoinRollTests.swift
  • Tests/AetherEngineTests/Issue445VMRegionCensusTests.swift
  • Tests/AetherEngineTests/Issue450ConcurrentReaderCeilingTests.swift
  • Tests/AetherEngineTests/Issue455ForcedDVRouteTests.swift
  • Tests/AetherEngineTests/Issue458AudioLanguageMetadataTests.swift
  • Tests/AetherEngineTests/Issue493HostDisplayAssertionTests.swift
  • Tests/AetherEngineTests/Issue535RefusalNeedsEligibilityTests.swift
  • Tests/AetherEngineTests/Issue551RedirectHandoffTests.swift
  • Tests/AetherEngineTests/Issue594BoundedStartFloorTests.swift
  • Tests/AetherEngineTests/Issue597MediaServicesResetTests.swift
  • Tests/AetherEngineTests/Issue597StrandedDiagnosticReadTests.swift
  • Tests/AetherEngineTests/Issue605SoftwareVODStillTests.swift
  • Tests/AetherEngineTests/Issue613PacketCoverageTests.swift
  • Tests/AetherEngineTests/Issue619ChunkedByteWindowTests.swift
  • Tests/AetherEngineTests/Issue647DiscReaderPrewarmTests.swift
  • Tests/AetherEngineTests/Issue667MasterAfterSwitchTests.swift
  • Tests/AetherEngineTests/Issue684JoinBoundSealTests.swift
  • Tests/AetherEngineTests/Issue684PumpJoinSpentTests.swift
  • Tests/AetherEngineTests/Issue694AudioHostEndOfMediaParkTests.swift
  • Tests/AetherEngineTests/Issue726AudioRenditionChannelsTests.swift
  • Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift
  • Tests/AetherEngineTests/Issue733PlaylistOnForwardOnlyOpenTests.swift
  • Tests/AetherEngineTests/Issue735FormatDescriptionsOffMainTests.swift
  • Tests/AetherEngineTests/Issue93SlowSegmentServeTests.swift
  • Tests/AetherEngineTests/ItemSwapStillTests.swift
  • Tests/AetherEngineTests/LiveDVRLimitsTests.swift
  • Tests/AetherEngineTests/LivePacketReplayGuardTests.swift
  • Tests/AetherEngineTests/LivePlaylistRenewalTests.swift
  • Tests/AetherEngineTests/LiveProductionHaltTests.swift
  • Tests/AetherEngineTests/LiveRecordingTeardownTests.swift
  • Tests/AetherEngineTests/LiveRecoveryPlaylistGrowthTests.swift
  • Tests/AetherEngineTests/LiveReopenAfterTeardownTests.swift
  • Tests/AetherEngineTests/LiveReplayPacketReaderTests.swift
  • Tests/AetherEngineTests/LiveSourceReplayTests.swift
  • Tests/AetherEngineTests/LiveStartupAdmissionTests.swift
  • Tests/AetherEngineTests/LiveTimestampRollbackTests.swift
  • Tests/AetherEngineTests/LiveWindowBackpressureTests.swift
  • Tests/AetherEngineTests/LongGOPLiveStartupTests.swift
  • Tests/AetherEngineTests/LostSourceReadErrorTests.swift
  • Tests/AetherEngineTests/MATCarrierDownmixWarningTests.swift
  • Tests/AetherEngineTests/MasterFallbackDecisionTests.swift
  • Tests/AetherEngineTests/MovencFragmentEndTests.swift
  • Tests/AetherEngineTests/NativeReaderDeferralTests.swift
  • Tests/AetherEngineTests/ObjectMetadataHoldTests.swift
  • Tests/AetherEngineTests/OriginRequestBudgetAbortTests.swift
  • Tests/AetherEngineTests/OriginRequestBudgetTests.swift
  • Tests/AetherEngineTests/PacketRingBufferTests.swift
  • Tests/AetherEngineTests/PeakLimiterTests.swift
  • Tests/AetherEngineTests/PlaybackErrorInfoTests.swift
  • Tests/AetherEngineTests/PlaybackNetworkPriorityTests.swift
  • Tests/AetherEngineTests/ProgressiveSegmentDeliveryTests.swift
  • Tests/AetherEngineTests/RangeIgnoringOriginTests.swift
  • Tests/AetherEngineTests/RangedResponseIntegrityTests.swift
  • Tests/AetherEngineTests/RemoteDiscSessionReleaseTests.swift
  • Tests/AetherEngineTests/ResolvedURLInvalidationTests.swift
  • Tests/AetherEngineTests/RestartTimelineContinuityTests.swift
  • Tests/AetherEngineTests/SWClockAnchorPolicyTests.swift
  • Tests/AetherEngineTests/ScrubThumbnailSegmentMappingTests.swift
  • Tests/AetherEngineTests/SegmentFetchWaitTests.swift
  • Tests/AetherEngineTests/SegmentStorageExhaustionTests.swift
  • Tests/AetherEngineTests/SequentialOriginReaderTests.swift
  • Tests/AetherEngineTests/ServedFromMemoryProgressTests.swift
  • Tests/AetherEngineTests/SoftwareDVRRetentionTests.swift
  • Tests/AetherEngineTests/SoftwarePacketReadAheadParkTests.swift
  • Tests/AetherEngineTests/SourceOpenPolicyTests.swift
  • Tests/AetherEngineTests/SourceOpenRecoveryTests.swift
  • Tests/AetherEngineTests/SourcePrewarmAdoptionTests.swift
  • Tests/AetherEngineTests/SourcePrewarmFetcherTests.swift
  • Tests/AetherEngineTests/SpatialAudioBridgeTests.swift
  • Tests/AetherEngineTests/SpatialObjectRenderingTests.swift
  • Tests/AetherEngineTests/StreamingBufferTrimTests.swift
  • Tests/AetherEngineTests/SupersededProducerReportTests.swift
  • Tests/AetherEngineTests/SupersededPumpExitTests.swift
  • Tests/AetherEngineTests/Support/CannedHTTPOrigin.swift
  • Tests/AetherEngineTests/Support/KeepAliveRangeOrigin.swift
  • Tests/AetherEngineTests/Support/LongGOPTransportStreamFixture.swift
  • Tests/AetherEngineTests/Support/LoopbackListener.swift
  • Tests/AetherEngineTests/Support/OffCooperativePool.swift
  • Tests/AetherEngineTests/Support/PrewarmStoreSuites.swift
  • Tests/AetherEngineTests/Support/ProbeHTTPTestOrigin.swift
  • Tests/AetherEngineTests/ThrottledOriginServer.swift
  • Tests/AetherEngineTests/TinyTransportStreamFixture.swift
  • Tests/AetherEngineTests/TrueHDObjectMetadataMappingTests.swift
  • Tests/AetherEngineTests/UDFFixture.swift
  • Tests/AetherEngineTests/UDFOnlyDVDImageTests.swift
  • Tests/AetherEngineTests/VODOpenSeekIntegrationTests.swift
  • Tests/AetherEngineTests/VODSeekCapabilityTests.swift
  • Tests/AetherEngineTests/VideoProgressTests.swift
  • docs/api.md
  • docs/architecture.md
  • docs/cli.md
  • docs/formats.md
  • docs/validation/live-video-progress.md

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@silo-kody

silo-kody Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Silo Kody — review complete

Review finished. Check the inline comments for findings and verify each suggestion against the code and tests.

Reviewing changes in Silo
  • Include the related issue, expected behavior, and validation steps in the PR description.
  • For API changes, describe the effect on Apple and Android clients and Jellyfin compatibility.
  • For plugin changes, identify the affected SDK contract, plugin, and catalog entry.
  • Follow this repository's AGENTS.md and CONTRIBUTING.md.
  • Request another review with @kody start-review in a PR comment.
  • React with 👍 or 👎 to give feedback on individual suggestions.
Review settings
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug ✅
Performance ✅
Security ✅
Business Logic ❌

@Quick104
Quick104 merged commit 23889c5 into main Oct 13, 2026
3 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4ab3e3cdeb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +151 to +152
let first = await group.next() ?? nil
group.cancelAll()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Enforce the Dolby Vision hold budget outside the task group

When a Profile 5/10 audio switch needs more than three seconds to decode the resident frame—for example with a long GOP or unusually expensive segment—the timeout child can finish first, but returning from withTaskGroup still waits for every child to exit. This cancelAll() does not stop the decode because snapshot(afterFirstFrameBy:) passes isCancelled: { false } into decodeFrame, so the outgoing player can remain paused and the audio switch delayed well beyond heldPictureDolbyVisionBudgetSeconds; use a cancellation-aware bounded extraction or an unstructured task whose completion is not awaited after the deadline.

Useful? React with 👍 / 👎.

Comment on lines +151 to +153
let first = await group.next() ?? nil
group.cancelAll()
return first

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Performance medium

Timeout enforcement fails for Dolby Vision frame extraction: the three-second task-group race still waits for decoding after the timer wins because FrameExtractor.snapshot(afterFirstFrameBy:) awaits a dispatch-queue continuation and passes isCancelled: { false } to its decoder, keeping the outgoing native item paused and delaying the entire audio switch beyond its budget. Propagate cancellation into the extractor's read/decode loop rather than relying on group.cancelAll().

Prompt for LLM

File Sources/AetherEngine/AetherEngine+HeldPicture.swift:

Line 151 to 153:

Timeout enforcement fails for Dolby Vision frame extraction: the three-second task-group race still waits for decoding after the timer wins because `FrameExtractor.snapshot(afterFirstFrameBy:)` awaits a dispatch-queue continuation and passes `isCancelled: { false }` to its decoder, keeping the outgoing native item paused and delaying the entire audio switch beyond its budget. Propagate cancellation into the extractor's read/decode loop rather than relying on `group.cancelAll()`.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

// #127: readiness + deferred host seeks are session-scoped; the host-side sink can't clear them
// once nativeCancellables are gone.
isSessionReady = false
isSourceSeekable = nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Bug high

Seekability state remains invalid after internal software reloads clear isSourceSeekable to nil because loadSoftware never republishes the reopened demuxer's capability, leaving canSeek permanently false after readiness when switching audio tracks on seekable software VOD. Publish dem.isSourceSeekable after a successful, generation-checked software open.

Prompt for LLM

File Sources/AetherEngine/AetherEngine.swift:

Line 7545:

Seekability state remains invalid after internal software reloads clear `isSourceSeekable` to `nil` because `loadSoftware` never republishes the reopened demuxer's capability, leaving `canSeek` permanently false after readiness when switching audio tracks on seekable software VOD. Publish `dem.isSourceSeekable` after a successful, generation-checked software open.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Comment on lines +4759 to +4763
prefersObjectAudio: options.objectAudioRendering != .off && ObjectAudioRendering.isAvailable
&& !options.isLive && !options.audioOnly
&& !VideoRoutingPolicy.requiresSoftwarePath(
codecID: detectedCodecID, fieldOrder: detectedFieldOrder,
av1Available: VTCapabilityProbe.av1Available),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Bug medium

Object-audio ranking ignores explicit preferredDecodePath == .software, although later routing honors it, replacing a same-language default track with TrueHD Atmos/DTS:X for HEVC video on an APAC-capable OS even though SoftwarePlaybackHost decodes only its conventional bed. Exclude the explicit software preference from prefersObjectAudio.

Prompt for LLM

File Sources/AetherEngine/AetherEngine.swift:

Line 4759 to 4763:

Object-audio ranking ignores explicit `preferredDecodePath == .software`, although later routing honors it, replacing a same-language default track with TrueHD Atmos/DTS:X for HEVC video on an APAC-capable OS even though `SoftwarePlaybackHost` decodes only its conventional bed. Exclude the explicit software preference from `prefersObjectAudio`.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

if #available(visionOS 2, *) {
return Task.detached(executorPreference: BlockingExecutor.shared, priority: priority, operation: operation)
}
return Task.detached(priority: priority, operation: operation)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Kody Rules high

Cooperative-executor starvation can occur when the availability check fails because this throwing overload schedules documented blocking work on Swift's cooperative executor, potentially starving async authorization resolvers. Refactor the async operation boundary to isolate synchronous blocking work in a GCD-backed fallback and bridge completion through withCheckedThrowingContinuation, preserving errors, the cancellation flag, and the cancellation handler; creating its Task inside a GCD block alone does not fix the problem.

Also found in:

  • Sources/AetherEngine/BlockingWork.swift:25-25
  • Tests/AetherEngineTests/Issue260FrameTimeAxisTests.swift:120-120
  • Tests/AetherEngineTests/EngineTLSHandshakeTests.swift:88-88
  • Tests/AetherEngineTests/Issue259A53CaptionAxisTests.swift:77-77
  • Tests/AetherEngineTests/Support/OffCooperativePool.swift:20-20
  • Tests/AetherEngineTests/Issue314FrameTimeSequenceTests.swift:138-138
  • Tests/AetherEngineTests/SourcePrewarmAdoptionTests.swift:155-155
  • Tests/AetherEngineTests/Issue450ConcurrentReaderCeilingTests.swift:185-185
  • Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:26-26
  • Tests/AetherEngineTests/IFrameSessionTests.swift:171-171
  • Tests/AetherEngineTests/RemoteDiscSessionReleaseTests.swift:102-102
  • Tests/AetherEngineTests/IFrameSessionTests.swift:189-189
  • Tests/AetherEngineTests/BoundedFrameExtractorTests.swift:53-53
  • Tests/AetherEngineTests/SourcePrewarmAdoptionTests.swift:177-177
  • Tests/AetherEngineTests/SourcePrewarmAdoptionTests.swift:199-199
  • Tests/AetherEngineTests/RemoteDiscSessionReleaseTests.swift:121-121
  • Tests/AetherEngineTests/Issue551RedirectHandoffTests.swift:49-49
  • Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:55-55
  • Tests/AetherEngineTests/Issue647DiscReaderPrewarmTests.swift:78-78
  • Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:59-59
  • Tests/AetherEngineTests/Issue647DiscReaderPrewarmTests.swift:86-86
  • Tests/AetherEngineTests/Issue647DiscReaderPrewarmTests.swift:123-123
  • Tests/AetherEngineTests/Issue551RedirectHandoffTests.swift:109-109
  • Tests/AetherEngineTests/RemoteDiscSessionReleaseTests.swift:162-162
  • Tests/AetherEngineTests/Issue551RedirectHandoffTests.swift:64-64
  • Tests/AetherEngineTests/RemoteDiscSessionReleaseTests.swift:144-144
  • Tests/AetherEngineTests/Issue551RedirectHandoffTests.swift:95-95
  • Tests/AetherEngineTests/Issue551RedirectHandoffTests.swift:46-46
  • Tests/AetherEngineTests/Issue647DiscReaderPrewarmTests.swift:53-53
  • Tests/AetherEngineTests/Issue551RedirectHandoffTests.swift:112-112
  • Tests/AetherEngineTests/Issue551RedirectHandoffTests.swift:134-134
  • Tests/AetherEngineTests/LiveSourceReplayTests.swift:451-451
  • Tests/AetherEngineTests/Issue551RedirectHandoffTests.swift:92-92
  • Tests/AetherEngineTests/Issue551RedirectHandoffTests.swift:63-63
  • Tests/AetherEngineTests/LiveSourceReplayTests.swift:480-480
  • Tests/AetherEngineTests/Issue551RedirectHandoffTests.swift:75-75
  • Tests/AetherEngineTests/Issue647DiscReaderPrewarmTests.swift:102-102
  • Tests/AetherEngineTests/Issue551RedirectHandoffTests.swift:133-133
  • Tests/AetherEngineTests/Issue647DiscReaderPrewarmTests.swift:71-71
  • Tests/AetherEngineTests/LiveSourceReplayTests.swift:459-459
  • Tests/AetherEngineTests/LiveSourceReplayTests.swift:478-478
  • Tests/AetherEngineTests/Issue551RedirectHandoffTests.swift:128-128
  • Tests/AetherEngineTests/LiveSourceReplayTests.swift:448-448
  • Tests/AetherEngineTests/Issue647DiscReaderPrewarmTests.swift:113-113

Kody rule violation: Move blocking decodes off Swift's cooperative executor

Prompt for LLM

File Sources/AetherEngine/BlockingWork.swift:

Line 36:

Cooperative-executor starvation can occur when the availability check fails because this throwing overload schedules documented blocking work on Swift's cooperative executor, potentially starving async authorization resolvers. Refactor the async `operation` boundary to isolate synchronous blocking work in a GCD-backed fallback and bridge completion through `withCheckedThrowingContinuation`, preserving errors, the cancellation flag, and the cancellation handler; creating its `Task` inside a GCD block alone does not fix the problem.

**Also found in:**
- `Sources/AetherEngine/BlockingWork.swift:25-25`
- `Tests/AetherEngineTests/Issue260FrameTimeAxisTests.swift:120-120`
- `Tests/AetherEngineTests/EngineTLSHandshakeTests.swift:88-88`
- `Tests/AetherEngineTests/Issue259A53CaptionAxisTests.swift:77-77`
- `Tests/AetherEngineTests/Support/OffCooperativePool.swift:20-20`
- `Tests/AetherEngineTests/Issue314FrameTimeSequenceTests.swift:138-138`
- `Tests/AetherEngineTests/SourcePrewarmAdoptionTests.swift:155-155`
- `Tests/AetherEngineTests/Issue450ConcurrentReaderCeilingTests.swift:185-185`
- `Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:26-26`
- `Tests/AetherEngineTests/IFrameSessionTests.swift:171-171`
- `Tests/AetherEngineTests/RemoteDiscSessionReleaseTests.swift:102-102`
- `Tests/AetherEngineTests/IFrameSessionTests.swift:189-189`
- `Tests/AetherEngineTests/BoundedFrameExtractorTests.swift:53-53`
- `Tests/AetherEngineTests/SourcePrewarmAdoptionTests.swift:177-177`
- `Tests/AetherEngineTests/SourcePrewarmAdoptionTests.swift:199-199`
- `Tests/AetherEngineTests/RemoteDiscSessionReleaseTests.swift:121-121`
- `Tests/AetherEngineTests/Issue551RedirectHandoffTests.swift:49-49`
- `Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:55-55`
- `Tests/AetherEngineTests/Issue647DiscReaderPrewarmTests.swift:78-78`
- `Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:59-59`
- `Tests/AetherEngineTests/Issue647DiscReaderPrewarmTests.swift:86-86`
- `Tests/AetherEngineTests/Issue647DiscReaderPrewarmTests.swift:123-123`
- `Tests/AetherEngineTests/Issue551RedirectHandoffTests.swift:109-109`
- `Tests/AetherEngineTests/RemoteDiscSessionReleaseTests.swift:162-162`
- `Tests/AetherEngineTests/Issue551RedirectHandoffTests.swift:64-64`
- `Tests/AetherEngineTests/RemoteDiscSessionReleaseTests.swift:144-144`
- `Tests/AetherEngineTests/Issue551RedirectHandoffTests.swift:95-95`
- `Tests/AetherEngineTests/Issue551RedirectHandoffTests.swift:46-46`
- `Tests/AetherEngineTests/Issue647DiscReaderPrewarmTests.swift:53-53`
- `Tests/AetherEngineTests/Issue551RedirectHandoffTests.swift:112-112`
- `Tests/AetherEngineTests/Issue551RedirectHandoffTests.swift:134-134`
- `Tests/AetherEngineTests/LiveSourceReplayTests.swift:451-451`
- `Tests/AetherEngineTests/Issue551RedirectHandoffTests.swift:92-92`
- `Tests/AetherEngineTests/Issue551RedirectHandoffTests.swift:63-63`
- `Tests/AetherEngineTests/LiveSourceReplayTests.swift:480-480`
- `Tests/AetherEngineTests/Issue551RedirectHandoffTests.swift:75-75`
- `Tests/AetherEngineTests/Issue647DiscReaderPrewarmTests.swift:102-102`
- `Tests/AetherEngineTests/Issue551RedirectHandoffTests.swift:133-133`
- `Tests/AetherEngineTests/Issue647DiscReaderPrewarmTests.swift:71-71`
- `Tests/AetherEngineTests/LiveSourceReplayTests.swift:459-459`
- `Tests/AetherEngineTests/LiveSourceReplayTests.swift:478-478`
- `Tests/AetherEngineTests/Issue551RedirectHandoffTests.swift:128-128`
- `Tests/AetherEngineTests/LiveSourceReplayTests.swift:448-448`
- `Tests/AetherEngineTests/Issue647DiscReaderPrewarmTests.swift:113-113`

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Quick104 added a commit to Silo-Server/silo-apple that referenced this pull request Oct 14, 2026
* chore(deps): pin AetherEngine with upstream 7.36.0

Move the AetherEngine pin to 23889c5b, the fork main that merges upstream
AetherEngine 7.29.0 through 7.36.0 plus upstream's live-playlist renewal
(#748), Silo-Server/AetherEngine#21.

Resolution moves LibTrueHD 2.0.0 to 2.3.0 and FFmpegBuild 3.6.0 to 3.7.1,
and adds LibDTSX 1.0.1, the DTS:X decoder behind objectAudioRendering.
Bundle LibDTSX's NOTICE and Apache license, list it in the
acknowledgements, refresh the LibTrueHD NOTICE and the provenance notes,
and refresh the reviewed build-input and selector snapshots for the new
project.yml.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: count LibDTSX in the release source proof's package graph

The source proof requires the locked package graph to have an exact size.
The AetherEngine pin adds LibDTSX, so the graph has 12 packages and the
complete source graph 19 entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(tvos): show Dolby Vision in playback stats when the TV switches to it

An Apple TV set to SDR with Match Dynamic Range on switches the TV to the
content's range, but Aether can't prove the panel left SDR (its EDR
headroom stays 1.0), so it keeps reporting SDR. The stats showed
"Dolby Vision Profile 7 → SDR" while the TV displayed Dolby Vision.

When matching is on and the display supports the source's range, label
that range, as Aether serves it to the panel either way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(tvos): label the stats range from the load's own matching decision

A load made with HDR off in Silo's settings doesn't match the content's
range, so the TV stays in SDR; the stats now follow that load's
matchContentEnabled instead of the system setting alone. On a display
without Dolby Vision, a Profile 5 or 7 source shows HDR10, the PQ base
Aether routes there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(tvos): label a Dolby Vision base layer by its transfer

On a matched display without Dolby Vision, Aether presents the Dolby
Vision base layer: HLG for an HLG base, otherwise HDR10. Read the base's
transfer from sourceVideoStreamFormat instead of inferring it from the
profile number, so Profile 8.1 shows HDR10 and 8.4 shows HLG.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(tvos): mark the stats range unconfirmed when the TV can't prove it

tvOS reports one setting for frame-rate and range matching, so the app
can't tell whether the TV switched to HDR. Instead of claiming the TV's
mode, the stats name the range Aether sent and mark it unconfirmed, e.g.
"Dolby Vision Profile 7 → Profile 8.1 (unconfirmed)". A Dolby Vision
source with an SDR base layer (Profile 8.2) keeps the SDR label.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(tvos): keep an SDR Dolby Vision base labelled SDR on any display

Aether strips Dolby Vision from an SDR base layer (Profiles 8.2 and 10.2)
and plays that base as SDR even on a Dolby Vision TV, so the stats check
the base layer's transfer before naming Dolby Vision.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants