mirror of
https://github.com/suitenumerique/meet.git
synced 2026-08-21 23:57:00 +00:00
fixup! wip handle device in use
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { deviceAvailabilityStore } from '@/stores/deviceAvailability'
|
||||
import { probeDeviceReleased } from '../livekit/utils/mediaPermissions'
|
||||
import type { PermissionKind } from '@/stores/permissions'
|
||||
|
||||
const RETRY_INTERVAL_MS = 30_000
|
||||
|
||||
/**
|
||||
* There is no browser event for "another app released the device", so
|
||||
* while a device is flagged in use, re-probe it periodically. Only clears
|
||||
* the flag (the toggle becomes usable again); it never re-enables the
|
||||
* device on the user's behalf.
|
||||
*/
|
||||
export function useWatchDeviceReleased() {
|
||||
const { cameraInUse, microphoneInUse } = useSnapshot(deviceAvailabilityStore)
|
||||
useWatchKind('camera', cameraInUse)
|
||||
useWatchKind('microphone', microphoneInUse)
|
||||
}
|
||||
|
||||
function useWatchKind(kind: PermissionKind, inUse: boolean) {
|
||||
useEffect(() => {
|
||||
if (!inUse) return
|
||||
const id = setInterval(
|
||||
() => void probeDeviceReleased(kind),
|
||||
RETRY_INTERVAL_MS
|
||||
)
|
||||
return () => clearInterval(id)
|
||||
}, [kind, inUse])
|
||||
}
|
||||
+16
-10
@@ -99,6 +99,7 @@ export const ToggleDevice = <T extends ToggleSource>({
|
||||
const cannotUseDevice = useCannotUseDevice(kind)
|
||||
const deviceMissing = useDeviceMissing(kind)
|
||||
const deviceInUse = useDeviceInUse(kind)
|
||||
const explainDeviceInUse = deviceInUse && context === 'room'
|
||||
const { status: silentMicStatus } = useSnapshot(silentMicStore)
|
||||
const silentMicWarning =
|
||||
kind === 'audioinput' &&
|
||||
@@ -112,26 +113,27 @@ export const ToggleDevice = <T extends ToggleSource>({
|
||||
const isRequestingPermission = useRef(false)
|
||||
const [alertError, setAlertError] = useState<MediaDeviceFailure | null>(null)
|
||||
|
||||
const mediaPath = context === 'join' ? 'join_preview' : 'room'
|
||||
|
||||
const onPress = async () => {
|
||||
if (!enabled && deviceMissing) {
|
||||
setAlertError(MediaDeviceFailure.NotFound)
|
||||
return
|
||||
}
|
||||
if (!cannotUseDevice) {
|
||||
if (!cannotUseDevice && !deviceInUse) {
|
||||
toggle()
|
||||
return
|
||||
}
|
||||
if (isRequestingPermission.current) return
|
||||
isRequestingPermission.current = true
|
||||
try {
|
||||
const granted = await requestDevicePermission(
|
||||
kind,
|
||||
context === 'join' ? 'join_preview' : 'room'
|
||||
)
|
||||
if (granted) {
|
||||
const acquired = await requestDevicePermission(kind, mediaPath)
|
||||
if (acquired) {
|
||||
toggle()
|
||||
} else {
|
||||
} else if (cannotUseDevice) {
|
||||
openPermissionsDialog(kind)
|
||||
} else if (explainDeviceInUse) {
|
||||
setAlertError(MediaDeviceFailure.DeviceInUse)
|
||||
}
|
||||
} finally {
|
||||
isRequestingPermission.current = false
|
||||
@@ -184,7 +186,7 @@ export const ToggleDevice = <T extends ToggleSource>({
|
||||
|
||||
const getToggleTooltip = () => {
|
||||
if (deviceMissing) return t(`deviceNotFound.${kind}`)
|
||||
if (deviceInUse) return t(`deviceInUse.${kind}`)
|
||||
if (explainDeviceInUse) return t(`deviceInUse.${kind}`)
|
||||
if (cannotUseDevice) return t('tooltip', { keyPrefix: 'permissionsButton' })
|
||||
return toggleLabel
|
||||
}
|
||||
@@ -204,8 +206,12 @@ export const ToggleDevice = <T extends ToggleSource>({
|
||||
)}
|
||||
{deviceInUse && (
|
||||
<PermissionNeededButton
|
||||
tooltip={t(`deviceInUse.${kind}`)}
|
||||
onPress={() => setAlertError(MediaDeviceFailure.DeviceInUse)}
|
||||
tooltip={explainDeviceInUse ? t(`deviceInUse.${kind}`) : undefined}
|
||||
onPress={
|
||||
explainDeviceInUse
|
||||
? () => setAlertError(MediaDeviceFailure.DeviceInUse)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{silentMicWarning && (
|
||||
|
||||
@@ -92,6 +92,26 @@ export const onMediaPermissionError = (
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Silent availability check for a device that was reported "in use":
|
||||
* acquires and releases it without any error reporting, so it can be
|
||||
* polled. Clears the in-use flag on success.
|
||||
*/
|
||||
export const probeDeviceReleased = async (
|
||||
kind: PermissionKind
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia(
|
||||
kind === 'camera' ? { video: true } : { audio: true }
|
||||
)
|
||||
stream.getTracks().forEach((track) => track.stop())
|
||||
noteDeviceReady(kind)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers the browser permission prompt for one device kind by acquiring
|
||||
* and immediately releasing a track. Resolves to whether access was granted.
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
import { useConfig } from '@/api/useConfig.ts'
|
||||
import { LogLevel, setLogLevel } from 'livekit-client'
|
||||
import { useWatchDeviceAvailability } from '@/features/rooms/hooks/useWatchDeviceAvailability'
|
||||
import { useWatchDeviceReleased } from '@/features/rooms/hooks/useWatchDeviceReleased'
|
||||
import { useRoomPageTitle } from '@/features/rooms/livekit/hooks/useRoomPageTitle'
|
||||
|
||||
const BaseRoom = ({ children }: { children: ReactNode }) => {
|
||||
@@ -49,6 +50,7 @@ const Room = () => {
|
||||
|
||||
useKeyboardShortcuts()
|
||||
useWatchDeviceAvailability()
|
||||
useWatchDeviceReleased()
|
||||
|
||||
const clearRouterState = () => {
|
||||
if (window?.history?.state) {
|
||||
|
||||
Reference in New Issue
Block a user