Fix Android speaker: use music stream type instead of voiceCall

- audioStreamType: music (routes to SPEAKER by default)
- audioMode: normal (not inCommunication which uses earpiece)
- audioAttributesUsageType: media
- audioAttributesContentType: music

Previous voiceCall stream was routing to earpiece on Android devices.
This commit is contained in:
Sergei 2026-01-25 13:12:16 -08:00
parent 8240e51bc5
commit cd4137ef36

View File

@ -93,50 +93,32 @@ export async function configureAudioForVoiceCall(): Promise<void> {
}
} else if (Platform.OS === 'android') {
// Android-specific configuration - FORCE SPEAKER OUTPUT
// SOLUTION: Use 'inCommunication' for echo cancellation + forceHandleAudioRouting + explicit speaker selection
console.log('[AudioSession] Configuring Android audio for SPEAKER with echo cancellation...');
// CRITICAL: Use 'music' stream type - it defaults to SPEAKER!
// 'voiceCall' stream type defaults to EARPIECE on many Android devices
console.log('[AudioSession] Configuring Android audio for SPEAKER...');
await AudioSession.configureAudio({
android: {
// Force speaker as preferred output
preferredOutputList: ['speaker'],
// CRITICAL: This flag forces audio routing even in communication mode
forceHandleAudioRouting: true,
// Use MEDIA mode to ensure speaker output
audioTypeOptions: {
manageAudioFocus: true,
// Use 'inCommunication' for echo cancellation (important for voice calls!)
audioMode: 'inCommunication',
audioMode: 'normal',
audioFocusMode: 'gain',
// Voice call stream type for proper routing
audioStreamType: 'voiceCall',
audioAttributesUsageType: 'voiceCommunication',
audioAttributesContentType: 'speech',
// Use 'music' stream - goes to speaker by default!
audioStreamType: 'music',
audioAttributesUsageType: 'media',
audioAttributesContentType: 'music',
},
// Force speaker as output
preferredOutputList: ['speaker'],
// Allow us to control audio routing
forceHandleAudioRouting: true,
},
});
console.log('[AudioSession] Starting Android audio session...');
await AudioSession.startAudioSession();
// CRITICAL: Explicitly select speaker AFTER session starts
// This overrides the default earpiece routing of inCommunication mode
try {
console.log('[AudioSession] Explicitly selecting speaker output...');
await AudioSession.selectAudioOutput('speaker');
console.log('[AudioSession] Speaker output explicitly selected!');
} catch (speakerErr) {
console.warn('[AudioSession] selectAudioOutput failed, trying showAudioRoutePicker:', speakerErr);
// Fallback: try to show audio route picker or use alternative method
try {
if (AudioSession.showAudioRoutePicker) {
await AudioSession.showAudioRoutePicker();
}
} catch (pickerErr) {
console.warn('[AudioSession] showAudioRoutePicker also failed:', pickerErr);
}
}
console.log('[AudioSession] Android speaker mode with echo cancellation configured!');
console.log('[AudioSession] Android speaker mode configured!');
}
console.log('[AudioSession] Configuration complete!');
@ -210,30 +192,22 @@ export async function reconfigureAudioForPlayback(): Promise<void> {
});
console.log('[AudioSession] iOS reconfigured for speaker playback');
} else if (Platform.OS === 'android') {
// Reconfigure Android - force speaker while keeping echo cancellation
// Reconfigure Android audio to ensure speaker output
// Using 'music' stream type to force speaker
await AudioSession.configureAudio({
android: {
preferredOutputList: ['speaker'],
forceHandleAudioRouting: true,
audioTypeOptions: {
manageAudioFocus: true,
audioMode: 'inCommunication', // Keep for echo cancellation
audioMode: 'normal',
audioFocusMode: 'gain',
audioStreamType: 'voiceCall',
audioAttributesUsageType: 'voiceCommunication',
audioAttributesContentType: 'speech',
audioStreamType: 'music',
audioAttributesUsageType: 'media',
audioAttributesContentType: 'music',
},
preferredOutputList: ['speaker'],
forceHandleAudioRouting: true,
},
});
// Explicitly select speaker output
try {
await AudioSession.selectAudioOutput('speaker');
console.log('[AudioSession] Android speaker explicitly selected');
} catch (err) {
console.warn('[AudioSession] selectAudioOutput failed in reconfigure:', err);
}
console.log('[AudioSession] Android reconfigured for speaker playback');
}
@ -276,29 +250,25 @@ export async function setAudioOutput(useSpeaker: boolean): Promise<void> {
},
});
} else if (Platform.OS === 'android') {
// Android: Keep inCommunication mode for echo cancellation, use explicit output selection
// Android: Switch stream type to control speaker/earpiece
// - 'music' stream goes to speaker by default
// - 'voiceCall' stream goes to earpiece by default
await AudioSession.configureAudio({
android: {
preferredOutputList: useSpeaker ? ['speaker'] : ['earpiece'],
forceHandleAudioRouting: true,
audioTypeOptions: {
manageAudioFocus: true,
// Always use inCommunication for echo cancellation
audioMode: 'inCommunication',
audioMode: useSpeaker ? 'normal' : 'inCommunication',
audioFocusMode: 'gain',
audioStreamType: 'voiceCall',
audioAttributesUsageType: 'voiceCommunication',
audioAttributesContentType: 'speech',
// Key difference: music→speaker, voiceCall→earpiece
audioStreamType: useSpeaker ? 'music' : 'voiceCall',
audioAttributesUsageType: useSpeaker ? 'media' : 'voiceCommunication',
audioAttributesContentType: useSpeaker ? 'music' : 'speech',
},
// Also set preferred output list
preferredOutputList: useSpeaker ? ['speaker'] : ['earpiece'],
forceHandleAudioRouting: true,
},
});
// Explicitly select output device
try {
await AudioSession.selectAudioOutput(useSpeaker ? 'speaker' : 'earpiece');
} catch (err) {
console.warn('[AudioSession] selectAudioOutput failed:', err);
}
}
console.log(`[AudioSession] Audio output set to ${useSpeaker ? 'SPEAKER' : 'EARPIECE'}`);