♻️(frontend) refactor device select for controlled behavior
Major refactor of device select component with several key improvements: * Set permission=true for Firefox compatibility - without this flag, device list returns empty on Firefox * Implement controlled component pattern for active device selection, ensuring sync with preview track state * Remove default device handling as controlled behavior eliminates need * Render selectors only after permissions granted to prevent double permission prompts (separate for mic/camera) Ensures usePreviewTrack handles initial permission request, then selectors allow specific device choice once access is granted.
This commit is contained in:
committed by
aleb_the_flash
parent
7c6182cc4e
commit
cb8b415ef9
@@ -7,6 +7,8 @@ import { useTranslation } from 'react-i18next'
|
||||
import { useMediaDeviceSelect } from '@livekit/components-react'
|
||||
import { useMemo } from 'react'
|
||||
import { Select } from '@/primitives/Select'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { permissionsStore } from '@/stores/permissions'
|
||||
|
||||
type DeviceItems = Array<{ value: string; label: string }>
|
||||
|
||||
@@ -20,34 +22,23 @@ type SelectDeviceProps = {
|
||||
kind: MediaDeviceKind
|
||||
}
|
||||
|
||||
export const SelectDevice = ({ id, onSubmit, kind }: SelectDeviceProps) => {
|
||||
type SelectDevicePermissionsProps = SelectDeviceProps & {
|
||||
config: DeviceConfig
|
||||
}
|
||||
|
||||
const SelectDevicePermissions = ({
|
||||
id,
|
||||
kind,
|
||||
config,
|
||||
onSubmit,
|
||||
}: SelectDevicePermissionsProps) => {
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'join' })
|
||||
|
||||
const config = useMemo<DeviceConfig | undefined>(() => {
|
||||
switch (kind) {
|
||||
case 'audioinput':
|
||||
return {
|
||||
icon: RiMicLine,
|
||||
}
|
||||
case 'videoinput':
|
||||
return {
|
||||
icon: RiVideoOnLine,
|
||||
}
|
||||
}
|
||||
}, [kind])
|
||||
|
||||
const getDefaultSelectedKey = (items: DeviceItems) => {
|
||||
if (!items || items.length === 0) return
|
||||
const defaultItem =
|
||||
items.find((item) => item.value === 'default') || items[0]
|
||||
return defaultItem.value
|
||||
}
|
||||
|
||||
const {
|
||||
devices: devices,
|
||||
activeDeviceId: activeDeviceId,
|
||||
setActiveMediaDevice: setActiveMediaDevice,
|
||||
} = useMediaDeviceSelect({ kind, requestPermissions: false })
|
||||
} = useMediaDeviceSelect({ kind: kind, requestPermissions: true })
|
||||
|
||||
const items: DeviceItems = devices
|
||||
.filter((d) => !!d.deviceId)
|
||||
@@ -64,7 +55,7 @@ export const SelectDevice = ({ id, onSubmit, kind }: SelectDeviceProps) => {
|
||||
items={items}
|
||||
iconComponent={config?.icon}
|
||||
placeholder={t('selectDevice.loading')}
|
||||
defaultSelectedKey={id || activeDeviceId || getDefaultSelectedKey(items)}
|
||||
selectedKey={id || activeDeviceId}
|
||||
onSelectionChange={(key) => {
|
||||
onSubmit?.(key as string)
|
||||
setActiveMediaDevice(key as string)
|
||||
@@ -72,3 +63,56 @@ export const SelectDevice = ({ id, onSubmit, kind }: SelectDeviceProps) => {
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export const SelectDevice = ({ id, onSubmit, kind }: SelectDeviceProps) => {
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'join' })
|
||||
|
||||
const permissions = useSnapshot(permissionsStore)
|
||||
|
||||
const config = useMemo<DeviceConfig | undefined>(() => {
|
||||
switch (kind) {
|
||||
case 'audioinput':
|
||||
return {
|
||||
icon: RiMicLine,
|
||||
}
|
||||
case 'videoinput':
|
||||
return {
|
||||
icon: RiVideoOnLine,
|
||||
}
|
||||
}
|
||||
}, [kind])
|
||||
|
||||
const isPermissionDeniedOrPrompted = useMemo(() => {
|
||||
if (kind == 'audioinput') {
|
||||
return permissions.isMicrophoneDenied || permissions.isMicrophonePrompted
|
||||
}
|
||||
if (kind == 'videoinput') {
|
||||
return permissions.isCameraDenied || permissions.isCameraPrompted
|
||||
}
|
||||
return false
|
||||
}, [kind, permissions])
|
||||
|
||||
if (!config) return null
|
||||
|
||||
if (isPermissionDeniedOrPrompted || permissions.isLoading) {
|
||||
return (
|
||||
<Select
|
||||
aria-label={t(`${kind}.permissionsNeeded`)}
|
||||
label=""
|
||||
isDisabled={true}
|
||||
items={[]}
|
||||
iconComponent={config?.icon}
|
||||
placeholder={t('selectDevice.permissionsNeeded')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<SelectDevicePermissions
|
||||
id={id}
|
||||
onSubmit={onSubmit}
|
||||
kind={kind}
|
||||
config={config}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,10 +9,12 @@
|
||||
},
|
||||
"join": {
|
||||
"selectDevice": {
|
||||
"loading": "Laden…"
|
||||
"loading": "Laden…",
|
||||
"permissionsNeeded": "Genehmigung erforderlich"
|
||||
},
|
||||
"videoinput": {
|
||||
"choose": "Kamera auswählen",
|
||||
"permissionsNeeded": "Kamera auswählen - genehmigung erforderlich",
|
||||
"disable": "Kamera deaktivieren",
|
||||
"enable": "Kamera aktivieren",
|
||||
"label": "Kamera",
|
||||
@@ -20,6 +22,7 @@
|
||||
},
|
||||
"audioinput": {
|
||||
"choose": "Mikrofon auswählen",
|
||||
"permissionsNeeded": "Mikrofon auswählen - genehmigung erforderlich",
|
||||
"disable": "Mikrofon deaktivieren",
|
||||
"enable": "Mikrofon aktivieren",
|
||||
"label": "Mikrofon"
|
||||
|
||||
@@ -9,10 +9,12 @@
|
||||
},
|
||||
"join": {
|
||||
"selectDevice": {
|
||||
"loading": "Loading…"
|
||||
"loading": "Loading…",
|
||||
"permissionsNeeded": "Permission needed"
|
||||
},
|
||||
"videoinput": {
|
||||
"choose": "Select camera",
|
||||
"permissionsNeeded": "Select camera - permission needed",
|
||||
"disable": "Disable camera",
|
||||
"enable": "Enable camera",
|
||||
"label": "Camera",
|
||||
@@ -20,6 +22,7 @@
|
||||
},
|
||||
"audioinput": {
|
||||
"choose": "Select microphone",
|
||||
"permissionsNeeded": "Select microphone - permission needed",
|
||||
"disable": "Disable microphone",
|
||||
"enable": "Enable microphone",
|
||||
"label": "Microphone"
|
||||
|
||||
@@ -9,10 +9,12 @@
|
||||
},
|
||||
"join": {
|
||||
"selectDevice": {
|
||||
"loading": "Chargement…"
|
||||
"loading": "Chargement…",
|
||||
"permissionsNeeded": "Autorisations nécessaires"
|
||||
},
|
||||
"videoinput": {
|
||||
"choose": "Choisir la webcam",
|
||||
"permissionsNeeded": "Choisir la webcam - autorisations nécessaires",
|
||||
"disable": "Désactiver la webcam",
|
||||
"enable": "Activer la webcam",
|
||||
"label": "Webcam",
|
||||
@@ -20,6 +22,7 @@
|
||||
},
|
||||
"audioinput": {
|
||||
"choose": "Choisir le micro",
|
||||
"permissionsNeeded": "Choisir le micro - autorisations nécessaires",
|
||||
"disable": "Désactiver le micro",
|
||||
"enable": "Activer le micro",
|
||||
"label": "Microphone"
|
||||
|
||||
@@ -9,10 +9,12 @@
|
||||
},
|
||||
"join": {
|
||||
"selectDevice": {
|
||||
"loading": "Bezig met laden…"
|
||||
"loading": "Bezig met laden…",
|
||||
"permissionNeeded": "Toestemming vereist"
|
||||
},
|
||||
"videoinput": {
|
||||
"choose": "Selecteer camera",
|
||||
"permissionNeeded": "Selecteer camera - Toestemming vereist",
|
||||
"disable": "Camera uitschakelen",
|
||||
"enable": "Camera inschakelen",
|
||||
"label": "Camera",
|
||||
@@ -20,6 +22,7 @@
|
||||
},
|
||||
"audioinput": {
|
||||
"choose": "Selecteer microfoon",
|
||||
"permissionNeeded": "Selecteer microfoon - Toestemming vereist",
|
||||
"disable": "Microfoon dempen",
|
||||
"enable": "Microfoon dempen opheffen",
|
||||
"label": "Microfoon"
|
||||
|
||||
Reference in New Issue
Block a user