Skip to content

Fix: dictation cancels itself immediately when the input is a Bluetooth headset - #20

Open
katkrasner wants to merge 1 commit into
VasenevEA:mainfrom
katkrasner:fix-audio-device-change
Open

Fix: dictation cancels itself immediately when the input is a Bluetooth headset#20
katkrasner wants to merge 1 commit into
VasenevEA:mainfrom
katkrasner:fix-audio-device-change

Conversation

@katkrasner

Copy link
Copy Markdown

Problem

On a Mac whose default input is a Bluetooth headset, dictation cancels itself the instant it starts. The HUD never appears, nothing is transcribed, and there is no error — it just does nothing.

The cause is in Recorder.installConfigChangeObserver. Any AVAudioEngineConfigurationChange during recording is treated as "the hardware went away": the buffer is dropped, the engine torn down, and onHardwareChange cancels the dictation.

But that notification is not only fired when someone unplugs headphones. Opening an input stream on a Bluetooth headset makes macOS switch the device from A2DP to HFP, which fires the notification within milliseconds of engine.start(). So the recording cancels itself before the user has said a word.

Reproduced with a Shokz OpenRun Pro 2 (16 kHz HFP) as the default input device, where FastWord could not record at all. Log from an instrumented build:

recording started
audio input: 16000 Hz, 1 ch
audio device changed — engine rebuilt, recording continues   <- was: cancelled here

Change

start() is split so buildEngine() can re-run against the new device mid-recording, keeping the audio captured so far. The dictation continues across the device switch instead of dying.

Cancelling is kept as the fallback when the rebuild itself fails, and a budget of three rebuilds per recording stops a device that keeps renegotiating from looping forever. Unplugging headphones mid-sentence still behaves sensibly: the engine rebinds to the new default input.

Also in here, because it is the same class of silent failure:

  • The app now asks for microphone access at launch. Previously nothing requested it and the app relied on the system prompting when the audio engine first starts; if that prompt is missed or dismissed, every dictation fails with no visible cause.
  • The input format, engine rebuilds and cancel reasons are logged, so this class of problem is diagnosable from the outside.

Testing

Built and run on macOS 26, Apple Silicon. Verified dictation now works end to end with the Bluetooth headset as default input, and that it still works with the built-in microphone. Switching the input device mid-recording keeps the recording alive rather than discarding it.

Any AVAudioEngineConfigurationChange during recording dropped the buffer
and cancelled. Opening the input on a Bluetooth headset makes macOS
switch it from A2DP to HFP, which fires that notification within
milliseconds of engine.start() — so on such a device every dictation
cancels itself the instant it begins, with no visible cause. Tested with
a Shokz OpenRun Pro 2 as the default input, where FastWord could not
record at all before this change.

start() is split so buildEngine() can re-run against the new device
mid-recording, keeping the audio captured so far. Cancelling stays as
the fallback for a failed rebuild, and a budget of three rebuilds stops
a device that keeps renegotiating from looping.

The app also asks for microphone access at launch rather than relying on
the system to prompt when the engine first starts, and the input format,
rebuilds and cancel reasons are logged.
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