Первоначальная версия VidConf
This commit is contained in:
110
frontend/src/components/room/DeviceSettingsDialog.tsx
Normal file
110
frontend/src/components/room/DeviceSettingsDialog.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import { X } from 'lucide-react'
|
||||
import { useMediaDeviceSelect, usePersistentUserChoices } from '@livekit/components-react'
|
||||
import { useToast } from '@/components/ui/ToastProvider'
|
||||
|
||||
interface DeviceSettingsDialogProps {
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
/** Человекочитаемая подпись пункта списка устройств — `label` пуст, пока нет разрешения на медиа. */
|
||||
function deviceLabel(device: MediaDeviceInfo, index: number, fallback: string): string {
|
||||
return device.label || `${fallback} ${index + 1}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Диалог «Настройки устройств» — два селекта
|
||||
* на хуках `@livekit/components-react`: список устройств и переключение —
|
||||
* целиком в `useMediaDeviceSelect` (сама подписана на
|
||||
* `RoomEvent.MediaDevicesChanged`), персист выбора — в `usePersistentUserChoices`
|
||||
* (localStorage, читается заново при следующем входе в комнату — см.
|
||||
* `RoomPage.tsx`, `options` пропс `LiveKitRoom`).
|
||||
*
|
||||
* ДОЛЖЕН рендериться внутри `<LiveKitRoom>`: `useMediaDeviceSelect` без явно
|
||||
* переданного `room` берёт активную комнату из `RoomContext` — вне контекста
|
||||
* он создал бы отдельный, ни с чем не связанный `Room()` и переключал бы
|
||||
* устройство «в никуда».
|
||||
*/
|
||||
export function DeviceSettingsDialog({ onClose }: DeviceSettingsDialogProps) {
|
||||
const toast = useToast()
|
||||
const { saveAudioInputDeviceId, saveVideoInputDeviceId } = usePersistentUserChoices()
|
||||
const mic = useMediaDeviceSelect({ kind: 'audioinput' })
|
||||
const camera = useMediaDeviceSelect({ kind: 'videoinput' })
|
||||
|
||||
async function handleMicChange(deviceId: string) {
|
||||
try {
|
||||
await mic.setActiveMediaDevice(deviceId)
|
||||
saveAudioInputDeviceId(deviceId)
|
||||
} catch {
|
||||
// activeDeviceId хука — источник истины, состояние селекта само не меняется.
|
||||
toast.show('Не удалось переключить микрофон — устройство занято или отключено', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCameraChange(deviceId: string) {
|
||||
try {
|
||||
await camera.setActiveMediaDevice(deviceId)
|
||||
saveVideoInputDeviceId(deviceId)
|
||||
} catch {
|
||||
toast.show('Не удалось переключить камеру — устройство занято или отключено', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="room-modal-overlay"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="device-settings-title"
|
||||
onClick={onClose}
|
||||
>
|
||||
<div className="room-modal-panel" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="room-modal-head">
|
||||
<h2 id="device-settings-title">Настройки устройств</h2>
|
||||
<button type="button" className="room-modal-close" aria-label="Закрыть" onClick={onClose}>
|
||||
<X className="lucide" style={{ width: 16, height: 16 }} aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="room-field">
|
||||
<label htmlFor="device-settings-mic">Микрофон</label>
|
||||
<select
|
||||
id="device-settings-mic"
|
||||
value={mic.activeDeviceId}
|
||||
onChange={(e) => void handleMicChange(e.target.value)}
|
||||
>
|
||||
{/* Заглушка на случай, пока activeDeviceId не совпадает ни с одним
|
||||
устройством из списка (нет разрешения на медиа/список ещё не
|
||||
перечислен) — без неё controlled-select рассинхронизируется с
|
||||
DOM (ни одна из настоящих option не соответствует value). */}
|
||||
<option value="" disabled>
|
||||
Определяется…
|
||||
</option>
|
||||
{mic.devices.map((device, index) => (
|
||||
<option key={device.deviceId} value={device.deviceId}>
|
||||
{deviceLabel(device, index, 'Микрофон')}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="room-field">
|
||||
<label htmlFor="device-settings-camera">Камера</label>
|
||||
<select
|
||||
id="device-settings-camera"
|
||||
value={camera.activeDeviceId}
|
||||
onChange={(e) => void handleCameraChange(e.target.value)}
|
||||
>
|
||||
<option value="" disabled>
|
||||
Определяется…
|
||||
</option>
|
||||
{camera.devices.map((device, index) => (
|
||||
<option key={device.deviceId} value={device.deviceId}>
|
||||
{deviceLabel(device, index, 'Камера')}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user