diff --git a/app/(tabs)/profile.tsx b/app/(tabs)/profile.tsx
index 7dcce9f..2583e1b 100644
--- a/app/(tabs)/profile.tsx
+++ b/app/(tabs)/profile.tsx
@@ -8,6 +8,8 @@ import {
Alert,
TextInput,
Modal,
+ KeyboardAvoidingView,
+ Platform,
} from 'react-native';
import { router } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
@@ -223,7 +225,10 @@ export default function ProfileScreen() {
animationType="fade"
onRequestClose={() => setShowDeploymentModal(false)}
>
-
+
Deployment ID
@@ -264,7 +269,7 @@ export default function ProfileScreen() {
-
+
);
diff --git a/utils/audioSession.ts b/utils/audioSession.ts
index efec804..e566f7f 100644
--- a/utils/audioSession.ts
+++ b/utils/audioSession.ts
@@ -50,102 +50,93 @@ export async function configureAudioForVoiceCall(): Promise {
}
if (Platform.OS === 'ios') {
- // iOS-specific configuration with fallback strategies
- // Try multiple configurations in order of preference
+ // iOS-specific configuration - FORCE SPEAKER OUTPUT
+ // Using videoChat mode + defaultSpeakerOutput option for guaranteed speaker
+ console.log('[AudioSession] Configuring iOS for SPEAKER output...');
- const configs = [
- // Strategy 1: videoChat mode (speaker by default, no problematic options)
- {
- name: 'videoChat',
- config: {
- audioCategory: 'playAndRecord',
- audioCategoryOptions: ['allowBluetooth', 'mixWithOthers'],
- audioMode: 'videoChat',
- },
- },
- // Strategy 2: voiceChat mode (more compatible, but earpiece by default)
- {
- name: 'voiceChat',
- config: {
- audioCategory: 'playAndRecord',
- audioCategoryOptions: ['allowBluetooth', 'mixWithOthers'],
- audioMode: 'voiceChat',
- },
- },
- // Strategy 3: Minimal config (most compatible)
- {
- name: 'minimal',
- config: {
- audioCategory: 'playAndRecord',
- audioCategoryOptions: [],
- audioMode: 'default',
- },
- },
- ];
-
- let configSuccess = false;
- let lastError: any = null;
-
- for (const { name, config } of configs) {
- try {
- console.log(`[AudioSession] Trying ${name} configuration...`);
- await AudioSession.setAppleAudioConfiguration(config);
- console.log(`[AudioSession] ${name} configuration succeeded!`);
- configSuccess = true;
- break;
- } catch (err) {
- console.warn(`[AudioSession] ${name} config failed:`, err);
- lastError = err;
- // Continue to next strategy
- }
+ try {
+ // Primary config: videoChat mode with defaultSpeakerOutput
+ await AudioSession.setAppleAudioConfiguration({
+ audioCategory: 'playAndRecord',
+ audioCategoryOptions: [
+ 'allowBluetooth',
+ 'mixWithOthers',
+ 'defaultToSpeaker', // KEY: Forces speaker as default output
+ ],
+ audioMode: 'videoChat', // videoChat mode uses speaker by default
+ });
+ console.log('[AudioSession] iOS videoChat + defaultToSpeaker configured!');
+ } catch (err) {
+ console.warn('[AudioSession] Primary iOS config failed, trying fallback:', err);
+ // Fallback: just videoChat without defaultToSpeaker option
+ await AudioSession.setAppleAudioConfiguration({
+ audioCategory: 'playAndRecord',
+ audioCategoryOptions: ['allowBluetooth', 'mixWithOthers'],
+ audioMode: 'videoChat',
+ });
}
- if (!configSuccess) {
- console.error('[AudioSession] All iOS configurations failed!');
- throw lastError || new Error('All audio configurations failed');
- }
-
- console.log('[AudioSession] Starting audio session...');
+ console.log('[AudioSession] Starting iOS audio session...');
await AudioSession.startAudioSession();
- // Try to set speaker output (non-critical, don't throw on failure)
+ // Additionally set default output to speaker (belt and suspenders)
try {
- console.log('[AudioSession] Setting default output to speaker...');
+ console.log('[AudioSession] Setting iOS default output to speaker...');
await AudioSession.configureAudio({
ios: {
defaultOutput: 'speaker',
},
});
+ console.log('[AudioSession] iOS speaker output set!');
} catch (outputErr) {
console.warn('[AudioSession] Could not set speaker output:', outputErr);
- // Continue anyway - audio will work, just maybe on earpiece
}
} else if (Platform.OS === 'android') {
- // Android-specific configuration
- // IMPORTANT: Using 'music' stream type to force output to speaker
- // 'voiceCall' stream type defaults to earpiece on many Android devices
- console.log('[AudioSession] Configuring Android audio for SPEAKER...');
+ // 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...');
+
await AudioSession.configureAudio({
android: {
- // Use MEDIA mode to ensure speaker output
+ // Force speaker as preferred output
+ preferredOutputList: ['speaker'],
+ // CRITICAL: This flag forces audio routing even in communication mode
+ forceHandleAudioRouting: true,
audioTypeOptions: {
manageAudioFocus: true,
- audioMode: 'normal',
+ // Use 'inCommunication' for echo cancellation (important for voice calls!)
+ audioMode: 'inCommunication',
audioFocusMode: 'gain',
- // Use 'music' stream - goes to speaker by default
- audioStreamType: 'music',
- audioAttributesUsageType: 'media',
- audioAttributesContentType: 'music',
+ // Voice call stream type for proper routing
+ audioStreamType: 'voiceCall',
+ audioAttributesUsageType: 'voiceCommunication',
+ audioAttributesContentType: 'speech',
},
- // 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] Configuration complete!');
@@ -191,7 +182,7 @@ export async function reconfigureAudioForPlayback(): Promise {
return;
}
- console.log(`[AudioSession] Reconfiguring for playback on ${Platform.OS}...`);
+ console.log(`[AudioSession] Reconfiguring for playback (SPEAKER) on ${Platform.OS}...`);
try {
const AudioSession = await getAudioSession();
@@ -200,33 +191,50 @@ export async function reconfigureAudioForPlayback(): Promise {
}
if (Platform.OS === 'ios') {
- // Reconfigure with same safe settings - this "refreshes" the audio routing
+ // Reconfigure iOS - force speaker output
await AudioSession.setAppleAudioConfiguration({
audioCategory: 'playAndRecord',
audioCategoryOptions: [
'allowBluetooth',
'mixWithOthers',
+ 'defaultToSpeaker', // Force speaker
],
- // Use 'videoChat' - defaults to speaker
- audioMode: 'videoChat',
+ audioMode: 'videoChat', // videoChat = speaker by default
});
- } else if (Platform.OS === 'android') {
- // Reconfigure Android audio to ensure speaker output
- // Using 'music' stream type to force speaker
+
+ // Also set default output to speaker
await AudioSession.configureAudio({
- android: {
- audioTypeOptions: {
- manageAudioFocus: true,
- audioMode: 'normal',
- audioFocusMode: 'gain',
- audioStreamType: 'music',
- audioAttributesUsageType: 'media',
- audioAttributesContentType: 'music',
- },
- preferredOutputList: ['speaker'],
- forceHandleAudioRouting: true,
+ ios: {
+ defaultOutput: 'speaker',
},
});
+ console.log('[AudioSession] iOS reconfigured for speaker playback');
+ } else if (Platform.OS === 'android') {
+ // Reconfigure Android - force speaker while keeping echo cancellation
+ await AudioSession.configureAudio({
+ android: {
+ preferredOutputList: ['speaker'],
+ forceHandleAudioRouting: true,
+ audioTypeOptions: {
+ manageAudioFocus: true,
+ audioMode: 'inCommunication', // Keep for echo cancellation
+ audioFocusMode: 'gain',
+ audioStreamType: 'voiceCall',
+ audioAttributesUsageType: 'voiceCommunication',
+ audioAttributesContentType: 'speech',
+ },
+ },
+ });
+
+ // 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');
}
console.log('[AudioSession] Reconfigured successfully');
@@ -252,11 +260,12 @@ export async function setAudioOutput(useSpeaker: boolean): Promise {
}
if (Platform.OS === 'ios') {
- // iOS: Update configuration based on desired output
- // Use 'videoChat' mode for speaker, 'voiceChat' for earpiece
+ // iOS: Use videoChat mode + defaultToSpeaker for speaker, voiceChat for earpiece
await AudioSession.setAppleAudioConfiguration({
audioCategory: 'playAndRecord',
- audioCategoryOptions: ['allowBluetooth', 'mixWithOthers'],
+ audioCategoryOptions: useSpeaker
+ ? ['allowBluetooth', 'mixWithOthers', 'defaultToSpeaker']
+ : ['allowBluetooth', 'mixWithOthers'],
audioMode: useSpeaker ? 'videoChat' : 'voiceChat',
});
@@ -267,25 +276,29 @@ export async function setAudioOutput(useSpeaker: boolean): Promise {
},
});
} else if (Platform.OS === 'android') {
- // Android: Switch stream type to control speaker/earpiece
- // - 'music' stream goes to speaker by default
- // - 'voiceCall' stream goes to earpiece by default
+ // Android: Keep inCommunication mode for echo cancellation, use explicit output selection
await AudioSession.configureAudio({
android: {
- audioTypeOptions: {
- manageAudioFocus: true,
- audioMode: useSpeaker ? 'normal' : 'inCommunication',
- audioFocusMode: 'gain',
- // 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,
+ audioTypeOptions: {
+ manageAudioFocus: true,
+ // Always use inCommunication for echo cancellation
+ audioMode: 'inCommunication',
+ audioFocusMode: 'gain',
+ audioStreamType: 'voiceCall',
+ audioAttributesUsageType: 'voiceCommunication',
+ audioAttributesContentType: 'speech',
+ },
},
});
+
+ // 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'}`);