Skip to content

Commit 332c04a

Browse files
authored
Merge pull request #102 from Resgrid/develop
Develop
2 parents 3354d61 + cb6aa75 commit 332c04a

6 files changed

Lines changed: 99 additions & 61 deletions

File tree

src/components/audio-stream/audio-stream-bottom-sheet.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,4 +245,3 @@ export const AudioStreamBottomSheet = () => {
245245
</Actionsheet>
246246
);
247247
};
248-

src/components/livekit/livekit-bottom-sheet.tsx

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export const LiveKitBottomSheet = () => {
3232
const { trackEvent } = useAnalytics();
3333

3434
const [currentView, setCurrentView] = useState<BottomSheetView>(BottomSheetView.ROOM_SELECT);
35+
const [previousView, setPreviousView] = useState<BottomSheetView>(BottomSheetView.ROOM_SELECT);
3536
const [isMuted, setIsMuted] = useState(true); // Default to muted
3637
const [permissionsRequested, setPermissionsRequested] = useState(false);
3738

@@ -181,12 +182,15 @@ export const LiveKitBottomSheet = () => {
181182
}, [disconnectFromRoom]);
182183

183184
const handleShowAudioSettings = useCallback(() => {
185+
if (currentView !== BottomSheetView.AUDIO_SETTINGS) {
186+
setPreviousView(currentView);
187+
}
184188
setCurrentView(BottomSheetView.AUDIO_SETTINGS);
185-
}, []);
189+
}, [currentView]);
186190

187191
const handleBackFromAudioSettings = useCallback(() => {
188-
setCurrentView(BottomSheetView.CONNECTED);
189-
}, []);
192+
setCurrentView(previousView);
193+
}, [previousView]);
190194

