Skip to content

feat(audio): let the host veto a scheduled audio-session release - #13

Open
Quick104 wants to merge 3 commits into
fix/direct-play-range-authfrom
fix/audio-session-release-gate
Open

Quick104 wants to merge 3 commits into
fix/direct-play-range-authfrom
fix/audio-session-release-gate

Conversation

@Quick104

@Quick104 Quick104 commented Oct 5, 2026 •

Copy link
Copy Markdown

Summary

A final stop() with deactivatesAudioSessionOnStop schedules setActive(false) off the main actor, and on an Atmos passthrough route that call alone takes about half a second. Only a load() on the same engine cancels it. A host that closes one player and opens another engine straight away could have the old engine's late release deactivate the session the new player just took. This was reported but never reproduced, so this PR is a defensive guard: it lets the host veto the release.

Stacked on #12; review that first. Used by Silo-Server/silo-apple#611.

What changed

  • New public var audioSessionReleaseGate: (@Sendable () -> Bool)? on AetherEngine, next to deactivatesAudioSessionOnStop, default nil.
  • The engine reads the gate when stop() schedules the release. Inside the queued release it calls the gate off the main actor just before setActive(false), after the existing cancel and load-generation checks. Ordering behind a pending renderer activation is unchanged.
  • false skips the release and logs AVAudioSession release skipped: audioSessionReleaseGate returned false. With no gate set, the release runs as before.
  • docs/api.md (the "Now Playing and the audio session" table) and CHANGELOG.md.

What this does not close: the gate is checked when the queued release starts, a few milliseconds after stop(). A setActive(false) already in flight cannot be cancelled, so fully ordering releases across engine instances would need coordination inside the engine. Silo's host-side ownership check blocks a release only when a newer load has started.

Test plan

  • Device / OS: macOS 27 (swift test). Not run on a device or on an Atmos or AirPlay route. The iOS/tvOS release path is covered through its policy function, because swift test runs on macOS.
  • Source media: none.
  • Result: swift test passes in both CONTRIBUTING.md groups on d0aa7c2b, which merges both rounds of fix(avio): refresh direct-play credentials on every range request #12's review fixes: 3,395 Swift Testing tests in 458 suites and 636 XCTest tests (1 skipped), plus 65 authorization tests. New tests in AudioSessionTeardownPolicyTests: gate nil releases, false skips, true releases. The default-off test also checks that the gate is nil.

Evidence: https://evidence.siloserver.org/r/aetherengine/audio-session-release-gate/

Checklist

  • CHANGELOG.md updated
  • Commit messages follow Conventional Commits (feat(...), fix(...), chore(...))
  • The fix lives in the engine, not in a host-side workaround (the engine owns the release; the host only answers the gate)
  • Public API changes are intentional and documented (audioSessionReleaseGate)

AI disclosure

  • Harness: Claude Code (T3 Code)
  • Model: claude-opus-5-5
  • Involvement: AI-generated. A Claude Code subagent wrote the change and the tests, and ran the suite. Not yet reviewed by a person or verified on a device.

🤖 Generated with Claude Code

Note

Add audioSessionReleaseGate so hosts can veto a scheduled audio-session release

  • Adds an optional audioSessionReleaseGate closure property on AetherEngine in AetherEngine.swift. It is read when the release is scheduled and invoked off the main actor right before setActive(false).
  • scheduleAudioSessionDeactivation captures the gate at schedule time. After the existing cancellation and same-engine checks, a false result logs a skip and returns without deactivating.
  • Documents the option in docs/api.md and CHANGELOG.md, and covers default-nil, absent, allowing, and refusing gates in AudioSessionTeardownPolicyTests.swift.
  • Behavioral Change: default remains nil, so existing hosts see no change; setting a gate to false now skips the shared AVAudioSession deactivation and logs the refusal.

Macroscope summarized d0aa7c2.

A final stop() with deactivatesAudioSessionOnStop schedules setActive(false)
off the main actor, and that call alone takes about half a second on an
Atmos passthrough route. Only a load() on the same engine cancels it. A
host that tears one player down and opens another engine straight away
could therefore have the old engine's late release deactivate the session
the new player has just taken. This was reported but never reproduced, so
the change is a defensive guard.

Add audioSessionReleaseGate, an optional @sendable () -> Bool the engine
reads when stop() schedules the release and calls right before
setActive(false), after any renderer activation queued ahead of it. When
it returns false the release is skipped and logged. With no gate set the
release runs as before. Document it in docs/api.md and the changelog, and
cover nil, false and true in the teardown policy tests.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e09635da-8d29-41f5-983b-5f59928926ae

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

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

❤️ Share

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

Quick104 and others added 2 commits October 5, 2026 14:11
…ase-gate

Bring in the review fixes from #12: resolvers run off the cooperative
pool, backward detour reads latch authorization refusals, resolver
headers stay off cross-origin targets, and the failure docs separate
pre-send from post-401 failures.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ase-gate

Bring in the second round of #12 review fixes: detour reads honour a
latched authorization refusal, the detour tests no longer race the pump,
and the pool-parking resolver test runs in the main test group.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant