Skip to content

fix(software-host): #808 review follow-ups (stacked on #808) - #856

Merged
superuser404notfound merged 56 commits into
superuser404notfound:mainfrom
PieterVO:pr/808-followups
Oct 25, 2026
Merged

superuser404notfound merged 56 commits into
superuser404notfound:mainfrom
PieterVO:pr/808-followups

Conversation

@PieterVO

@PieterVO PieterVO commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

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 with main (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

What changed

Merge with main. stopInternal keeps both stopSoftwareNowPlaying() and softwareClockSessionZero = nil; aetherctl keeps main's --atmos-bed parser (the branch's duplicate parser and global are gone); CHANGELOG / README / docs/api.md / docs/formats.md take main's text with the software-host sentences added.

Review items 1–4

  1. SoftwareAtmosRouteTests.apacRoutes takes .l714 for 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.
  2. Route removal pauses only for headphones, Bluetooth (A2DP, LE, HFP), AirPlay and USB. HDMI is left out, so an ARC / eARC receiver switching on or off keeps playing.
  3. A packet read before a seek can no longer anchor the APAC bridge: SoftwareAudioProducing gains the AE#492-style feed epoch, the demux loop reads it before the read, and SoftwareAPACProducer checks it under the same lock as flush()'s startSegment. Covered by a test that failed before the change (post-seek audio stamped 59 s early).
  4. A local software session reports supportsCacheBackedStills == false for Dolby Vision Profile 5 / 10.0 without the DolbyVisionStillReshaping trait.

Found on device

  • TrueHD on the APAC route started on the video clock. The "audio is not recovering" fallback fired after 50 audio packets, but the APAC route's first buffer is about 100 TrueHD access units in. The bound now comes from the producer (the bridge's own dead-decoder threshold for APAC).
  • Unlabelled TrueHD. The software host now resolves a TrueHD track whose profile the probe missed from its major sync, sharing main's fix(audio): read TrueHD Atmos from the major sync when the probe never reached the track (7.40.1) #838 helper with the loopback route.
  • Atmos indicator on the software host. The Atmos output activator now also follows a software host whose renderer receives Dolby Atmos: TrueHD Atmos as APAC, or compressed E-AC-3 JOC. It tracks resumes after an interruption and 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.
  • HDMI port type. AVAudioSession.Port.HDMI is "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.
  • Reporting. On the software host, audioDelivery is .bridged for APAC and .streamCopy for compressed E-AC-3 JOC (the loopback route's terms), and activeAudioDecoder names 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

  • macOS: swift test --enable-all-traits on 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 in HLSOriginRelay*, EngineTLSHandshake, ThreadCPUSampler, SubtitlePacketStoreScaling, PythonOriginLaunch and HLSPlaylistHostileInput, all passing alone. xcodebuild -scheme AetherEngine -destination 'generic/platform=tvOS' builds.
  • Apple TV 4K (1st generation), tvOS 26.6, Release build of a host app against this branch with all four traits, HDMI to a Dolby Vision TV through an Atmos receiver:
    • UHD Blu-ray remux, Dolby Vision Profile 7 FEL, TrueHD Atmos: the dual-layer route is taken and TrueHD Atmos is rendered to APAC 7.1.4 on the software host. The TV shows Dolby Vision and the receiver shows Atmos. Over three sessions of several minutes: 0 lost frames, 2–39 display-layer drops per session (mostly at start), no rebuffers, audio lead steady around 4 s, the clock armed from audio.
    • UHD Blu-ray remux, Profile 7 MEL, TrueHD Atmos (loopback route, APAC): the activator logs active on route ports=[HDMIOutput]; before the HDMI fix it logged off.
  • Not run on a device: route removal, channel-count change, interruption hold, Now Playing, caption style, local scrub stills, DTS:X. The device list in 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 is still open for these.

Checklist

  • CHANGELOG updated
  • Conventional Commit messages
  • Engine-side fix
  • Public API behaviour change documented (audioDelivery values on the software host)

Co-authored with Claude (Anthropic); the commits carry a Co-authored-by: Claude trailer.

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.
…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.
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:
#	README.md
#	Sources/AetherEngine/Audio/Spatial/ObjectAudioRendering.swift
#	docs/api.md
#	docs/formats.md
pkajaba and others added 20 commits October 17, 2026 12:37
# Conflicts:
#	CHANGELOG.md
#	docs/formats.md
… 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>
PieterVO and others added 2 commits October 23, 2026 09:58
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>
pkajaba added a commit to pkajaba/AetherEngine that referenced this pull request Oct 25, 2026
…ty/now-playing: superuser404notfound#808 review items 1-4, software-host Atmos activator and delivery reporting
pkajaba added a commit to pkajaba/AetherEngine that referenced this pull request Oct 25, 2026
… doc comment, apac first-buffer comment, changelog entries for the route follow-ups
@superuser404notfound
superuser404notfound merged commit 9638152 into superuser404notfound:main Oct 25, 2026
@superuser404notfound

Copy link
Copy Markdown
Owner

Merged into main as 96381521 (with #808 as 1ed65a90 right before it), thanks a lot, Pieter. Running the branch on a real FEL + TrueHD Atmos setup and bringing the Atmos indicator, the first-buffer budget and the epoch race back with tests is exactly what #808 needed to land.

Main had moved again since your last merge, so I resolved four conflicts locally:

One follow-up commit (b8305476): the firstBufferPacketBudget comment still named the bed splitter, which #731 removed.

Checked: swift test --enable-all-traits on the merged tree, 5276 Swift Testing tests and 801 XCTest cases green; tvOS build green. It ships with the next release. The device list from #808 (route removal, channel-count change, interruption hold, Now Playing, caption style, local scrub stills, DTS:X) is still open, and reports from either of you on those are very welcome.

@PieterVO PieterVO mentioned this pull request Oct 25, 2026
1 task done
superuser404notfound pushed a commit that referenced this pull request Oct 26, 2026
…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
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.

3 participants