wip sync again devices preferences

This commit is contained in:
lebaudantoine
2026-08-07 20:28:27 +02:00
parent e93bc91b24
commit 71ea6421dd
4 changed files with 69 additions and 0 deletions
@@ -55,6 +55,7 @@ import {
import { saveUsername, userStore } from '@/stores/user'
import { useCannotUseDevice } from '../livekit/hooks/useCannotUseDevice'
import { useSyncTrackDeviceId } from '../livekit/hooks/useSyncTrackDeviceId'
import { useSnapshot } from 'valtio'
import { useUser } from '@/features/auth/api/useUser'
import { useConfig } from '@/api/useConfig'
@@ -274,6 +275,8 @@ export const Join = ({
const videoTrack = dynamicVideoTrack || previewVideoTrack
const audioTrack = dynamicAudioTrack || previewAudioTrack
useSyncTrackDeviceId(audioTrack, saveAudioInputDeviceId)
useSyncTrackDeviceId(videoTrack, saveVideoInputDeviceId)
const videoEl = useRef(null)
const isVideoInitiated = useRef(false)
@@ -0,0 +1,20 @@
import { useLocalParticipant } from '@livekit/components-react'
import type { LocalTrack } from 'livekit-client'
import { useSyncTrackDeviceId } from '../hooks/useSyncTrackDeviceId'
import {
saveAudioInputDeviceId,
saveVideoInputDeviceId,
} from '@/stores/userChoices'
export const SyncDevicePreferences = () => {
const { cameraTrack, microphoneTrack } = useLocalParticipant()
useSyncTrackDeviceId(
cameraTrack?.track as LocalTrack | undefined,
saveVideoInputDeviceId
)
useSyncTrackDeviceId(
microphoneTrack?.track as LocalTrack | undefined,
saveAudioInputDeviceId
)
return null
}
@@ -0,0 +1,44 @@
import { useEffect } from 'react'
import { TrackEvent, type LocalTrack } from 'livekit-client'
/**
* Keeps the persisted device preference aligned with the device the track
* is ACTUALLY using — the track is the ground truth, not the store.
*
* Syncs on mount and on every TrackEvent.Restarted, which covers all the
* moments the underlying device can change on the join screen:
* - initial acquisition where LiveKit resolved the 'default' alias
* (getDeviceId(normalize=true) resolves it to the concrete id via
* livekit's DeviceManager — this replaces the former
* useResolveInitiallyDefaultDeviceId, which never fired after init),
* - a device switch via setDeviceId,
* - an unmute re-acquisition,
* - the browser falling back to another device.
*/
export const useSyncTrackDeviceId = (
track: LocalTrack | undefined,
save: (deviceId: string) => void
) => {
useEffect(() => {
if (!track) return
let cancelled = false
const sync = () => {
track
.getDeviceId()
.then((deviceId) => {
if (!cancelled && deviceId && deviceId !== 'default') {
save(deviceId)
}
})
.catch(() => {
// A track without settings (ended, screen share) has no id to sync.
})
}
sync()
track.on(TrackEvent.Restarted, sync)
return () => {
cancelled = true
track.off(TrackEvent.Restarted, sync)
}
}, [track, save])
}
@@ -26,6 +26,7 @@ import { PipRoomPlaceholder } from '@/features/pip/components/PipRoomPlaceholder
import { StageLayout } from '@/features/layout/components/StageLayout'
import { PinAnnouncer } from '@/features/layout/components/PinAnnouncer'
import { ChatProvider } from '@/features/chat/components/ChatProvider'
import { SyncDevicePreferences } from '@/features/rooms/livekit/components/SyncDevicePreferences'
/**
* @public
@@ -65,6 +66,7 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
<>
<RoomMetadataSynchronizer />
<ConnectionObserver />
<SyncDevicePreferences />
<MediaStateObserver />
<ChatProvider />
<VideoResolutionSubscription />