On iOS, when joining a LiveKit room without the local microphone enabled, remote audio plays through the physical earpiece (receiver) instead of the bottom loudspeaker, despite using configureAudio({ ios: { defaultOutput: "speaker" } }), selectAudioOutput("force_speaker"), and setAppleAudioConfiguration. The only reliable workaround is briefly enabling the microphone, which causes an audible bleep and risks transmitting audio when users should not speak (listen-only sessions).
To Reproduce
Steps to reproduce the behavior:
- Create an Expo app with
@livekit/react-native, @livekit/react-native-webrtc, call registerGlobals() at startup
- Connect to a LiveKit room with remote audio (e.g. host speaking) using the connect flow below
- Join without enabling the microphone
- Hold phone at arm's length — audio is inaudible (routed to earpiece)
- Enable microphone — audio immediately switches to loudspeaker ✅
Minimal connect flow:
await AudioSession.configureAudio({ ios: { defaultOutput: 'speaker' } });
await AudioSession.startAudioSession();
// selectAudioOutput before connect throws OSStatus -50
await room.connect(url, token);
// After connect: selectAudioOutput + setAppleAudioConfiguration have no effect
if (Platform.OS === 'ios') {
await AudioSession.setAppleAudioConfiguration({
audioCategory: 'playAndRecord',
audioCategoryOptions: ['defaultToSpeaker', 'allowBluetooth', 'allowBluetoothA2DP', 'allowAirPlay'],
audioMode: 'videoChat',
});
await AudioSession.selectAudioOutput('force_speaker');
}
Expected behavior
- Audio should route to loudspeaker when
defaultOutput: "speaker" is set
selectAudioOutput("force_speaker") should work after connect without errors
selectAudioOutput before connect should not throw OSStatus -50
Screenshots
N/A — audio routing behavior.
Device Info:
- Device: testing on iPhone 13
- OS: iOS 26.2.1
Dependencies Info (please reference your package-lock.json or yarn.lock file, not just your package.json):
- @livekit/react-native: 2.9.6
- livekit-client: 2.16.1
- react-native-webrtc: @livekit/react-native-webrtc 137.0.2
Additional context
What we've tried (none work):
selectAudioOutput("force_speaker") before connect → throws OSStatus -50 (paramErr)
selectAudioOutput + setAppleAudioConfiguration at 100ms, 300ms, 800ms, 2s, 4s after connect → no error, no effect
room.on("trackSubscribed", ...) → apply config when first remote audio subscribes → no effect
- Patched
selectAudioOutput to use RTCAudioSession instead of AVAudioSession → OSStatus -50, connection fails
showAudioRoutePicker() shows Speaker selected, but physical output is still earpiece → suggests session/config mismatch
Behavior summary:
| Scenario |
Result |
| Join (no mic) |
❌ Earpiece |
| Join with Bluetooth |
❌ Earpiece |
| Bluetooth disconnect mid-call |
❌ Earpiece (expected: loudspeaker) |
| Enable mic |
✅ Loudspeaker |
| If speaker already on → BT connect → BT disconnect |
✅ Loudspeaker |
On iOS, when joining a LiveKit room without the local microphone enabled, remote audio plays through the physical earpiece (receiver) instead of the bottom loudspeaker, despite using
configureAudio({ ios: { defaultOutput: "speaker" } }),selectAudioOutput("force_speaker"), andsetAppleAudioConfiguration. The only reliable workaround is briefly enabling the microphone, which causes an audible bleep and risks transmitting audio when users should not speak (listen-only sessions).To Reproduce
Steps to reproduce the behavior:
@livekit/react-native,@livekit/react-native-webrtc, callregisterGlobals()at startupMinimal connect flow:
Expected behavior
defaultOutput: "speaker"is setselectAudioOutput("force_speaker")should work after connect without errorsselectAudioOutputbefore connect should not throw OSStatus -50Screenshots
N/A — audio routing behavior.
Device Info:
Dependencies Info (please reference your package-lock.json or yarn.lock file, not just your package.json):
Additional context
What we've tried (none work):
selectAudioOutput("force_speaker")before connect → throws OSStatus -50 (paramErr)selectAudioOutput+setAppleAudioConfigurationat 100ms, 300ms, 800ms, 2s, 4s after connect → no error, no effectroom.on("trackSubscribed", ...)→ apply config when first remote audio subscribes → no effectselectAudioOutputto use RTCAudioSession instead of AVAudioSession → OSStatus -50, connection failsshowAudioRoutePicker()shows Speaker selected, but physical output is still earpiece → suggests session/config mismatchBehavior summary: