Skip to content

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 startAudioCapture errors
  • 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