fix(auth): превью и проверка устройств на шаге "Подключиться к конференции"
Продолжение 33: раньше превью показывалось только на карточке "Как вас зовут?" (guest-info), а авторизованный пользователь, входящий через /join, этот шаг вообще не проходит (сразу connecting) — значит, никогда не видел проверку устройств и не мог задать enterWithVideo/Audio. Теперь превью и кнопки — на обоих шагах (input и guest-info), с одним непрерывным потоком: hook enabled/release эффект завязаны на общий флаг "мы на одном из шагов с превью", а не на конкретный step, иначе переход input -> guest-info выглядел бы для эффекта как уход с гашением камеры. Заодно нашёл и починил реальную грабли: <video> на разных шагах — это разные DOM-узлы (разные позиции в JSX), обычный ref.current не пережил бы переезд между ними — поток остаётся жив, но картинка гаснет в чёрный прямоугольник. videoRef хука теперь callback-ref, переподключающий уже открытый поток к любому новому узлу автоматически.
This commit is contained in:
@@ -6,9 +6,16 @@ export type DeviceCheckStatus = 'idle' | 'pending' | 'granted' | 'denied'
|
||||
interface UseDeviceCheckAccessResult {
|
||||
videoStatus: DeviceCheckStatus
|
||||
audioStatus: DeviceCheckStatus
|
||||
/** Привязать к `<video>` превью — хук сам управляет `srcObject` по мере
|
||||
* появления/остановки потока камеры. */
|
||||
videoRef: React.RefObject<HTMLVideoElement | null>
|
||||
/** Привязать к `ref` `<video>` превью — коллбэк, НЕ объект-реф: карточка
|
||||
* превью рендерится в разных местах JSX-дерева на разных шагах
|
||||
* (`input`/`guest-info` в `JoinPage`), и React монтирует для каждого места
|
||||
* СВОЙ DOM-узел `<video>`, хотя тип компонента один и тот же — обычный
|
||||
* `ref.current` продолжал бы указывать на старый (уже отмонтированный)
|
||||
* узел. Коллбэк вызывается при каждом монтировании нового узла и сам
|
||||
* подключает уже открытый поток (см. докстринг хука) — без этого переход
|
||||
* между шагами давал бы на месте превью чёрный прямоугольник: поток жив,
|
||||
* но не подключён к новому элементу. */
|
||||
videoRef: (node: HTMLVideoElement | null) => void
|
||||
/** Повесить на карточку (`onPointerDown`/`onKeyDown`) — первое взаимодействие
|
||||
* внутри неё запускает запрос доступа. Идемпотентно, повторные вызовы —
|
||||
* no-op (см. `requestedRef`). */
|
||||
@@ -88,7 +95,17 @@ export function useDeviceCheckAccess(enabled: boolean): UseDeviceCheckAccessResu
|
||||
const toggleAudioEnabled = useCallback(() => setAudioEnabled((v) => !v), [])
|
||||
|
||||
const videoStreamRef = useRef<MediaStream | null>(null)
|
||||
const videoRef = useRef<HTMLVideoElement | null>(null)
|
||||
const videoNodeRef = useRef<HTMLVideoElement | null>(null)
|
||||
// См. докстринг `videoRef` в интерфейсе выше — коллбэк-реф, переподключает
|
||||
// уже открытый поток к КАЖДОМУ новому DOM-узлу `<video>` сам, без этого
|
||||
// переход между шагами с превью терял бы картинку (но не поток — камера
|
||||
// продолжала бы физически работать, просто без видимого превью).
|
||||
const videoRef = useCallback((node: HTMLVideoElement | null) => {
|
||||
videoNodeRef.current = node
|
||||
if (node) {
|
||||
node.srcObject = videoStreamRef.current
|
||||
}
|
||||
}, [])
|
||||
const requestedRef = useRef(false)
|
||||
|
||||
// Полный сброс — не только остановка треков, но и статусы/флаги/охрана
|
||||
@@ -103,8 +120,8 @@ export function useDeviceCheckAccess(enabled: boolean): UseDeviceCheckAccessResu
|
||||
stream.getTracks().forEach((track) => track.stop())
|
||||
videoStreamRef.current = null
|
||||
}
|
||||
if (videoRef.current) {
|
||||
videoRef.current.srcObject = null
|
||||
if (videoNodeRef.current) {
|
||||
videoNodeRef.current.srcObject = null
|
||||
}
|
||||
requestedRef.current = false
|
||||
setVideoStatus('idle')
|
||||
@@ -127,8 +144,8 @@ export function useDeviceCheckAccess(enabled: boolean): UseDeviceCheckAccessResu
|
||||
try {
|
||||
const stream = await openStream('video', userChoicesRef.current.videoDeviceId)
|
||||
videoStreamRef.current = stream
|
||||
if (videoRef.current) {
|
||||
videoRef.current.srcObject = stream
|
||||
if (videoNodeRef.current) {
|
||||
videoNodeRef.current.srcObject = stream
|
||||
}
|
||||
setVideoStatus('granted')
|
||||
// Первичная верификация сразу показывает превью — «включено» по факту
|
||||
@@ -152,8 +169,8 @@ export function useDeviceCheckAccess(enabled: boolean): UseDeviceCheckAccessResu
|
||||
stream.getTracks().forEach((track) => track.stop())
|
||||
videoStreamRef.current = null
|
||||
}
|
||||
if (videoRef.current) {
|
||||
videoRef.current.srcObject = null
|
||||
if (videoNodeRef.current) {
|
||||
videoNodeRef.current.srcObject = null
|
||||
}
|
||||
setVideoEnabled(false)
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user