Skip to content

fix(audio): route follows-default apps when highest-priority output reconnects#314

Open
clem-git wants to merge 1 commit into
ronitsingh10:mainfrom
clem-git:fix/bt-reconnect-output-routing
Open

fix(audio): route follows-default apps when highest-priority output reconnects#314
clem-git wants to merge 1 commit into
ronitsingh10:mainfrom
clem-git:fix/bt-reconnect-output-routing

Conversation

@clem-git

@clem-git clem-git commented May 26, 2026

Copy link
Copy Markdown

Problem

Addresses #255. On a Bluetooth output reconnect, the system default and the UI correctly move to the reconnected device, but per-app audio (follows-default apps, e.g. Spotify) keeps playing on the previous device (typically the built-in speakers). The user has to manually toggle the output in FineTune to make audio actually follow.

Reproduced on macOS 26.4.1 with FineTune 1.7.0:

  • Priority list had a disconnected device as the top priority and the connected headset ranked just below it.
  • macOS reported the headset as the default output, FineTune's UI showed it as active, Spotify's level meter was live — yet audio came from the laptop speakers.

Root cause

In handleDeviceConnected, macOS natively auto-switches the system default to a newly connected BT device within milliseconds. By the time the handler runs, deviceUID == currentDefault, so the existing branching matched neither case:

if isNewDeviceHigherPriority, deviceUID != currentDefault {   // false: already default
    reEvaluateOutputDefault()
} else if !isNewDeviceHigherPriority, currentDefault == deviceUID {  // false: it IS higher priority
    restoreConfirmedDefault()
}

Result: lastConfirmedDefaultUID stays stale on the old device and routeFollowsDefaultApps is never called, so the app taps are never re-pointed. A subsequent handleDefaultDeviceChanged (Case 1) then treats macOS's switch as something to undo and routes follows-default apps back to the stale device — stranding audio on the old output while the system default sits on the new one.

Fix

Extract the decision into a pure, testable connectedOutputDefaultAction(...) and add the missing .confirmConnected case: when the connected device is the highest-priority connected device and macOS already made it the default, confirm it (lastConfirmedDefaultUID) and route follows-default apps so their taps follow.

The existing .switchToConnected (higher-priority reconnect, not yet default) and .restorePrevious (lower-priority device macOS hijacked to) behaviours are preserved exactly — lower-priority-hijack protection is unchanged.

Testing

  • New OutputDeviceReconnectRoutingTests covers the reconnect desync (the previously-unhandled case) and the preserved protection behaviour, including the "disconnected top-priority device → highest connected device still wins" scenario.
  • Full FineTuneTests target passes.
  • Manually verified on a local ad-hoc build: with the patch, audio follows to the reconnected headset automatically; without it, it stranded on the speakers until a manual toggle.

🤖 Generated with Claude Code

…econnects

When a Bluetooth output device reconnects, macOS auto-switches the system
default to it within milliseconds. By the time handleDeviceConnected runs,
the connected device already equals the current default, so the previous
branching matched neither case — it neither re-evaluated the default nor
restored the prior one. lastConfirmedDefaultUID stayed stale on the old
device and the follows-default app taps were never re-pointed; a subsequent
handleDefaultDeviceChanged then reverted routing to the stale device,
leaving the system default on the newly connected device while app audio
kept playing on the previous one (e.g. built-in speakers).

Resolve the connect decision through a pure connectedOutputDefaultAction(...):
when the connected device is the highest-priority *connected* device, ensure
it is the default and re-route follows-default apps via reEvaluateOutputDefault
(which sets the default only when it differs and always re-points the taps).
Lower-priority-hijack protection (.restorePrevious) is unchanged.

Add OutputDeviceReconnectRoutingTests covering the reconnect desync, the
disconnected-ronitsingh10#1 priority resolution (WH-1000XM5 off / Buds4 on), and the
preserved protection behaviour. Full FineTuneTests target passes.

Addresses ronitsingh10#255

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@clem-git clem-git force-pushed the fix/bt-reconnect-output-routing branch from e4a60b1 to 18204ce Compare May 26, 2026 09:59
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