Skip to content

fix: detect Bluetooth/DJI mics connected mid-session (no app restart) - #35

Merged
joeblau merged 1 commit into
mainfrom
fix/bt-audio-route-refresh
Jul 6, 2026
Merged

fix: detect Bluetooth/DJI mics connected mid-session (no app restart)#35
joeblau merged 1 commit into
mainfrom
fix/bt-audio-route-refresh

Conversation

@joeblau

@joeblau joeblau commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Problem

Audio input enumeration was pull-only — it ran on the Settings view's onAppear and the manual "Refresh Inputs" button, and nowhere else. Nothing observed AVAudioSession.routeChangeNotification, so a Bluetooth headset (or DJI mic) connected while the app was already running never re-enumerated. The only way to pick it up was quitting and relaunching (which re-runs onAppear).

Fix

  • AudioInputProvider now registers a single AVAudioSession.routeChangeNotification observer (idempotently, from refresh()) and re-enumerates on .newDeviceAvailable / .oldDeviceUnavailable. It deliberately ignores .categoryChange / .override, which we provoke ourselves by setting the category and preferred input — reacting to those would loop. Torn down in a nonisolated deinit.
  • SettingsView wires the new onInputsChanged callback to re-apply the persisted preferred input to the session and restart the mic-level meter on the new route, so a headset plugged in now goes live within a second. Skipped while broadcasting (the ScreenCaptureKit extension owns the route).

Scope

2 files, +64 lines — Stream/AudioInputProvider.swift, Stream/SettingsView.swift. App-target only; no StreamCore changes.

🤖 Generated with Claude Code

Input enumeration was pull-only — it ran on the Settings view's onAppear
and the manual "Refresh Inputs" button, and nowhere else. Nothing observed
AVAudioSession.routeChangeNotification, so a Bluetooth headset connected
while the app was already running never re-enumerated; the only way to pick
it up was relaunching (which re-runs onAppear).

AudioInputProvider now registers a single routeChangeNotification observer
(idempotently, from refresh()) and re-enumerates on .newDeviceAvailable /
.oldDeviceUnavailable. It deliberately ignores .categoryChange and .override,
which we provoke ourselves by setting the category and preferred input —
reacting to those would loop. The observer is torn down in a nonisolated
deinit.

SettingsView wires the new onInputsChanged callback to re-apply the persisted
preferred input to the session and restart the mic-level meter on the new
route, so a headset plugged in now goes live within a second. Skipped while
broadcasting, where the ScreenCaptureKit extension owns the route.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@joeblau
joeblau merged commit 451b31f into main Jul 6, 2026
1 check passed
@joeblau
joeblau deleted the fix/bt-audio-route-refresh branch July 6, 2026 01:31
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