AudioRecord SCO Link Stability Bug (2026-05-21)
Symptom
Error Dialog:
Glasses not paired as Bluetooth audio device. Please pair in Settings → Bluetooth.
(Call to function 'WearablesModule.startAudioCapture' has been rejected.
→ Caused by: java.lang.IllegalStateException: AudioRecord failed to initialize (state=0)
→ SCO link may have dropped between setup and recording.)
Root Cause
In WearablesModule.kt:startAudioCapture(), the SCO Bluetooth link is validated at lines 695-702, but the validation window closes before we construct the AudioRecord at lines 728-734. If the SCO link drops during the construction phase (between waitForScoDevice() returning and AudioRecord() constructor completion), the recorder fails with state=0 (STATE_UNINITIALIZED).
The issue is a timing window where: 1. SCO link confirmed as active (line 695-702) 2. AudioRecord constructed (line 728-734) 3. SCO link can drop in between, but we don't detect it 4. AudioRecord returns state=0, indicating initialization failed silently due to SCO link dropout
This is a known Android behavior where SCO negotiation is inherently fragile—the link can drop if: - The headset enters power-save mode - Radio interference occurs - The headset firmware times out waiting for audio data - The connection is weak
Prior Investigation
- Previous commit added validation for
recorder.state != AudioRecord.STATE_INITIALIZED - This correctly catches the error but comes too late (after failed construction)
- The error is wrapped by JS caller as "Glasses not paired" message for all
startAudioCaptureerrors - True fix: don't let SCO drop during construction; add a wait loop or broadcast listener
Solution
Add a broadcast receiver to monitor ACTION_SCO_AUDIO_STATE_CHANGED events. After waitForScoDevice() confirms the link is active, keep listening until we construct and start the AudioRecord. If SCO drops during construction, either: 1. Retry the entire flow (simpler, matches user retry pattern) 2. Wait for SCO to re-stabilize, then construct AudioRecord 3. Defer AudioRecord construction until the broadcast confirms SCO_AUDIO_STATE_CONNECTED
Chosen approach: Add a second validation check immediately before AudioRecord() constructor to ensure SCO is still active. If not, retry the entire wait-and-construct sequence (max 3 attempts).
Implementation
File: main/app/wearables-module/android/src/main/java/expo/modules/wearablesmodule/WearablesModule.kt
Changes: 1. Extract AudioRecord construction into a retry loop 2. Before constructing AudioRecord, check that audioManager.communicationDevice?.id == glassesDevice.id 3. If check fails, re-validate SCO link and retry (up to 3 attempts) 4. If all retries exhausted, throw original error message
Verification
- Manual test: pair glasses, attempt audio capture, verify SCO link does not drop during AudioRecord construction
- Monitor logs for
[AUDIO]messages to confirm retry attempts if needed - Verify no new errors on normal operation (no SCO drops during stable connection)
Status
✅ Fixed
✅ Verification complete
✅ PR #500 ready