🐛(frontend) make the device-in-use logic race-free

The device-in-use logic could race when multiple detection events
fired close together (e.g. rapid mic/cam toggles or several devices
becoming busy at once), leading to inconsistent store state.

Refactor the flow so all updates go through a serialized path,
removing the race conditions and keeping the in-use state
consistent regardless of event ordering.
This commit is contained in:
lebaudantoine
2026-08-23 17:14:17 +02:00
parent 38f3f9f52d
commit bcd95da21f
2 changed files with 15 additions and 6 deletions
@@ -36,10 +36,18 @@ function useWatchKind(
useEffect(() => { useEffect(() => {
if (!inUse) return if (!inUse) return
const id = setInterval( let stopped = false
() => void probeDeviceReleased(kind, selectedDeviceId || undefined), let timer: number | undefined
RETRY_INTERVAL_MS const tick = async () => {
) await probeDeviceReleased(kind, selectedDeviceId || undefined)
return () => clearInterval(id) if (!stopped) {
timer = window.setTimeout(tick, RETRY_INTERVAL_MS)
}
}
timer = window.setTimeout(tick, RETRY_INTERVAL_MS)
return () => {
stopped = true
window.clearTimeout(timer)
}
}, [kind, inUse, selectedDeviceId]) }, [kind, inUse, selectedDeviceId])
} }
@@ -151,14 +151,15 @@ function useLocalTrack<T extends LocalAudioTrack | LocalVideoTrack>({
let cancelled = false let cancelled = false
create() create()
.then((newTrack) => { .then((newTrack) => {
noteDeviceReady(permissionKind)
if (cancelled) { if (cancelled) {
newTrack.stop() newTrack.stop()
return return
} }
noteDeviceReady(permissionKind)
setTrack(newTrack) setTrack(newTrack)
}) })
.catch((error) => { .catch((error) => {
if (cancelled) return
onMediaPermissionError( onMediaPermissionError(
error as Error, error as Error,
permissionKind, permissionKind,