Skip to content

feat(sender): streaming audio controls — volume key relay and local auto-mute#138

Open
lionheo9 wants to merge 1 commit into
swellweb:3.2-devfrom
lionheo9:feat/audio-streaming-controls
Open

feat(sender): streaming audio controls — volume key relay and local auto-mute#138
lionheo9 wants to merge 1 commit into
swellweb:3.2-devfrom
lionheo9:feat/audio-streaming-controls

Conversation

@lionheo9

@lionheo9 lionheo9 commented Jul 15, 2026

Copy link
Copy Markdown

Summary

ScreenCaptureKit captures system audio upstream of the output device, so streamed audio always doubled onto the sender's local speakers, and once the sender was muted by hand the hardware volume keys only flashed the local mute bezel — receiver volume was adjustable solely from the app's slider. This PR makes streaming audio behave like a real output device:

  • Local auto-mute. The default output mutes when a session starts streaming with audio and restores when the last one stops (claim-counted across sessions). Only a mute the app applied itself is undone — a pre-existing manual mute, or a deliberate unmute mid-stream, is respected.
  • Volume keys drive the receiver. A consuming NX_SYSDEFINED event tap redirects sound-up/down/mute to the connected sessions' stream volume — 1/16 steps, 1/64 with shift+option, mute toggles and restores the previous level — which the receiver applies to its system volume with its own HUD. Events pass through untouched whenever nothing is streaming audio, so normal local volume behavior is unaffected.

Implementation notes

  • The tap needs Accessibility trust: it is created lazily, retried when the app becomes active (covers granting permission in System Settings and switching back), and a settings hint appears while trust is missing. Manually enabling the toggle triggers the system prompt.
  • New settings toggle "Volume keys control the receiver while streaming" (default on, persisted, localized en/it/de/zh).
  • Auto-mute uses CoreAudio (kAudioDevicePropertyMute on the default output); devices without a settable mute control are left alone.

Testing

  • xcodebuild test (TBDisplaySender scheme): 68/68 pass.
  • Mute coordinator exercised against real hardware: unmuted → claim → muted → release → restored.
  • Verified end-to-end streaming to a 5K iMac receiver: sender mutes on stream start and restores on stop; volume keys step the receiver's volume with its HUD while local keys behave normally when not streaming.

Generated with Claude Code

Cheeky claude adding the watermark.

…uto-mute

ScreenCaptureKit captures system audio upstream of the output device, so
streamed audio doubled onto the local speakers, and once muted by hand the
hardware volume keys only flashed the local mute bezel — receiver volume was
adjustable solely from the app.

Two changes make streaming audio behave like a real output device:

- The local default output mutes automatically while a session streams with
  audio and restores when the last one stops. Only a mute the app applied
  itself is undone; a pre-existing mute or a manual unmute mid-stream stands.
- A consuming NX_SYSDEFINED event tap redirects sound-up/down/mute to the
  connected sessions' stream volume (1/16 steps, 1/64 with shift+option,
  mute toggles and restores), which the receiver applies to its own system
  volume with its HUD. Keys pass through untouched when nothing streams
  audio or the settings toggle is off. Requires Accessibility trust; the
  settings pane shows a hint while it is missing.

Co-Authored-By: Claude Fable 5 <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