Skip to content

Commit 598bb30

Browse files
authored
Merge pull request #224 from Resgrid/develop
RU-T47 Fixing issue with voice channel
2 parents 2691ffa + 677474d commit 598bb30

File tree

2 files changed

+96
-3
lines changed

2 files changed

+96
-3
lines changed

__mocks__/@livekit/react-native.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Mock for @livekit/react-native
2+
export const AudioSession = {
3+
startAudioSession: jest.fn().mockResolvedValue(undefined),
4+
stopAudioSession: jest.fn().mockResolvedValue(undefined),
5+
configureAudio: jest.fn().mockResolvedValue(undefined),
6+
getAudioOutputs: jest.fn().mockResolvedValue([]),
7+
selectAudioOutput: jest.fn().mockResolvedValue(undefined),
8+
showAudioRoutePicker: jest.fn().mockResolvedValue(undefined),
9+
setAppleAudioConfiguration: jest.fn().mockResolvedValue(undefined),
10+
};
11+
12+
export const registerGlobals = jest.fn();
13+
14+
export default {
15+
AudioSession,
16+
registerGlobals,
17+
};

src/stores/app/livekit-store.ts

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { AudioSession } from '@livekit/react-native';
12
import { RTCAudioSession } from '@livekit/react-native-webrtc';
23
import notifee, { AndroidForegroundServiceType, AndroidImportance } from '@notifee/react-native';
34
import { getRecordingPermissionsAsync, requestRecordingPermissionsAsync } from 'expo-audio';
@@ -381,18 +382,58 @@ export const useLiveKitStore = create<LiveKitState>((set, get) => ({
381382
message: 'Cannot connect to room - permissions not granted',
382383
context: { roomName: roomInfo.Name },
383384
});
385+
Alert.alert('Voice Connection Error', 'Microphone permission is required to join a voice channel. Please grant the permission in your device settings.', [
386+
{ text: 'Cancel', style: 'cancel' },
387+
{ text: 'Open Settings', onPress: () => Linking.openSettings() },
388+
]);
384389
return;
385390
}
386391

387392
const { currentRoom, voipServerWebsocketSslAddress } = get();
388393

389-
// Disconnect from current room if connected
394+
// Validate connection parameters before attempting to connect
395+
if (!voipServerWebsocketSslAddress) {
396+
logger.error({
397+
message: 'Cannot connect to room - no VoIP server address available',
398+
context: { roomName: roomInfo.Name },
399+
});
400+
Alert.alert('Voice Connection Error', 'Voice server address is not available. Please try again later.');
401+
return;
402+
}
403+
404+
if (!token) {
405+
logger.error({
406+
message: 'Cannot connect to room - no token provided',
407+
context: { roomName: roomInfo.Name },
408+
});
409+
Alert.alert('Voice Connection Error', 'Voice channel token is missing. Please try refreshing the voice channels.');
410+
return;
411+
}
412+
413+
// Disconnect from current room if connected (use full cleanup flow)
390414
if (currentRoom) {
391-
currentRoom.disconnect();
415+
await get().disconnectFromRoom();
392416
}
393417

394418
set({ isConnecting: true });
395419

420+
// Start the native audio session before connecting (required for production builds)
421+
// In dev builds, the audio session may persist across hot reloads, but in production
422+
// cold starts it must be explicitly started for WebRTC to function correctly
423+
if (Platform.OS !== 'web') {
424+
try {
425+
await AudioSession.startAudioSession();
426+
logger.info({
427+
message: 'Audio session started successfully',
428+
});
429+
} catch (audioSessionError) {
430+
logger.warn({
431+
message: 'Failed to start audio session - continuing with connection attempt',
432+
context: { error: audioSessionError },
433+
});
434+
}
435+
}
436+
396437
// Create a new room
397438
const room = new Room();
398439

@@ -576,10 +617,30 @@ export const useLiveKitStore = create<LiveKitState>((set, get) => ({
576617
} catch (error) {
577618
logger.error({
578619
message: 'Failed to connect to room',
579-
context: { error },
620+
context: { error, roomName: roomInfo?.Name },
580621
});
581622

623+
// Stop audio session on failure since we started it above
624+
if (Platform.OS !== 'web') {
625+
try {
626+
await AudioSession.stopAudioSession();
627+
} catch (stopError) {
628+
logger.warn({
629+
message: 'Failed to stop audio session after connection error',
630+
context: { error: stopError },
631+
});
632+
}
633+
}
634+
582635
set({ isConnecting: false });
636+
637+
// Show user-visible error so the failure is not silent in production builds
638+
const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred';
639+
Alert.alert(
640+
'Voice Connection Failed',
641+
`Unable to connect to voice channel "${roomInfo?.Name || 'Unknown'}". ${errorMessage}`,
642+
[{ text: 'OK' }]
643+
);
583644
}
584645
},
585646

@@ -589,6 +650,21 @@ export const useLiveKitStore = create<LiveKitState>((set, get) => ({
589650
await currentRoom.disconnect();
590651
await audioService.playDisconnectedFromAudioRoomSound();
591652

653+
// Stop the native audio session that was started during connectToRoom
654+
if (Platform.OS !== 'web') {
655+
try {
656+
await AudioSession.stopAudioSession();
657+
logger.debug({
658+
message: 'Audio session stopped',
659+
});
660+
} catch (audioSessionError) {
661+
logger.warn({
662+
message: 'Failed to stop audio session',
663+
context: { error: audioSessionError },
664+
});
665+
}
666+
}
667+
592668
// End CallKeep call (works on all platforms - web has no-op implementation)
593669
try {
594670
await callKeepService.endCall();

0 commit comments

Comments
 (0)