Skip to content

feat(voice): add requireHeadphones config to gate voice on audio output device#898

Closed
blu3dot wants to merge 1 commit intodanielmiessler:mainfrom
blu3dot:feat/voice-require-headphones
Closed

feat(voice): add requireHeadphones config to gate voice on audio output device#898
blu3dot wants to merge 1 commit intodanielmiessler:mainfrom
blu3dot:feat/voice-require-headphones

Conversation

@blu3dot
Copy link
Copy Markdown

@blu3dot blu3dot commented Mar 3, 2026

Summary

New voice.requireHeadphones setting that gates voice playback on the audio output device type. When enabled, voice is skipped through built-in laptop speakers but plays normally through headphones, AirPods, USB DACs, HDMI audio, and other external devices.

Problem

Users in shared environments (offices, cafes, meetings) have no way to prevent voice from blasting through laptop speakers. The only option is disabling voice entirely, losing the feature when headphones are connected.

Solution

Config (settings.json):

{
  "voice": {
    "requireHeadphones": false
  }
}

Default false — opt-in, non-breaking. Uses === true so missing key defaults to OFF.

Detection — queries system_profiler SPAudioDataType -json and checks the CoreAudio Transport type of the default output device:

Transport Device Type Voice Plays?
coreaudio_device_type_builtin MacBook speakers No
coreaudio_device_type_bluetooth AirPods, BT headphones Yes
coreaudio_device_type_usb USB DAC, dock audio Yes
coreaudio_device_type_hdmi External monitor Yes
coreaudio_device_type_airplay HomePod, Apple TV Yes
All other transport types External audio Yes

Key design decisions:

  • 30-second cachesystem_profiler takes 140-250ms; cache prevents repeated slow calls during burst scenarios (e.g., 10 notifications during a build)
  • 3-second timeout — prevents hangs if system_profiler stalls
  • Fail-open — if detection fails or times out, voice plays anyway (convenience feature, not security gate)
  • Desktop notifications always display regardless of headphone state
  • Uses -json flag for reliable machine-parseable output (no text scraping)

macOS-onlysystem_profiler is macOS-specific. Linux equivalent is out of scope (see #855, #872 for cross-platform audio work).

References #855

Test plan

  • Connect Bluetooth headphones → requireHeadphones: true → voice plays
  • Disconnect headphones (Built-in) → voice skipped, log shows 🔇
  • requireHeadphones: false (default) → voice plays regardless of output device
  • /health endpoint shows require_headphones field
  • Detection failure → voice plays (fail-open)
  • Config key absent → voice plays (opt-in default OFF)

Post-Deploy Monitoring & Validation

No additional operational monitoring required: opt-in feature defaulting to OFF, zero behavior change for existing users.


Compound Engineered 🤖 Generated with Claude Code

…ut device

New `voice.requireHeadphones` setting in settings.json (default: false).
When enabled, VoiceServer checks the default audio output device via
`system_profiler SPAudioDataType -json` and skips voice playback if the
output is built-in laptop speakers. Voice plays normally through
Bluetooth, USB, HDMI, AirPlay, and other external audio devices.

- Uses `-json` flag for reliable machine-parseable output
- Caches detection result for 30 seconds (system_profiler takes 140-250ms)
- 3-second timeout prevents hangs if system_profiler stalls
- Fails open — if detection fails, voice plays anyway (convenience, not security)
- Desktop notification banners display regardless of headphone state
- Config uses `=== true` (opt-in, missing key defaults to OFF)
- TODO: Linux equivalent (see danielmiessler#855, danielmiessler#872)

References danielmiessler#855

Co-Authored-By: Claude <noreply@anthropic.com>
@blu3dot
Copy link
Copy Markdown
Author

blu3dot commented Mar 3, 2026

Closing to test locally before resubmitting.

@blu3dot blu3dot closed this Mar 3, 2026
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