Skip to content

Commit e09f976

Browse files
RayoProtonMargeBot
authored andcommitted
Use room event from bridge between livekit - redux, unify and fix toggle...
1 parent 87989fa commit e09f976

12 files changed

Lines changed: 109 additions & 142 deletions

File tree

applications/meet/src/app/contexts/MediaManagementProvider/MediaManagementContext.tsx

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,13 @@ import { createContext, useContext } from 'react';
22

33
import debounce from 'lodash/debounce';
44

5-
import type { SwitchActiveDevice, ToggleVideoType } from '../../types';
5+
import type { SwitchActiveDevice, ToggleAudioType, ToggleVideoType } from '../../types';
66

77
export interface MediaManagementContextType {
88
isVideoEnabled: boolean;
99
isAudioEnabled: boolean;
1010
toggleVideo: ToggleVideoType;
11-
toggleAudio: ({
12-
isEnabled,
13-
audioDeviceId,
14-
preserveCache,
15-
}: {
16-
isEnabled?: boolean;
17-
audioDeviceId?: string;
18-
preserveCache?: boolean;
19-
}) => Promise<boolean | undefined>;
11+
toggleAudio: ToggleAudioType;
2012
backgroundBlur: boolean;
2113
toggleBackgroundBlur: ReturnType<typeof debounce>;
2214
isBackgroundBlurSupported: boolean;

applications/meet/src/app/contexts/MediaManagementProvider/MediaManagementProvider.tsx

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ import { TimeoutError, withTimeout } from '@proton/meet/utils/withTimeout';
3939
import { isMobile } from '@proton/shared/lib/helpers/browser';
4040
import { wait } from '@proton/shared/lib/helpers/promise';
4141

42-
import { useDeviceData } from '../../hooks/bridges/useDeviceData';
4342
import { useStableCallback } from '../../hooks/useStableCallback';
4443
import { preloadBackgroundProcessorAssets } from '../../processors/background-processor/createBackgroundProcessor';
4544
import type { SwitchActiveDevice } from '../../types';
@@ -50,9 +49,7 @@ import { PermissionsModal } from './PermissionsModal/PermissionsModal';
5049
import { useAudioToggle } from './mediaToggle/useAudioToggle';
5150
import { useVideoToggle } from './mediaToggle/useVideoToggle';
5251
import { useCameraPreview } from './useCameraPreview';
53-
import { useDeviceListSync } from './useDeviceListSync';
54-
import { useDevicePermissionChangeListener } from './useDevicePermissionChangeListener';
55-
import { useDynamicDeviceHandling } from './useDynamicDeviceHandling';
52+
import { useDeviceManagement } from './useDeviceManagement/useDeviceManagement';
5653
import { useMicrophoneVolumeAnalysis } from './useMicrophoneVolumeAnalysis';
5754

5855
const SWITCH_DEVICE_TIMEOUT_MS = 5000;
@@ -64,8 +61,6 @@ export const MediaManagementProvider = ({ children }: { children: React.ReactNod
6461
const dispatch = useMeetDispatch();
6562
const store = useMeetStore();
6663

67-
useDeviceListSync();
68-
6964
const initialCameraState = useMeetSelector(selectInitialCameraState);
7065
const initialAudioState = useMeetSelector(selectInitialAudioState);
7166

@@ -187,6 +182,8 @@ export const MediaManagementProvider = ({ children }: { children: React.ReactNod
187182

188183
const { toggleAudio, noiseFilter, toggleNoiseFilter, isAudioEnabled } = useAudioToggle(switchActiveDevice);
189184

185+
const { permissionsLoading } = useDeviceManagement({ toggleAudio, toggleVideo, switchActiveDevice });
186+
190187
const { handlePreviewCameraToggle, cleanupCameraPreview, cleanupPreviewTrack } = useCameraPreview({
191188
selectedCameraId: activeCameraDeviceId,
192189
facingMode: 'user',
@@ -371,16 +368,6 @@ export const MediaManagementProvider = ({ children }: { children: React.ReactNod
371368
}
372369
};
373370

374-
useDeviceData();
375-
376-
const { permissionsLoading } = useDevicePermissionChangeListener();
377-
378-
useDynamicDeviceHandling({
379-
toggleAudio,
380-
toggleVideo,
381-
switchActiveDevice,
382-
});
383-
384371
const initializedDevices = useRef({
385372
video: false,
386373
audio: false,

applications/meet/src/app/contexts/MediaManagementProvider/mediaToggle/useAudioToggle.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { wait } from '@proton/shared/lib/helpers/promise';
2424

2525
import { useStableCallback } from '../../../hooks/useStableCallback';
2626
import { audioQuality } from '../../../qualityConstants';
27-
import type { SwitchActiveDevice } from '../../../types';
27+
import type { AudioToggleParams, SwitchActiveDevice, ToggleAudioType } from '../../../types';
2828
import { getPersistedNoiseFilter, persistNoiseFilter } from '../../../utils/noiseFilterPersistence';
2929
import {
3030
RNNoiseFilter,
@@ -42,13 +42,6 @@ const NOISE_FILTER_SETTLE_DELAY_MS = 600;
4242
/** Longer delay after device change to let the track and devicechange events settle */
4343
const NOISE_FILTER_DEVICE_CHANGE_DELAY_MS = 1500;
4444
const SAFARI_DEVICE_RELEASE_DELAY_MS = 300;
45-
interface AudioToggleParams {
46-
isEnabled: boolean;
47-
audioDeviceId: string;
48-
preserveCache: boolean;
49-
/** Skip noise filter setup — used by track-ended recovery to get audio working immediately */
50-
skipNoiseFilter: boolean;
51-
}
5245

5346
const DEBUG_PREFIX = '[AudioToggle]';
5447

@@ -446,7 +439,7 @@ export const useAudioToggle = (switchActiveDevice: SwitchActiveDevice) => {
446439
* (LiveKit restarts it internally). If the track was replaced (new ID), schedules
447440
* a fresh noise filter attach with a longer delay to let devicechange events settle.
448441
*/
449-
const toggleAudio = useStableCallback(async (params: Partial<AudioToggleParams> = {}) => {
442+
const toggleAudio: ToggleAudioType = useStableCallback(async (params) => {
450443
let toggleResult = false;
451444
const operationId = ++toggleOperationId.current;
452445

@@ -456,7 +449,7 @@ export const useAudioToggle = (switchActiveDevice: SwitchActiveDevice) => {
456449
const {
457450
isEnabled = !currentMuteState,
458451
audioDeviceId = activeMicrophoneDeviceId,
459-
preserveCache,
452+
preserveCache = false,
460453
skipNoiseFilter = false,
461454
} = params;
462455

applications/meet/src/app/contexts/MediaManagementProvider/mediaToggle/useVideoToggle.ts

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
createBackgroundProcessor,
2323
ensureBackgroundBlurProcessor,
2424
} from '../../../processors/background-processor/createBackgroundProcessor';
25-
import type { SwitchActiveDevice } from '../../../types';
25+
import type { SwitchActiveDevice, ToggleVideoType } from '../../../types';
2626
import { getPersistedBackgroundBlur, persistBackgroundBlur } from '../../../utils/backgroundBlurPersistance';
2727
import { ERRORS_SIGNALING_POTENTIAL_STALE_DEVICE_STATE } from './constants';
2828

@@ -78,28 +78,17 @@ export const useVideoToggle = (switchActiveDevice: SwitchActiveDevice) => {
7878
}
7979
});
8080

81-
const toggleVideo = useStableCallback(
82-
async (
83-
params: {
84-
isEnabled?: boolean;
85-
videoDeviceId?: string;
86-
facingMode?: 'environment' | 'user';
87-
preserveCache?: boolean;
88-
recoveringFromError?: boolean;
89-
updateUserIntent?: boolean;
90-
} = {}
91-
) => {
81+
const toggleVideo: ToggleVideoType = useStableCallback(
82+
async ({
83+
isEnabled = userCameraIntent ?? initialCameraState,
84+
videoDeviceId = activeCameraDeviceId,
85+
facingMode: customFacingMode,
86+
preserveCache,
87+
recoveringFromError = false,
88+
updateUserIntent = true,
89+
} = {}) => {
9290
let toggleResult = false;
9391

94-
const {
95-
isEnabled = userCameraIntent ?? initialCameraState,
96-
videoDeviceId = activeCameraDeviceId,
97-
facingMode: customFacingMode,
98-
preserveCache,
99-
recoveringFromError = false,
100-
updateUserIntent = true,
101-
} = params;
102-
10392
const deviceId = videoDeviceId;
10493

10594
if (toggleInProgress.current || (!deviceId && !isMobile())) {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { useEffect } from 'react';
2+
3+
import { useRoomContext } from '@livekit/components-react';
4+
import { RoomEvent } from 'livekit-client';
5+
6+
import { useMeetDispatch } from '@proton/meet/store/hooks';
7+
import { setActiveDevice } from '@proton/meet/store/slices/deviceManagementSlice';
8+
9+
/**
10+
* Bridges LiveKit's active device state to the Redux store by subscribing to
11+
* `RoomEvent.ActiveDeviceChanged`.
12+
*
13+
* The previous `useMediaDeviceSelect` + useEffect approach was unreliable: the
14+
* effect intermittently didn't run on initial load, leaving the active deviceId
15+
* unset in Redux.
16+
*/
17+
export const useActiveDeviceSync = () => {
18+
const dispatch = useMeetDispatch();
19+
const room = useRoomContext();
20+
21+
useEffect(() => {
22+
const handleActiveDeviceChanged = (kind: MediaDeviceKind, deviceId: string) => {
23+
// 'default' is not a real enumerated videoinput id
24+
const normalizedDeviceId = kind === 'videoinput' && deviceId === 'default' ? '' : deviceId;
25+
dispatch(setActiveDevice({ kind, deviceId: normalizedDeviceId }));
26+
};
27+
28+
room.on(RoomEvent.ActiveDeviceChanged, handleActiveDeviceChanged);
29+
30+
return () => {
31+
room.off(RoomEvent.ActiveDeviceChanged, handleActiveDeviceChanged);
32+
};
33+
}, [room, dispatch]);
34+
};

applications/meet/src/app/contexts/MediaManagementProvider/useDeviceListSync.ts renamed to applications/meet/src/app/contexts/MediaManagementProvider/useDeviceManagement/useDeviceListSync.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { useEffect, useRef } from 'react';
22

33
import { useMeetStore } from '@proton/meet/store/hooks';
44
import { setDeviceList } from '@proton/meet/store/slices/deviceManagementSlice';
5+
import type { DeviceKind } from '@proton/meet/store/slices/deviceManagementSlice/types';
56
import { toSerializableDevice } from '@proton/meet/utils/deviceUtils';
67

7-
type DeviceKind = 'audioinput' | 'audiooutput' | 'videoinput';
88
const KINDS: DeviceKind[] = ['audioinput', 'audiooutput', 'videoinput'];
99

1010
const RECHECK_DELAY_MS = 150;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import type { SwitchActiveDevice, ToggleAudioType, ToggleVideoType } from '../../../types';
2+
import { useActiveDeviceSync } from './useActiveDeviceSync';
3+
import { useDeviceListSync } from './useDeviceListSync';
4+
import { useDevicePermissionsSync } from './useDevicePermissionsSync';
5+
import { useDynamicDeviceHandling } from './useDynamicDeviceHandling';
6+
7+
export const useDeviceManagement = ({
8+
toggleAudio,
9+
toggleVideo,
10+
switchActiveDevice,
11+
}: {
12+
toggleAudio: ToggleAudioType;
13+
toggleVideo: ToggleVideoType;
14+
switchActiveDevice: SwitchActiveDevice;
15+
}) => {
16+
// Sync device list from browser to redux
17+
useDeviceListSync();
18+
19+
// Sync active device from livekit to redux
20+
useActiveDeviceSync();
21+
22+
// Handle dynamic device (unplugging, plugging, etc.)
23+
useDynamicDeviceHandling({ toggleAudio, toggleVideo, switchActiveDevice });
24+
25+
// Sync device permissions from browser to redux
26+
const { permissionsLoading } = useDevicePermissionsSync();
27+
28+
return { permissionsLoading };
29+
};

applications/meet/src/app/contexts/MediaManagementProvider/useDevicePermissionChangeListener.ts renamed to applications/meet/src/app/contexts/MediaManagementProvider/useDeviceManagement/useDevicePermissionsSync.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { isFirefox, isSafari } from '@proton/shared/lib/helpers/browser';
1616
* Permission requests are not triggered here — they are initiated by the user
1717
* via the DevicesNeededConfirmation modal.
1818
*/
19-
export const useDevicePermissionChangeListener = () => {
19+
export const useDevicePermissionsSync = () => {
2020
const dispatch = useMeetDispatch();
2121
const [permissionsLoading, setPermissionsLoading] = useState(true);
2222

applications/meet/src/app/contexts/MediaManagementProvider/useDynamicDeviceHandling.ts renamed to applications/meet/src/app/contexts/MediaManagementProvider/useDeviceManagement/useDynamicDeviceHandling.ts

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@ import {
1414
selectPreferredCameraId,
1515
selectSpeakerState,
1616
} from '@proton/meet/store/slices/deviceManagementSlice/selectors';
17+
import type { DeviceKind } from '@proton/meet/store/slices/deviceManagementSlice/types';
1718
import { filterDevices, getDefaultDevice, isDefaultDevice } from '@proton/meet/utils/deviceUtils';
1819
import isTruthy from '@proton/utils/isTruthy';
1920

20-
import { useStableCallback } from '../../hooks/useStableCallback';
21-
import type { SwitchActiveDevice } from '../../types';
22-
import { supportsSetSinkId } from '../../utils/browser';
21+
import { useStableCallback } from '../../../hooks/useStableCallback';
22+
import type { SwitchActiveDevice, ToggleAudioType, ToggleVideoType } from '../../../types';
23+
import { supportsSetSinkId } from '../../../utils/browser';
2324

2425
const dynamicDeviceUpdate = ({
2526
deviceList,
@@ -80,28 +81,8 @@ interface DeviceIdSets {
8081
}
8182

8283
interface UseDynamicDeviceHandlingParams {
83-
toggleVideo: ({
84-
isEnabled,
85-
videoDeviceId,
86-
forceUpdate,
87-
preserveCache,
88-
updateUserIntent,
89-
}: {
90-
isEnabled?: boolean;
91-
videoDeviceId?: string;
92-
forceUpdate?: boolean;
93-
preserveCache?: boolean;
94-
updateUserIntent?: boolean;
95-
}) => Promise<boolean | undefined>;
96-
toggleAudio: ({
97-
isEnabled,
98-
audioDeviceId,
99-
preserveCache,
100-
}: {
101-
isEnabled?: boolean;
102-
audioDeviceId?: string;
103-
preserveCache?: boolean;
104-
}) => Promise<boolean | undefined>;
84+
toggleVideo: ToggleVideoType;
85+
toggleAudio: ToggleAudioType;
10586
switchActiveDevice: SwitchActiveDevice;
10687
}
10788

@@ -110,13 +91,15 @@ export const useDynamicDeviceHandling = ({
11091
toggleVideo,
11192
switchActiveDevice,
11293
}: UseDynamicDeviceHandlingParams) => {
94+
const room = useRoomContext();
95+
11396
const activeMicrophoneDeviceId = useMeetSelector(selectActiveMicrophoneId);
11497
const activeAudioOutputDeviceId = useMeetSelector(selectActiveAudioOutputId);
11598
const activeCameraDeviceId = useMeetSelector(selectActiveCameraId);
99+
116100
const preferredCameraId = useMeetSelector(selectPreferredCameraId);
117101
const microphoneState = useMeetSelector(selectMicrophoneState);
118102
const speakerState = useMeetSelector(selectSpeakerState);
119-
const room = useRoomContext();
120103

121104
const previousDevices = useRef<{
122105
microphones: MediaDeviceInfo[];
@@ -146,7 +129,7 @@ export const useDynamicDeviceHandling = ({
146129
}
147130

148131
const handleDeviceChange = useStableCallback(async () => {
149-
const getLocalDevicesWithErrorHandling = async (deviceType: 'audioinput' | 'videoinput' | 'audiooutput') => {
132+
const getLocalDevicesWithErrorHandling = async (deviceType: DeviceKind) => {
150133
try {
151134
return await Room.getLocalDevices(deviceType, false);
152135
} catch (error) {
@@ -253,7 +236,6 @@ export const useDynamicDeviceHandling = ({
253236

254237
void toggleVideo({
255238
videoDeviceId: newDeviceId,
256-
forceUpdate: true,
257239
preserveCache: true,
258240
updateUserIntent: false,
259241
});

applications/meet/src/app/hooks/bridges/useDeviceData.ts

Lines changed: 0 additions & 52 deletions
This file was deleted.

0 commit comments

Comments
 (0)