mirror of
https://github.com/suitenumerique/meet.git
synced 2026-08-30 20:29:07 +00:00
wip observe media device error on room
This commit is contained in:
@@ -52,3 +52,89 @@ export const reportError = (
|
|||||||
console.warn(`[${logCode}]`, e, extraInfo)
|
console.warn(`[${logCode}]`, e, extraInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DeviceSnapshot {
|
||||||
|
cam_count: number
|
||||||
|
mic_count: number
|
||||||
|
out_count: number
|
||||||
|
labels_visible: boolean
|
||||||
|
saved_cam_present: boolean | null
|
||||||
|
saved_mic_present: boolean | null
|
||||||
|
saved_video_device_id_set: boolean
|
||||||
|
saved_audio_device_id_set: boolean
|
||||||
|
audio_enabled: boolean | null
|
||||||
|
video_enabled: boolean | null
|
||||||
|
cam_permission: PermissionState | 'unknown'
|
||||||
|
mic_permission: PermissionState | 'unknown'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Reads the persisted LiveKit user choices without importing the store. */
|
||||||
|
const readPersistedChoices = (): {
|
||||||
|
videoDeviceId?: string
|
||||||
|
audioDeviceId?: string
|
||||||
|
videoEnabled?: boolean
|
||||||
|
audioEnabled?: boolean
|
||||||
|
} => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(localStorage.getItem('lk-user-choices') ?? '{}')
|
||||||
|
} catch {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryPermission = async (
|
||||||
|
name: 'camera' | 'microphone'
|
||||||
|
): Promise<PermissionState | 'unknown'> => {
|
||||||
|
try {
|
||||||
|
const status = await navigator.permissions.query({
|
||||||
|
name: name as PermissionName,
|
||||||
|
})
|
||||||
|
return status.state
|
||||||
|
} catch {
|
||||||
|
return 'unknown'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deviceSnapshot = async (): Promise<DeviceSnapshot> => {
|
||||||
|
const choices = readPersistedChoices()
|
||||||
|
let devices: MediaDeviceInfo[] = []
|
||||||
|
try {
|
||||||
|
devices = await navigator.mediaDevices.enumerateDevices()
|
||||||
|
} catch {
|
||||||
|
/* snapshot stays partial */
|
||||||
|
}
|
||||||
|
const ofKind = (k: MediaDeviceKind) => devices.filter((d) => d.kind === k)
|
||||||
|
const present = (k: MediaDeviceKind, id?: string) =>
|
||||||
|
id ? ofKind(k).some((d) => d.deviceId === id) : null
|
||||||
|
|
||||||
|
const [cam_permission, mic_permission] = await Promise.all([
|
||||||
|
queryPermission('camera'),
|
||||||
|
queryPermission('microphone'),
|
||||||
|
])
|
||||||
|
|
||||||
|
return {
|
||||||
|
cam_count: ofKind('videoinput').length,
|
||||||
|
mic_count: ofKind('audioinput').length,
|
||||||
|
out_count: ofKind('audiooutput').length,
|
||||||
|
labels_visible: devices.some((d) => !!d.label),
|
||||||
|
saved_cam_present: present('videoinput', choices.videoDeviceId),
|
||||||
|
saved_mic_present: present('audioinput', choices.audioDeviceId),
|
||||||
|
saved_video_device_id_set: !!choices.videoDeviceId,
|
||||||
|
saved_audio_device_id_set: !!choices.audioDeviceId,
|
||||||
|
audio_enabled: choices.audioEnabled ?? null,
|
||||||
|
video_enabled: choices.videoEnabled ?? null,
|
||||||
|
cam_permission,
|
||||||
|
mic_permission,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const captureMediaEvent = async (
|
||||||
|
event:
|
||||||
|
| 'media-device-error'
|
||||||
|
| 'media-acquisition'
|
||||||
|
| 'media-device-topology'
|
||||||
|
| 'media-device-success',
|
||||||
|
props: Record<string, unknown>
|
||||||
|
) => {
|
||||||
|
captureEvent(event, { ...props, ...(await deviceSnapshot()) })
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,7 +26,11 @@ import { css } from '@/styled-system/css'
|
|||||||
import { BackgroundProcessorFactory } from '../livekit/components/blur'
|
import { BackgroundProcessorFactory } from '../livekit/components/blur'
|
||||||
import { LocalUserChoices } from '@/stores/userChoices'
|
import { LocalUserChoices } from '@/stores/userChoices'
|
||||||
import { MediaDeviceErrorAlert } from './MediaDeviceErrorAlert'
|
import { MediaDeviceErrorAlert } from './MediaDeviceErrorAlert'
|
||||||
import { captureEvent, reportError } from '@/features/analytics/telemetry'
|
import {
|
||||||
|
captureEvent,
|
||||||
|
reportError,
|
||||||
|
captureMediaEvent,
|
||||||
|
} from '@/features/analytics/telemetry'
|
||||||
import { useConfig } from '@/api/useConfig'
|
import { useConfig } from '@/api/useConfig'
|
||||||
import { isFireFox } from '@/utils/livekit'
|
import { isFireFox } from '@/utils/livekit'
|
||||||
import { useIsMobile } from '@/utils/useIsMobile'
|
import { useIsMobile } from '@/utils/useIsMobile'
|
||||||
@@ -235,6 +239,7 @@ export const Conference = ({
|
|||||||
})}
|
})}
|
||||||
onError={(e) => {
|
onError={(e) => {
|
||||||
reportError('livekit_room_error', e, {
|
reportError('livekit_room_error', e, {
|
||||||
|
path: 'connect_publish',
|
||||||
failure: MediaDeviceFailure.getFailure(e) ?? 'not-a-device-error',
|
failure: MediaDeviceFailure.getFailure(e) ?? 'not-a-device-error',
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
@@ -297,8 +302,19 @@ export const Conference = ({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onMediaDeviceFailure={(e, kind) => {
|
onMediaDeviceFailure={(e, kind) => {
|
||||||
if (e == MediaDeviceFailure.DeviceInUse && !!kind) {
|
if (!e || !kind) return
|
||||||
setMediaDeviceError({ error: e, kind })
|
void captureMediaEvent('media-device-error', {
|
||||||
|
log_code: 'media_devices_error_event',
|
||||||
|
path: 'connect_publish',
|
||||||
|
failure: e,
|
||||||
|
kind,
|
||||||
|
})
|
||||||
|
switch (e) {
|
||||||
|
case MediaDeviceFailure.DeviceInUse:
|
||||||
|
setMediaDeviceError({ error: e, kind })
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user