191195
const renderRoomSelect = () => (
192196
<View style={styles.content}>
@@ -300,14 +304,12 @@ export const LiveKitBottomSheet = () => {
300304
<ActionsheetDragIndicatorWrapper>
301305
<ActionsheetDragIndicator />
302306
</ActionsheetDragIndicatorWrapper>
303-
<View className="w-full p-4">
304-
<HStack className="mb-4 items-center justify-between">
307+
<View className="w-full">
308+
<HStack className="mb-2 items-center justify-between px-4 pt-2">
305309
<Text className="text-xl font-bold">{t('livekit.title')}</Text>
306-
{currentView === BottomSheetView.CONNECTED && (
307-
<TouchableOpacity onPress={handleShowAudioSettings} testID="header-audio-settings-button">
308-
<Headphones size={20} color="#6B7280" />
309-
</TouchableOpacity>
310-
)}
310+
<TouchableOpacity onPress={handleShowAudioSettings} testID="header-audio-settings-button">
311+
<Headphones size={20} color="#6B7280" />
312+
</TouchableOpacity>
311313
</HStack>
312314

313315
<View className="min-h-[400px]" testID="bottom-sheet-content">
@@ -323,7 +325,6 @@ const styles = StyleSheet.create({
323325
content: {
324326
flex: 1,
325327
width: '100%',
326-
paddingHorizontal: 16,
327328
},
328329
roomList: {
329330
flex: 1,

src/components/personnel/personnel-card.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ import { HStack } from '../ui/hstack';
1313
import { Text } from '../ui/text';
1414
import { VStack } from '../ui/vstack';
1515

16-
17-
1816
interface PersonnelCardProps {
1917
personnel: PersonnelInfoResultData;
2018
onPress: (id: string) => void;

src/components/settings/audio-device-selection.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,17 @@ export const AudioDeviceSelection: React.FC<AudioDeviceSelectionProps> = ({ show
111111
);
112112
};
113113

114-
const availableMicrophones = availableAudioDevices.filter((device) => (device.type === 'bluetooth' ? device.isAvailable : true));
115-
116-
const availableSpeakers = availableAudioDevices.filter((device) => device.isAvailable);
114+
const availableMicrophones = availableAudioDevices.filter((device) => {
115+
// Microphones include bluetooth, wired, and default input devices
116+
// Specifially exclude devices that are explicitly speakers
117+
return device.type === 'bluetooth' || device.type === 'wired' || device.type === 'default';
118+
});
119+
120+
const availableSpeakers = availableAudioDevices.filter((device) => {
121+
// Speakers include bluetooth, wired, speaker, and default output devices
122+
// Specifically exclude default microphone if it somehow gets tagged as output, though usually default output is 'speaker' or 'default'
123+
return (device.type === 'bluetooth' || device.type === 'wired' || device.type === 'speaker' || device.type === 'default') && device.id !== 'default-mic';
124+
});
117125

118126
return (
119127
<ScrollView className="flex-1">

src/stores/app/audio-stream-store.ts

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ interface AudioStreamState {
3232
// Stream operations
3333
fetchAvailableStreams: () => Promise<void>;
3434
playStream: (stream: DepartmentAudioResultStreamData) => Promise<void>;
35-
stopStream: () => Promise<void>;
35+
stopStream: (clearState?: boolean) => Promise<void>;
3636
cleanup: () => Promise<void>;
3737
}
3838

@@ -79,13 +79,18 @@ export const useAudioStreamStore = create<AudioStreamState>((set, get) => ({
7979
try {
8080
const { soundObject: currentSound, stopStream } = get();
8181

82-
// Stop current stream if playing
82+
// Optimistically set the current stream and loading state
83+
set({
84+
currentStream: stream,
85+
isLoading: true,
86+
isBuffering: true,
87+
});
88+
89+
// Stop current stream if playing, but don't clear the state since we just set it
8390
if (currentSound) {
84-
await stopStream();
91+
await stopStream(false);
8592
}
8693

87-
set({ isLoading: true, isBuffering: true });
88-
8994
logger.debug({
9095
message: 'Starting audio stream',
9196
context: { streamName: stream.Name, streamUrl: stream.Url },
@@ -189,12 +194,10 @@ export const useAudioStreamStore = create<AudioStreamState>((set, get) => ({
189194
isLoading: false,
190195
isBuffering: false,
191196
});
192-
193-
194197
}
195198
},
196199

197-
stopStream: async () => {
200+
stopStream: async (clearState = true) => {
198201
try {
199202
const { soundObject, currentStream } = get();
200203

@@ -208,13 +211,21 @@ export const useAudioStreamStore = create<AudioStreamState>((set, get) => ({
208211
});
209212
}
210213

211-
set({
212-
soundObject: null,
213-
currentStream: null,
214-
isPlaying: false,
215-
isLoading: false,
216-
isBuffering: false,
217-
});
214+
if (clearState) {
215+
set({
216+
soundObject: null,
217+
currentStream: null,
218+
isPlaying: false,
219+
isLoading: false,
220+
isBuffering: false,
221+
});
222+
} else {
223+
// If not clearing state, just clear the sound object
224+
set({
225+
soundObject: null,
226+
isPlaying: false,
227+
});
228+
}
218229
} catch (error) {
219230
logger.error({
220231
message: 'Failed to stop audio stream',

src/stores/app/livekit-store.ts

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import notifee, { AndroidImportance } from '@notifee/react-native';
22
import { getRecordingPermissionsAsync, requestRecordingPermissionsAsync } from 'expo-audio';
3+
import { Audio } from 'expo-av';
34
import { Room, RoomEvent } from 'livekit-client';
45
import { Platform } from 'react-native';
56
import { create } from 'zustand';
@@ -12,44 +13,54 @@ import { headsetButtonService } from '../../services/headset-button.service';
1213
import { toggleMicrophone } from '../../utils/microphone-toggle';
1314
import { useBluetoothAudioStore } from './bluetooth-audio-store';
1415

16+
// Helper function to setup audio routing based on selected devices
1517
// Helper function to setup audio routing based on selected devices
1618
const setupAudioRouting = async (room: Room): Promise<void> => {
1719
try {
1820
const bluetoothStore = useBluetoothAudioStore.getState();
19-
const { selectedAudioDevices, connectedDevice } = bluetoothStore;
21+
const { selectedAudioDevices } = bluetoothStore;
22+
const speaker = selectedAudioDevices.speaker;
23+
const microphone = selectedAudioDevices.microphone;
24+
25+
logger.info({
26+
message: 'Setting up audio routing',
27+
context: {
28+
speakerType: speaker?.type,
29+
speakerName: speaker?.name,
30+
micType: microphone?.type,
31+
},
32+
});
2033

21-
// If we have a connected Bluetooth device, prioritize it
22-
if (connectedDevice && connectedDevice.hasAudioCapability) {
23-
logger.info({
24-
message: 'Using Bluetooth device for audio routing',
25-
context: { deviceName: connectedDevice.name },
26-
});
34+
if (Platform.OS === 'android' || Platform.OS === 'ios') {
35+
// Default configuration for voice call
36+
const audioModeConfig: any = {
37+
allowsRecordingIOS: true,
38+
staysActiveInBackground: true,
39+
playsInSilentModeIOS: true,
40+
shouldDuckAndroid: true,
41+
// Default to earpiece unless speaker is explicitly selected
42+
playThroughEarpieceAndroid: true,
43+
};
2744

28-
// Update selected devices to use Bluetooth
29-
const deviceName = connectedDevice.name || 'Bluetooth Device';
30-
const bluetoothMicrophone = connectedDevice.supportsMicrophoneControl ? { id: connectedDevice.id, name: deviceName, type: 'bluetooth' as const, isAvailable: true } : selectedAudioDevices.microphone;
45+
// If speaker device is selected (explicitly 'speaker' type), force speaker output
46+
if (speaker?.type === 'speaker') {
47+
logger.debug({ message: 'Routing audio to Speakerphone' });
48+
audioModeConfig.playThroughEarpieceAndroid = false;
3149

32-
const bluetoothSpeaker = {
33-
id: connectedDevice.id,
34-
name: deviceName,
35-
type: 'bluetooth' as const,
36-
isAvailable: true,
37-
};
50+
// On iOS, we might need to handle this differently if we wanted to force speaker,
51+
// but typically standard routing handles it or AVRoutePickerView is used.
52+
// For Expo AV, we can sometimes influence it.
53+
} else {
54+
logger.debug({ message: 'Routing audio to Earpiece/Headset' });
55+
audioModeConfig.playThroughEarpieceAndroid = true;
56+
}
3857

39-
bluetoothStore.setSelectedMicrophone(bluetoothMicrophone);
40-
bluetoothStore.setSelectedSpeaker(bluetoothSpeaker);
58+
await Audio.setAudioModeAsync(audioModeConfig);
59+
}
4160

42-
// Note: Actual audio routing would be implemented via native modules
43-
// This is a placeholder for the audio routing logic
44-
logger.debug({
45-
message: 'Audio routing configured for Bluetooth device',
46-
});
47-
} else {
48-
// Use default audio devices (selected devices or default)
49-
logger.debug({
50-
message: 'Using default audio devices',
51-
context: { selectedAudioDevices },
52-
});
61+
// Handle LiveKit specific device switching if needed (mostly for web/desktop, but good to have)
62+
if (speaker?.id && speaker.id !== 'default-speaker' && speaker.type === 'bluetooth') {
63+
// logic for specific bluetooth device selection if feasible
5364
}
5465
} catch (error) {
5566
logger.error({
@@ -466,3 +477,13 @@ export const useLiveKitStore = create<LiveKitState>((set, get) => ({
466477
}
467478
},
468479
}));
480+
481+
// Subscribe to bluetooth store changes to trigger audio routing updates
482+
useBluetoothAudioStore.subscribe((state, prevState) => {
483+
if (state.selectedAudioDevices !== prevState.selectedAudioDevices) {
484+
const room = useLiveKitStore.getState().currentRoom;
485+
if (room) {
486+
setupAudioRouting(room);
487+
}
488+
}
489+
});

0 commit comments

Comments
 (0)