Repository navigation
fix(software-host): #808 review follow-ups (stacked on #808) - #856
Conversation
…with state-mapping tests
…ession behind ownsVideoNowPlayingSession
Subtitles composited into software-path PiP frames read the user's MediaAccessibility caption style (font, size, text, background and window colour and opacity, edge style) and re-render when it changes. Fields the user has not overridden keep the cue's styling or the default look. SubtitleCaptionStyle.current() / observeChanges(_:) serve host overlays.
Such a session keeps no packet cache, so supportsCacheBackedStills was false and scrubThumbnail / scrubPreviewFrame answered nil. They now decode from the file through a FrameExtractor (base layer for dual-layer Dolby Vision), with no extra connection.
…ehind glyphs A field is the user's choice only when the user domain differs from the system default; the UseValue / UseContentIfAvailable flag is carried per field as a precedence instead of being read as a change. The uniform edge is a stroke pass under the fill, the overlay cache is keyed by the style it was drawn in, the system style is read once on first enable, and a released observation no longer reaches its handler.
The extractor entry polls isCancelled, applies the 750 ms / 900 packet bound and reports the decoded frame time, which becomes actualSeconds on the session axis. The past-the-end clamp measures from the file's own start time, so a file that does not begin at 0 is no longer clamped against its duration alone. The test fixture is built once per process.
E-AC-3 JOC is enqueued to the audio renderer compressed, with the Atmos decoder primed once per process. TrueHD Atmos and DTS:X render to APAC under the existing objectAudioRendering option. Everything else, live sessions and .off keep the PCM decode. Adds aetherctl play --atmos-bed.
…l detection, review fixes
…command state alone on stop
…line knock-out The past-the-end clamp measures the container's duration from the container's start time and keeps the video stream's start as the target axis. Choice.resolved(cue:) is public so a host overlay can apply precedence. A uniform outline knocks the glyph area out before the fill, so a translucent text colour no longer sits over the stroke. The standard-look test checks measured values, and the observation test delivers an in-flight notification after release.
…mmand enablement restored only if unchanged
Check for a stop after the route-choosing suspensions and treat it as a cancellation. Start the Atmos decoder registration when the route is chosen and await it at the end of load. Open the audio tap decoders on first use. Flush the APAC encoder at end of stream and log a refused bridge packet. Separate PCM-fallback reasons, retry the dec3 build on a few packets, search dec3 only inside moov. Log renderer errors in release builds. Offer the Atmos audio pick on the software host and update the docs and tests.
…ls without the gate The uniform outline is drawn as a stroke and a copy-blend fill inside a transparency layer, replacing the clear knock-out: line background and window stay intact under translucent text, and opaque glyph edges stay solid. The observation test makes the post-release delivery the first one on its observation, so only the gate can stop it. clampSeekSeconds gets its doc comment back.
The E-AC-3 look read the stream's codec parameters with no guard held, so a stop closing the demuxer could free them mid-read. Read inside the peek closure under withStream and keep only copied values. A closed demuxer yields no audio packet.
The description handed to the audio renderer for APAC packets had no dapa cookie and no packet size, so the renderer failed every packet inside while reporting no error and playing silence. Build it with the cookie and 1024 frames per packet where the encoder is created.
Require the cookie in the test instead of force-unwrapping it, give the decode callback stable buffers instead of escaping pointers, and refuse to build a description from an empty cookie.
# Conflicts: # CHANGELOG.md
# Conflicts: # CHANGELOG.md
…le and Atmos source files
…, wrap the Atmos changelog entry
… APAC producer comes back nil
… the hardware decoder cannot play
# Conflicts: # README.md # Sources/AetherEngine/Audio/Spatial/ObjectAudioRendering.swift # docs/api.md # docs/formats.md
… and resolve the source like activation
… a stuck interruption
# Conflicts: # CHANGELOG.md # docs/formats.md
…e audio route is removed)
… on a route change) # Conflicts: # Sources/AetherEngine/AetherEngine.swift
… during an audio interruption) # Conflicts: # Sources/AetherEngine/AetherEngine.swift
…/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.
…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>
Conflicts: CHANGELOG.md (Unreleased › Changed takes main's two entries beside ours) and AtmosOutputActivator.swift (main's TrueHD-only rule for the AVPlayer path, in this branch's followAtmosOutput). The software host follows the same rule: its activator now runs for TrueHD Atmos rendered to APAC and for compressed E-AC-3 JOC, not for DTS:X rendered to APAC (SoftwareAtmosRoute.carriesDolbyAtmos, tested; docs/formats.md). Co-authored-by: Claude <noreply@anthropic.com>
Main now carries the HDMIOutput fix (814fef1), so this branch takes main's version of it and drops its own CHANGELOG entry. CHANGELOG (Unreleased › Added / Fixed) and docs/api.md (stereoPresentation row) keep both sides; SoftwarePlaybackHost keeps the first-buffer budget beside main's live-join keyframe wait (AE#844). Co-authored-by: Claude <noreply@anthropic.com>
…ty/now-playing: superuser404notfound#808 review items 1-4, software-host Atmos activator and delivery reporting
… doc comment, apac first-buffer comment, changelog entries for the route follow-ups
|
Merged into main as Main had moved again since your last merge, so I resolved four conflicts locally:
One follow-up commit ( Checked: |
…t, APAC first-buffer comment, changelog for the route follow-ups Folds a168689 and 5d75e12 from the #808 branch, pushed after it landed: a truth table for softwareAtmosOutputPlaying, the first-buffer budget comment counting the encoder's 2048 priming frames beside the 2048-frame chunk (about 4096 frames, some 100 TrueHD access units, as the changelog already says), and changelog entries for the channel-count follow and the interruption play hold, which landed with #856 without one. 3cb9dee (drop the LibTrueHD pin from Package.resolved) is left out: main depends on LibTrueHD 3.1.0 since the object renderer, so the pin is real now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
Summary
Follow-up to #808, stacked on it. This branch is #808's
parity/now-playing(fbb66b3). On top of that, it brings the branch up to date withmain(7.45.0, then 7.46.0), with the conflicts resolved the way the review suggested. It also addresses the review's four "Before merge" items and adds fixes found while running the branch on an Apple TV with a Dolby Vision Profile 7 FEL title that has a TrueHD Atmos track.Refs #808, #838, #848, #462.
Reviewing this alongside #808
mainalso includes feat(software-host): parity with the AVPlayer path: Now-Playing and remote commands, caption style, local scrub stills, Atmos, audio route and interruption handling #808's own changes (42 of the 46 files). The follow-up is these commits; review them one at a time, or compare againstparity/now-playing:6e88cb80review items 1–4a1e7fb29HDMI port type (main has the same fix since 814fef1; after the merge this adds nothing)bf48b1c0Atmos activator on the software host1d1b2f5bdelivery reporting6bd9b896activator interruption / detach527e2c13activator andsetRate(0)1912cd3dCHANGELOG4628c0fdmerge of main (cc29b7c): the software host takes main's TrueHD-only activator rule6ddadc16merge of main (814fef1 and later): takes main's HDMIOutput fix and its CHANGELOGparity/now-playingis an ancestor of this branch, so @pkajaba can fast-forward it toPieterVO:pr/808-followupsand this PR can be closed.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. DTS:X rendered to APAC never runs it, matching main's rule from cc29b7c (SoftwareAtmosRoute.carriesDolbyAtmos, tested). 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. Main now fixes this itself (814fef1). After merging main this branch has main's version, and the entry is gone from this PR's CHANGELOG.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 after merging main: 5233 Swift Testing tests and 785 XCTest cases. In the full parallel run, two local-server tests failed (HLSOriginRelayTests,EngineTLSHandshakeTests); both pass when re-run on their own. Earlier full runs on this branch showed the same kind of load-only failures inHLSOriginRelay*,EngineTLSHandshake,ThreadCPUSampler,SubtitlePacketStoreScaling,PythonOriginLaunchandHLSPlaylistHostileInput, all passing alone.xcodebuild -scheme AetherEngine -destination 'generic/platform=tvOS'builds.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.