Skip to content

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

Closed
pkajaba wants to merge 67 commits into
superuser404notfound:mainfrom
pkajaba:parity/now-playing
Closed

pkajaba wants to merge 67 commits into
superuser404notfound:mainfrom
pkajaba:parity/now-playing

Conversation

@pkajaba

@pkajaba pkajaba commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

The software-host parity work from #702 (the "parity: subtitles, PiP, scrub thumbnails" thread). The software host (the Dolby Vision FEL route and video the hardware decoder cannot play) has no AVPlayer, so several things the AVPlayer path gives for free were missing. Nine pieces, on one branch. It also includes the changes from #856 by @PieterVO (review items 1 to 4 and the fixes from his Apple TV run): commits 6e88cb8, a1e7fb2, bf48b1c, 1d1b2f5, 6bd9b89, 1912cd3 and 527e2c1, plus his merges of main (51b4137, 1f287d6, 4628c0f, 6ddadc1). They are merged in, not rebased, so his authorship is kept.

What's in it

  • Now-Playing card and remote commands, opt-in.

    • ownsSoftwareNowPlaying, off by default and independent of ownsVideoNowPlayingSession, so the AVPlayer paths are unchanged.
    • With it on, the engine publishes title, artwork, duration, elapsed and rate to MPNowPlayingInfoCenter.default() and registers play, pause, toggle, skip (remoteSkipInterval, default 10 s) and change-position on MPRemoteCommandCenter.shared(). It republishes on seek, pause, speed change, stall and end.
    • softwareNowPlayingActive tells a host when to add its own next and previous.
    • The centers are process-wide, so at stop the engine removes only its own targets, restores each command's isEnabled unless another owner changed it, and clears the info only while the center still holds what it last published.
  • System caption style on the software host. Subtitles composited into software-path PiP frames use the user's Settings > Accessibility > Subtitles & Captioning choices (font, size, text colour and opacity, background and window colour and opacity, edge style), and re-render when the setting changes. A field counts as chosen only where it differs from the system default. SubtitleCaptionStyle.current() and observeChanges(_:) expose the same style to a host that draws its own overlay. Bitmap subtitles are untouched.

  • Scrub thumbnails on a software session that plays a local file. scrubThumbnail and scrubPreviewFrame answered nil there (supportsCacheBackedStills was false), so a FEL title had no scrub stills unless the host built its own FrameExtractor. The engine now decodes them from the file (the base layer for a dual-layer title), through the same calls the AVPlayer path answers, with scrubPreviewFrame honouring isCancelled and the 750 ms / 900 packet bound. A Dolby Vision Profile 5 / 10.0 file without the DolbyVisionStillReshaping trait keeps supportsCacheBackedStills false (review item 4). Remote software sessions already served stills from the packet cache.

  • Atmos on the software host. The host decoded every track to PCM, so a JOC stream played as its 5.1 core and TrueHD Atmos or DTS:X as its 7.1 bed. With LoadOptions.objectAudioRendering set (the engine's existing option for object-audio delivery), E-AC-3 JOC is handed to the audio renderer compressed, after the system's Atmos decoder is registered once per process, and TrueHD Atmos and DTS:X are rendered into the chosen bed and encoded as APAC. .off (the default), live sessions and every other codec keep the PCM decode. The automatic audio pick prefers a TrueHD Atmos or DTS:X track on the software host too. Two fixes came with it:

    • the APAC format description handed to the renderer had no cookie and no packet size, so the renderer failed every packet and played silence; it now carries both
    • the E-AC-3 look at the stream is made only inside the demuxer's stream guard, so a stop that closes the demuxer cannot free the codec parameters mid-read

    From the 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 review (items 1 to 4) and the Apple TV run in fix(software-host): #808 review follow-ups (stacked on #808) #856:

    • DTS:X is rendered only into 7.1.4 or 9.1.6 (main's one-to-one rule); a TrueHD or DTS track kept on PCM says why in the log; a TrueHD track whose profile the probe missed is read from its own major sync, as on the loopback route (fix(audio): read TrueHD Atmos from the major sync when the probe never reached the track (7.40.1) #838)
    • a packet read before a seek can no longer anchor the APAC bridge at the old position: the producer drops it under the same lock as the seek's flush (review item 3)
    • the clock's "audio is not recovering" fallback waits for the APAC route's first buffer instead of anchoring every TrueHD load on video after 50 packets
    • on tvOS the Atmos output activator runs beside the software host while its renderer gets Dolby Atmos (TrueHD Atmos as APAC, compressed E-AC-3 JOC; not DTS:X), follows pause (riding through a short pause for the 60 s grace from HDMI Atmos badge drops on pause/resume — follow-up to #834 #848), setRate(0) and interruption resumes, and detaches with the session
    • audioDelivery reports .bridged (APAC) and .streamCopy (compressed JOC) on the software host, activeAudioDecoder names the route ("TRUEHD Atmos → APAC 7.1.4"), and background audio counts both as audio
  • Pause when the output route is removed. On iOS and tvOS the renderer paths (software host and audio host) now pause when the active output goes away (headphones unplugged, a wireless output lost). The decision is platform-neutral and unit-tested; it fires only while playing or seeking. It pauses for headphones, Bluetooth (A2DP, LE, HFP), AirPlay and USB outputs only. HDMI is left out, so an ARC / eARC receiver switching on or off on an Apple TV keeps playing (review item 2). The AVPlayer path already does this itself and is untouched.

  • Follow a change of the output channel count. When the route changes (say a 7.1 receiver to 2.0 speakers), the engine re-evaluates the preferred output channel count against the source and the new maximum, and writes it only when it differs, on the shared audio-session queue, so a repeated notification ends at the first no-op.

  • play() during an audio-session interruption. On the renderer paths a play press during an interruption is held instead of starting a stalled clock; the session is reactivated and the held play runs when the interruption ends. A second press while one is held reactivates and plays at once, and a pause, a stop or a reload cancels the held play. Without a renderer path nothing is held.

  • FEL yields to an AirPlay video receiver, opt-in. On iOS, an AirPlay output holds the audio route and the system reports every AirPlay output as the same port type, so the engine cannot tell an Apple TV from a HomePod. The host says it with setAirPlayReceiverShowsVideo(_:) (default false, so nothing changes until a host calls it): with it on and a wireless AirPlay route active, a Profile 7 title keeps the native route (8.1 where the display takes Dolby Vision; the enhancement layer is lost), because a video receiver takes a stream, not a composed picture. An audio-only output such as a HomePod keeps FEL. The decision is the new DualLayerRouteDecision.Reason.airPlayVideoReceiver, logged with the rest of the route line, and is read once per load (a URL source decides again on reloadAtCurrentPosition(); a custom source keeps its route until the next load()).

    • setScreenMirrored(_:) (iOS) lets a host say the screen is mirrored, so airPlayPictureStaysLocal reads false while it is on: the picture goes to the receiver too.
  • Software play waits for the display mode switch (tvOS). The software host now goes through the same display-criteria gate the AVPlayer path uses before it starts the clock, so a rate-only or HDR mode switch finishes before the first frame instead of after it. The gate does nothing when the display criteria did not change or the load is a secondary output (nobody writes criteria for it), and it has no effect off tvOS.

  • docs/api.md, docs/architecture.md, docs/formats.md, docs/dolby-vision-fel.md and the README row describe the contracts. CHANGELOG entries are under Unreleased.

How it was tested

The branch is merged with the current origin/main (20a2272, including #848, #854 and #855), and with the commits from #856 listed above.

Tested on Mac

On the merged tip (7e8b09e):

  • The suites this branch touches pass (131 tests in 15 suites): Now-Playing, remote commands, the AirPlay route decision, the display-mode play gate, caption style, scrub stills, the Atmos routes, the audio delivery reporting, route removal, channel count, interruption, the APAC format description, the activator (including the pause grace) and the panel HDR proof from fix(display): an accepted HDR master proves the panel for every engine in the process #855. With the TrueHDObjectAudio, DTSXObjectAudio, DualLayerDolbyVision and DolbyVisionStillReshaping traits on, the same suites plus the spatial bridge (including the stale packet) pass (156 tests in 17 suites).
  • Generic tvOS and iOS xcodebuild build-for-testing pass. The tvOS build is the only compile check of the activator's software-host branch.
  • The full suite ran before the last merge of main, on c90d499: swift test 4950 Swift Testing tests in 685 suites and 800 XCTest cases (783 + 17 in the SMB target, 1 skipped), all passing; the strict-pool run (LIBDISPATCH_COOPERATIVE_POOL_STRICT=1) passed except the known wall-clock test VideoPrerollTests "holds the clock past the plain timeout", which passes alone. It was not re-run after merging main 20a2272, which changed the panel HDR proof in the display criteria, the demuxer's PGS probe parking and a preroll test (9 files). Only the panel HDR proof suite of those was run on the merged tip (above); main's own tests for the PGS probe parking and the audio probe come with the merge and were not re-run here.
  • New suites: SoftwareNowPlayingTests, SubtitleCaptionStyleTests, SoftwareLocalScrubStillTests, SoftwareAtmosRouteTests, SoftwareAPACFormatDescriptionTests, RouteRemovalPauseTests, ChannelCountChangeTests, InterruptionPlayGuardTests and AirPlayParityTests (the host-signal and mirror truth tables, the setter defaults, the software play-gate grace, and with the trait on the video-receiver route decision, including that an audio-only output keeps FEL), plus additions to FrameDecodeContextClampTests and ItemSwapStillTests.
  • New in this round: RouteRemovalPauseTests.hdmiDoesNotPause and portTypesMatchTheSession, SoftwareAtmosRouteTests.dtsxNeedsAOneToOneBed and dolbyAtmosRoutes, SoftwareLocalScrubStillTests.profile5FollowsTheStillVerdict, SpatialAudioBridgeTests.stalePacketDoesNotAnchorThePostSeekRun, AudioDeliveryClassificationTests.atmosRoutesReportTheirDelivery, the activator's transport truth table in Issue834AtmosActivatorRouteTests, and the renderer path's pause grace in Issue848AtmosActivatorPauseGraceTests.
  • Atmos APAC timing was measured on macOS: the audio renderer plays the APAC output with no decode errors, and with the packets stamped at the PTS minus the 2048 priming frames the APAC lines up with the PCM decode at 0.00 ms (unshifted it is +42.69 ms). The compressed E-AC-3 JOC route has the unit tests above but no playback measurement.
  • Load flakes seen on a busy machine (SoftwarePacketMemoryFIFOTests, SoftwarePacketCacheStorageTests, the refusal tests, DoviFelFuzzTests, VideoPrerollTests, and once the wall-clock Issue848AtmosActivatorPauseGraceTests "a pause keeps the stream playing until the grace runs out") are wall-clock or free-disk based and pass when re-run alone.

Tested on Apple TV (by @PieterVO, #856)

Apple TV 4K (1st generation), tvOS 26.6, HDMI to a Dolby Vision TV through an Atmos receiver: a Profile 7 FEL title with TrueHD Atmos takes the dual-layer route with TrueHD rendered to APAC 7.1.4; the TV shows Dolby Vision and the receiver Atmos; over three sessions 0 lost frames, 2 to 39 display-layer drops per session (mostly at start), no rebuffers, the clock armed from audio. A Profile 7 MEL title on the loopback route: the activator is active on HDMI.

Still needs Apple TV

This stays a draft until these have run on a device:

  • Atmos on tvOS: compressed E-AC-3 JOC on the software host, DTS:X as APAC, and the APAC priming offset (TrueHD Atmos as APAC is done, above).
  • The receiver's Atmos indicator during the tvOS display-mode switch wait: the activator starts while the load is still .loading. On the software path the state may read .paused before play when the load reaches readiness during the wait (the same .paused as the play-gate item below), so the activator can play, hold through the 60 s pause grace, and play again. Check that the receiver flips to Atmos once and stays there.
  • Now-Playing card and remote commands: the card contents, play, pause, skip and change-position from the remote, and that stop leaves another owner's targets alone.
  • System caption style in software-path PiP frames, and a change of the setting while playing.
  • Scrub stills on a local software session: the 750 ms / 900 packet bound on device storage.
  • Route removal (AOS5): unplug or lose the active output during playback on the software host and the audio host; playback pauses and does not resume by itself. Headphones or Bluetooth removal pauses; an HDMI receiver toggle does not.
  • Channel-count change (AOS6): play a 7.1 title to a 7.1 receiver, then switch to 2.0 speakers; the preferred channel count follows, with a downmix and not silence, and any extra gap is short. One write per real change, none on repeats.
  • Interruption (Siri, an alarm): press play while interrupted; the session reactivates, the clock runs and playback starts when the interruption ends. A pause press cancels it.
  • AirPlay video receiver (iOS): with setAirPlayReceiverShowsVideo(true) and an Apple TV picked as the AirPlay output, a Profile 7 title loads on the native route (the log line names airPlayVideoReceiver); with a HomePod as the output the same title keeps FEL. The setter is inert until a host calls it.
  • Software play gate (tvOS): on a real rate-only switch and on an HDR switch, the first frame waits for the mode switch and the picture does not flash. The state shown while it waits (.paused) is a caveat to look at.

Follow-up (not in this PR)

Open items from the review, to be taken separately:

  • the channel preference against the 12 or 16 channels fed to the renderer
  • a fallback when an Atmos route stays silent
  • LiveTelemetry.objectAudio is nil on the software host
  • the interruption hold when a begin never gets its end
  • one sentence in docs/api.md on host Now-Playing targets beside softwareNowPlayingActive
  • local scrub stills: no keyframe fallback at the 750 ms bound, and network mounts (volumeIsLocalKey)
  • caption text does not wrap at large sizes

When spatial rendering is unavailable, the software host falls back to the PCM decode, so asking for Atmos never costs a session its audio. audioDelivery and activeAudioDecoder now say which route a session took (.bridged, .streamCopy, .decoded), but the reason for a fallback is still only a log line. It happens when:

  • objectAudioRendering is .off, or the session is live
  • the E-AC-3 track is not confirmed JOC, or is not 6 channels at 48 kHz in 1536-sample packets, or the dec3 box cannot be built from the first packets, or the passthrough producer cannot be created
  • for TrueHD Atmos and DTS:X: the track declares a rate other than 48 kHz (spatialRenderingLayout returns nil), the OS is older than 26 or has no APAC encoder, the build lacks the TrueHDObjectAudio or DTSXObjectAudio trait, a DTS-HD MA track is not DTS:X, or the object decoder or the APAC bridge fails to build

In those cases JOC plays as its 5.1 core and TrueHD Atmos or DTS:X as its 7.1 bed, as before this PR. The follow-up is to publish why a session fell back so a host can show it, and to decide whether any of these cases should do something better than PCM.

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.
@pkajaba pkajaba changed the title feat(software-host): Now-Playing card and remote commands, opt-in feat(software-host): parity with the AVPlayer path: Now-Playing and remote commands, caption style, local scrub stills, Atmos Oct 16, 2026
# Conflicts:
#	README.md
#	Sources/AetherEngine/Audio/Spatial/ObjectAudioRendering.swift
#	docs/api.md
#	docs/formats.md
PieterVO and others added 8 commits October 22, 2026 23:12
…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 13 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>
…rts an airplay video receiver and wait for a display mode switch before playing

A Profile 7 FEL source loaded on the dual-layer route draws into a local layer that no AirPlay
receiver picks up, so a TV the viewer sent the title to played sound only. When the host reports
that the receiver shows video (setAirPlayReceiverShowsVideo, default off) the load takes the native
route instead; an audio-only AirPlay output keeps the dual-layer route. The audio session reports
every AirPlay output as the same port type, so the engine cannot tell them apart itself. The route
is read once per load.

Also: airPlayPictureStaysLocal reads false while the host reports the screen as mirrored
(setScreenMirrored), and the software host waits for a running display mode switch before it
starts playing, as the native path does (tvOS).
# Conflicts:
#	CHANGELOG.md
#	README.md
#	Sources/AetherEngine/AetherEngine.swift
#	Sources/aetherctl/PlaybackCmd.swift
#	Sources/aetherctl/main.swift
#	docs/api.md
#	docs/architecture.md
#	docs/formats.md
…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
…4notfound#854) into parity/now-playing-856-fold

Two conflicts. AtmosOutputActivator.swift: the type doc comment keeps both the software-host paragraph and the superuser404notfound#848 pause-grace paragraph; update() has one shared 'guard playing else { holdThroughPause(); return }' after the route check, covering the AVPlayer branch (playing = timeControlStatus != .paused) and the renderer branch (playing from the host transport), so the software host gets the same 60 s pause grace; pauseAfterGrace is cancelled before player.play(), release() cancels it, detach() reaches release() through follow(nil). Issue834AtmosActivatorRouteTests.swift: kept both the fold's softwareTransportTruthTable and main's portTypeIsHDMIOutput (superuser404notfound#854) pin.
superuser404notfound pushed a commit that referenced this pull request Oct 25, 2026
…tyle, local scrub stills, Atmos, route removal, channel count, interruption hold
superuser404notfound pushed a commit that referenced this pull request Oct 25, 2026
… activator and delivery reporting

Conflicts with main resolved: AtmosOutputActivator keeps #848's pause grace on the new renderer path,
stopInternal keeps endClockHold(), formats.md and CHANGELOG take main's text with the PR's entries.
@superuser404notfound

Copy link
Copy Markdown
Owner

This landed on main together with #856: 1ed65a90 merges this branch at 82805511 (including the AirPlay video-receiver gate), 96381521 merges PieterVO's follow-ups on top. Thank you, Pavel, for this big parity push; the stop-path care made it easy to land. Closing the draft since every commit is now in main. The device checks from the original list are still welcome as issues or comments here.

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
superuser404notfound pushed a commit that referenced this pull request Oct 26, 2026
… (thanks @pkajaba)

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

Copy link
Copy Markdown
Owner

The four commits pushed after the merge are handled in d2cb2b98 on main:

  • c90d499e (renderer-path pause grace test) is cherry-picked as is.
  • a168689a and 5d75e124 are folded into one commit under your name: the softwareAtmosOutputPlaying truth table, your first-buffer comment (the 2048 priming frames plus the 2048-frame chunk, which matches the APAC encoder notes better than the wording main had), and the changelog entries for the channel-count follow and the interruption play hold. The blank line in the activator's doc comment was already gone on main.
  • 3cb9dee1 is left out on purpose: main now depends on LibTrueHD 3.1.0 for the object renderer, so that Package.resolved pin is real.

Thanks again, Pavel.

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