Repository navigation
Conversation
…ncement layer's RPUs folded into the base layer A UHD Blu-ray carries Dolby Vision as two HEVC PIDs in one clip: the base layer on 0x1011 and the enhancement layer on 0x1015, whose access units hold the RPU for the base picture with the same PTS. No DOVI record sits on either stream, so the base layer played as plain HDR10. The demuxer now declares Profile 7 on the base layer, appends each picture's RPU to its base-layer access unit (Annex-B) and keeps the enhancement packets to itself, so the rest of the engine sees the single-track Profile 7 shape a remux has and takes its usual route (8.1 by default). The enhancement unit usually follows its base unit, but not always: base units wait in a short queue (8 pictures) and enhancement units that come first leave their RPU by PTS. A first version that held one base unit lost the RPU on 40% of pictures after a mid-film start. Measured on a retail UHD disc (Blu-ray folder, 90 GB main clip, over WebDAV): one RPU per enhancement unit, none in the base layer; aetherctl play 30 s from the start and from 5000 s: fmt=dolbyVision dvProfile=7 dvConversion=profile7ToProfile81, every base picture paired (was fmt=hdr10 before). Not yet watched on a Dolby Vision display.
The suite's tests changed process-wide statics while running in parallel, so one test's 1 s budget leaked into other readers. Give-up is timed on the wall clock, so a stalled CI runner ended the retry ladder after 2 refusals. The reader now takes both values as init parameters, the test-only static is removed, and the give-up test asserts time spent >= budget.
…in tests Tests set process-wide statics that every reader built by a concurrently running suite read at init, so one test's 0.02 backoff scale or 0.2 s pin gap leaked into other readers. AVIOReader now takes backoffScale and pinIdleSeconds as init parameters (nil keeps the shipped value), and the two AetherEngine statics are removed. The superuser404notfound#255 peak body reserve is tracked per reader instead of in a static that every reader's fetches raised.
The truncation test now fails if no chunk fetch reported a reservation, so it cannot pass on a reader that never reported. The peak body reserve comment now says it is lock-protected and raised from each fetch's delegate.
mediaChapters carried raw container times, but seek(to:) and currentTime run from the container start. On a source with a non-zero container start a host passing a chapter start to seek(to:) landed that many seconds late. The chapters are now rebased per backend onto the clock that seek(to:) uses: the container start on native, and the software clock's zero on the software path. That zero is predicted from the origin the software host anchors on (container start, backed up by the video stream start) and replaced by the clock's own session zero once it has anchored. Live keeps origin 0 on both routes. The probe snapshots the raw chapters, and the list is republished when the backend is settled, so reopens keep working; nothing is published while no backend is settled. A chapter is left out only when it ends at or before the content start, judged on its effective end (the next chapter's start, else a declared end after its start, else the container duration), because MKVs often declare end == start. Audio-only backends keep the raw axis.
…time On a source whose container starts past the 2 s tolerance, a seek issued before the clock is armed converted with session zero 0 and then armed the clock itself, so the demux loop never resolved the zero and the seek landed the container start early. The host now derives the zero from the source origin on that path, as a resume does, and reports it so the published chapters stay on the same axis. The media test now asserts the raw source time both before and after play() and waits for the session to be ready instead of a fixed settle window. Test comments describe the clips neutrally, and the stale comment in stop() is corrected.
…on-live source The zero was only set for a resume or by the first seek before play, so the subtitle-harvest anchor and the scrub stills of a cold load converted with zero and named content that sits an origin earlier. The host now derives it from the source origin when it loads, as a resume does, and reports it; the seek-time block is gone. The demux loop still re-anchors on a play. The media test adds a play-first pass on a fresh load, reads sourceTime once about 0.35 s after each seek inside a tight window instead of the 4 s window, asserts the converted zero before play, and checks sourceTime on the native route.
…lay (superuser404notfound#803) * feat(audio): LiveTelemetry.objectAudio reports the Atmos objects in play While objectAudioRendering plays TrueHD Atmos or DTS:X, the 1 Hz telemetry carries an ObjectAudioActivity: the bed it renders into, the objects the presentation declares, how many play and how many sit overhead, and its bed channels on height speakers (DTS:X's standard heights). These are the counts the bed levels log line has carried since superuser404notfound#731, for a host's stats panel without reading the log. The bridge publishes them behind a lock of its own, so the sampler never waits for a decode holding opLock. aetherctl play takes --atmos-bed as serve does and prints the counts: obj=13/13 up=4 bed=7.1.4 on a TrueHD Atmos remux. * docs(audio): LiveTelemetry.objectAudio is nil without the object-audio traits Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS * style(diagnostics): keep audioBridgeOutputBytesLifetime's doc comment on it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS --------- Co-authored-by: Vincent Herbst <superuser404@tuta.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A playlist can name any scheme, and URLSession reads file://localhost/... from the sandbox. The relay's pump already cancelled any answer that is not an HTTPURLResponse, so such a URI ended as a 502 rather than being served, but that was a side effect. Admission now refuses every scheme but http and https, and a request naming one answers 404. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
… 45 s exchange ceiling (superuser404notfound#732) Under heldSourceConnection the HEVC-in-MPEG-TS VOD ingest downloaded every segment whole before the demuxer saw a byte, its carriage probe downloaded segment 0 whole, and one 45 s deadline covered head and body together. On a slow origin a large segment could not finish inside it, and the expiry surfaced as the socket's own POSIX timeout (Code=60), naming neither cause. - PersistentHTTP1Transport.open returns the response head and a Body read piece by piece; get() is open plus collect. - The 45 s ceiling covers only the wait for a response head. A body ends on its size limit or on 30 s without a byte (typed .stalled with received/expected bytes and elapsed time). - One request per origin at a time: checkOut claims the origin and the next request waits for the connection to be pooled or closed, so a seek never opens a second connection beside a response still in flight. - An abandoned body with at most 2 MB left drains for up to 3 s and keeps its socket; a larger remainder closes it. - The probe reads the head and hands the open response to the producer. - With the FIFO full the current segment is read to its end, so a pause idles the connection between requests, not mid-body. - The h2 path's 45 s resource timeout is lifted the same way. Measured on a loopback keep-alive origin at 200 KB/s with 11.6 MB segments: before, the probe died at 45.1 s with Code=60 and the session left the ingest; after, the ingest held and played. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…user404notfound#732) - A probe response the producer's run does not open with (a seek onto another or an encrypted first segment) is abandoned before any request. It held the origin's claim unread, so the next request waited forever. - A request cancelled while it waited for the origin does not take the connection; its cancellation handler would close the drained socket. - A seek drains an abandoned body only when the rest arrives within the drain deadline at the rate seen so far; a slow remainder closes at once instead of holding the next request 3 s and closing anyway. - next() checks cancellation before marking a read in flight, so abandon() never waits on a read that does not happen. - A refused or redirecting body is drained for at most 45 s. - No extra copy of each chunk into the pending buffer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…/fix/732-streamed-segment-bodies fix(hls): stream segment bodies on the persistent HTTP/1.1 ingest, no 45 s exchange ceiling (superuser404notfound#732)
LiveStreamRefusalRetryTests failed on CI in six recent runs (main and four PR branches): "a stream refused mid-session ..." and "a stream refused for longer than the budget ..." with drained.failure != nil and too few requests. Cause: AVIOReader read AetherEngine.reconnectBackoffScaleForTesting, pinIdleSecondsForTesting and liveRefusalRetryBudgetForTesting from statics at init. swift-testing runs these tests, and the five other suites that set the scale, in parallel, so a reader picked up whatever its neighbours had set: the budget test's 1 s budget plus a scale another suite's defer had put back to 1.0 makes the mid-session test give up after its second refusal. Forcing exactly that interleaving on main reproduces the three CI expectation failures deterministically. The three values are now AVIOReader init parameters (backoffScale, pinIdleSeconds, liveRefusalRetryBudget) with the shipped defaults, the same shape connStallTimeout and windowHighWater already take for this reason, and the statics are gone. Six suites pass their values to the reader they build. No behaviour change outside tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…/fix/live-refusal-test-race test(io): reader test knobs are init parameters, not process-wide hooks
… split by object energy (superuser404notfound#731) The TrueHD path no longer mixes object signals into the layout. The decoder discards the matrix interpolation values and carries the stream's channel-based presentation (Dolby's 7.1 or 5.1 rendering of the same mix) alongside each block; BedSplitter divides every bed channel per STFT bin between the floor speakers it covers and the heights, by where the objects' energy lies, bounded by the bed and balanced per band towards a direct render. Elements are plain point sources. A 5.1 bed covers a 7.1 layout's sides and rears. The splitter's 2048-frame latency is drained before the encoder restarts elsewhere and at end of input, so no audio is lost there. AETHER_TRUEHD_REFERENCE_RENDER=1 keeps the exact decode and mix for measurements. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…peruser404notfound#731) The bed bound now compares an element with its bed channels per band instead of per bin: in a single bin Dolby's mix can hold less than one element where several cancel, and the per-bin bound cut legitimate height content there. The balance only raises an output towards the direct-render estimate and never lowers it, since the elements' energies add up to less than a mix of them wherever they carry the same signal. Against the exact render on nine height-heavy windows of five films the heights went from -0.61 to -0.20 dB, the median misplaced energy from 0.54 to 0.38 %; on Unfold from 4.6 to 3.0 %, worst second 9.9 to 6.8 %. The LFE delay line was one hop longer than the split outputs' latency. Bridge tests run with and without the stream's bed; BedSplitterTests pin the split, the 5.1 bed on a 7.1 layout and the timeline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…me size is wrong, changelog for superuser404notfound#828 The sample-clock model skipped the duration-less fallback whenever codec geometry was admitted. A declared frame size that understates the packet (implicit HE-AAC: codecpar 1024, packets 2048) fails the join on every packet, so a duration-less track covered nothing and every seek refetched, the regression superuser404notfound#775 fixed for TrueHD. The fallback now applies whenever the join fails. Also keeps superuser404notfound#823's overflowing successor case as a regression. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
# Conflicts: # CHANGELOG.md
…peruser404notfound#834) The activator looped its silent Atmos stream beside APAC on every tvOS route. Over AirPlay to a HomePod pair the programme stayed silent after pause and resume, and froze after the second pause, until a seek. It now plays only while every output port is HDMI, re-evaluates on route changes, and logs its route verdict when that changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
# Conflicts: # CHANGELOG.md
…peruser404notfound#839) Broadcast HLG (BBC iPlayer, UK DVB-T2 UHD) declares a backward-compatible BT.2020 10-bit or BT.709 transfer and names HLG only in an alternative_transfer_characteristics SEI (payloadType 147). Format detection, the display criteria, the route's VIDEO-RANGE and the muxer's colr all read codecpar.color_trc, so the source played as SDR. The demuxer now peeks up to 8 video packets after open, gated on HEVC or H.264 with BT.2020 primaries and a BT.709 / BT.2020 / unspecified transfer, and an SEI naming HLG sets color_trc to ARIB STD-B67. Doing it in the demuxer covers the load probe, probe(url:) and a live reopen with one hook. The SEI walk is shared with the A53 caption parser. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…tive transfer SEI (superuser404notfound#839) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…e behind the live edge A live viewer on a link slower or less steady than the channel had no way to trade latency for a buffer: each path kept its own distance (the loopback's 3 x TARGETDURATION, the software path's delivery-unit lead, the origin's HOLD-BACK on the remote bypass), and `.fastZap` made it shorter. The new option only ever moves the playhead further back, clamped to 45 s: - Loopback: the served HOLD-BACK, the first-serve gate and its deadline follow the cushion, the window grows to hold it, and `.fastZap`'s shallow start is floored at it. - Live HLS ingest: the join takes enough of the listing to cover it, so a listed backlog fills the cushion at link speed instead of at 1x. - Software path: the join waits until the cushion stands behind the newest media and starts that far back, landings hold it and a rebuffer refills it before resuming. Without a DVR window the host keeps a packet ring that deep and still publishes no rewind window. - nativeRemoteHLS: configuredTimeOffsetFromLive, preserved across stalls. Measured with `aetherctl live --realtime --preroll 0` (raw TS, no backlog): first manifest after 19.2 s and ~17 s behind live without it, after 31.2 s and ~29 s behind with 30 s, no stall or -16832 in either arm, isAtLiveEdge held. Software path (`--sw --preroll 8`): ~20 s behind with 20 s against 0.4 s without, with and without a DVR window. `aetherctl play` and `live` take `--live-cushion SECONDS`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…own offset Pre-merge review: on nativeRemoteHLS the cushion went straight into configuredTimeOffsetFromLive, so a cushion shallower than the origin's HOLD-BACK (5 s against 18 s) moved the playhead CLOSER to the edge, breaking the "only ever further back" contract. The offset is now raised to recommendedTimeOffsetFromLive once the origin's playlist is read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
… a host-chosen distance behind the live edge
…, broadcast HLG Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…/now-playing) Conflicts resolved as suggested in the review: stopInternal keeps stopSoftwareNowPlaying() and main's softwareClockSessionZero reset; aetherctl drops the PR's --atmos-bed parser and global in favour of main's objectAudioRendering(_:); docs take main's text with the software-host sentences added.
… shown Selecting a PGS / DVB / DVD track armed the OCR worker for its native WebVTT rendition at once, and with it the forward prefetch lead of 270 s. On Matroska that side reader pulls every cluster, so a fullscreen viewer paid for a rendition nothing displayed: on a 4K remux 434 MB of prefetch in 90 s against 17 MB with the overlay-only lead, and an Apple TV 4K (1st gen) dropped frames on an 89 Mbit/s source with a forced PGS track. The worker now follows the native selection (setNativeSubtitleSelected, reached by setNativeSubtitleRendering for PiP / AirPlay / an external display) and raises or drops the prefetch lead with it. Because it now starts the window empty, the native select of an OCR rendition waits until the harvest has read and the worker has recognized every stored composition through subtitleOCRLeadSeconds, instead of the cue-end heuristic a sparse forced track never satisfies. The wait has no deadline (a refused empty rendition is never retried, a partial one is cached empty by AVKit), ends when the request changes, and reports its state after 60 s. Measured with aetherctl against a 4K HEVC remux: fullscreen 17 MB of prefetch in 90 s with the worker off; PiP at t=20 arms it, rebuilds the prefetch at 270 s and selects with the cue after 2.6 to 72 s (the long waits are Vision recognizing the first 4K composition). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…item The loopback withheld its first media playlist until the live window held the holdback or the host's liveEdgeCushionSeconds. That wait ran on AVPlayer's playlist timer, which gives up after about 20 s on tvOS (-16839 "Unable to get playlist before long download timer", then -1008), measured in the tvOS simulator. A raw MPEG-TS channel filling at 1x after its connect burst never started with a 30 s cushion, and the plain .standard holdback served after 19.2 s on a strict-realtime origin. loadNative now waits out the same first-serve gate before host.load, so the playlist request is answered at once (repeat request held 0.000 s) and the session still plays the full cushion behind the edge. Refs superuser404notfound#851 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
… before AVPlayer is handed the item (AE#851) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…ase (AE#682) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
ThreadCPUSampler states the CPU time each thread group consumed between two samples, as a share of one core, with the main thread and threads that ended inside the window on their own fields. The memprobe now emits a cpuprobe line beside it every 30 s, so a field log from an Apple TV nobody here owns reads as a coarse profile (DerPicknicker, A10X stutter on 4K where Infuse plays clean). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…ive selection, live cushion on tvOS Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…X bed test, no pause on HDMI, stale packet and the APAC anchor, DV P5 local stills Before merge: 1. SoftwareAtmosRouteTests.apacRoutes takes .l714 for DTS:X (main renders DTS:X only into a bed that carries it one to one) and a new test pins that every other bed stays PCM while TrueHD Atmos renders into any. The software host now logs why a TrueHD or DTS track stays on PCM (SoftwareAtmosRoute.apacRefusal), as it already did for E-AC-3. 2. Route removal pauses only for outputs a viewer takes away: headphones, Bluetooth (A2DP, LE, HFP), AirPlay and USB, spelled as AVAudioSession.Port raw values (checked against the session on iOS / tvOS). HDMI is out, so an ARC / eARC receiver toggling on an Apple TV keeps playing. 3. A pre-seek audio packet can no longer anchor the APAC bridge: SoftwareAudioProducing gains the AE#492 feed epoch, the demux loop reads it before the read, and SoftwareAPACProducer checks it under the same lock as flush()'s startSegment. 4. A local software session reports supportsCacheBackedStills false for a Dolby Vision Profile 5 / 10.0 stream without the DolbyVisionStillReshaping trait (FrameDecodeContext.yieldsStill). Follow-ups on the FEL + TrueHD Atmos path: - The demux loop's "decoder not recovering" clock fallback uses the producer's own first-buffer budget; the APAC producer's is the bridge's dead-decoder bound, since its first packet is about 100 TrueHD access units in and the old 50 sent every TrueHD load to the video-anchored clock. - The software host resolves a TrueHD track whose profile the probe missed from its major sync, sharing HLSVideoEngine.trueHDMajorSyncVerdict with the loopback route (superuser404notfound#838). CHANGELOG: route removal and the setRate(0) interruption change are listed; the software-host Atmos entry drops `aetherctl play --atmos-bed` (main has it). Co-authored-by: Claude <noreply@anthropic.com>
AVAudioSession.Port.HDMI.rawValue is "HDMIOutput" (read on an Apple TV 4K, tvOS 26.6, and an iOS 26 simulator). Spelled "HDMI", superuser404notfound#834's route rule matched no real HDMI route, so the activator never ran: the device logged '[AtmosOutputActivator] off on route ports=[HDMIOutput]' beside an APAC 7.1.4 programme. The tests that spelled HDMI the same way now use the session's value. Co-authored-by: Claude <noreply@anthropic.com>
…st plays object audio The Atmos output activator followed only the engine's AVPlayer. It now also follows a software host whose audio reaches its renderer as APAC (TrueHD Atmos, DTS:X) or compressed E-AC-3 JOC, driven by the engine's transport state. On an Apple TV 4K (1st gen, tvOS 26.6) on HDMI to an AVR, the receiver showed Atmos for the loopback route's APAC but not for the same title's APAC on the software host (the Dolby Vision FEL route) until the activator ran there. Co-authored-by: Claude <noreply@anthropic.com>
audioDelivery is .bridged for TrueHD Atmos / DTS:X as APAC and .streamCopy for compressed E-AC-3 JOC (the loopback route's terms), and activeAudioDecoder names them ('TRUEHD Atmos → APAC 7.1.4'). The background-audio check reads deliversAudio instead of == .decoded so those routes keep their background audio. On device the FEL route showed 'libavcodec TRUEHD → CoreAudio' / 'Decoded' while it played APAC 7.1.4.
Co-authored-by: Claude <noreply@anthropic.com>
…and detaches with its session Review findings: play() after an audio interruption keeps state .playing, so the renderer-path activator is refreshed on every accepted play; a renderer session that is gone detaches the activator (route observer included) instead of only releasing the stream. AudioDelivery and docs/api.md describe the software host's .streamCopy / .bridged. Co-authored-by: Claude <noreply@anthropic.com>
…g and clock fallback Co-authored-by: Claude <noreply@anthropic.com>
setRate(0) parks the software host without moving state off .playing, so the activator kept its silent stream looping under a paused programme. It now reads the host's own playing flag in that state and is refreshed after every rate change. Co-authored-by: Claude <noreply@anthropic.com>
Author
|
Moved to superuser404notfound#856 so it sits next to superuser404notfound#808 in the main repository. It is the same branch, and |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up to superuser404notfound#808, meant to be merged into
parity/now-playing. It brings the branch up to date withmain(7.45.0, then 7.46.0) with the conflicts resolved the way the review suggested, addresses the review's four "Before merge" items, and adds three fixes found while running this branch on an Apple TV with a Dolby Vision Profile 7 FEL title that has a TrueHD Atmos track.Refs superuser404notfound#808, superuser404notfound#838, superuser404notfound#848, superuser404notfound#854, superuser404notfound#462.
What changed
Merge with main.
stopInternalkeeps bothstopSoftwareNowPlaying()andsoftwareClockSessionZero = nil;aetherctlkeeps main's--atmos-bedparser (the branch's duplicate parser and global are gone); CHANGELOG / README /docs/api.md/docs/formats.mdtake main's text with the software-host sentences added.Review items 1–4
SoftwareAtmosRouteTests.apacRoutestakes.l714for DTS:X, and a new test pins that every bed without a one-to-one DTS:X layout stays PCM while TrueHD Atmos renders into any. The software host now logs why a TrueHD or DTS track stays on PCM, as it already did for E-AC-3.SoftwareAudioProducinggains the AE#492-style feed epoch, the demux loop reads it before the read, andSoftwareAPACProducerchecks it under the same lock asflush()'sstartSegment. Covered by a test that failed before the change (post-seek audio stamped 59 s early).supportsCacheBackedStills == falsefor Dolby Vision Profile 5 / 10.0 without theDolbyVisionStillReshapingtrait.Found on device
setRate(0)), and detaches with the session. On the test receiver the Atmos indicator stayed off for the FEL route's APAC until it did.AVAudioSession.Port.HDMIis"HDMIOutput", so the activator never ran on HDMI. This commit is identical to fix(audio): the Atmos output activator matches tvOS's HDMI port type superuser404notfound/AetherEngine#854 and can be dropped once that is merged.audioDeliveryis.bridgedfor APAC and.streamCopyfor compressed E-AC-3 JOC (the loopback route's terms), andactiveAudioDecodernames the route (TRUEHD Atmos → APAC 7.1.4); the background-audio decision accepts both.CHANGELOG (Unreleased) and
docs/api.md/PlayerState.swift(AudioDelivery) are updated.Test plan
swift test --enable-all-traitson this branch: 5211 Swift Testing tests and 785 XCTest cases. In the full parallel run, two load-sensitive tests failed (ThreadCPUSamplerTests,HLSOriginRelayStalledConsumerTests); both pass when re-run on their own. Earlier full runs on this branch showed the same kind of load-only failures inHLSOriginRelay*,EngineTLSHandshake,SubtitlePacketStoreScaling,PythonOriginLaunchandHLSPlaylistHostileInput, all passing alone.swift build --build-testswith default traits passes.active on route ports=[HDMIOutput]; before the HDMI fix it loggedoff.Checklist
audioDeliveryvalues on the software host)Co-authored with Claude (Anthropic); the commits carry a
Co-authored-by: Claudetrailer.