mirror of
https://github.com/suitenumerique/meet.git
synced 2026-08-24 17:27:02 +00:00
🐛(frontend) handle Firefox/Windows AbortError on device start
Some versions of Firefox on Windows do not raise the `NotReadableError` that LiveKit expects when the camera or microphone fails to start. Instead, they surface an `AbortError` with a message explaining the browser could not start the video or microphone input. This case was observed in PostHog error tracking and was not handled, so users hit an unhelpful failure state. Detect that specific `AbortError` and route it through the same device-in-use / not-readable handling as the standard error, so users get a clear explanation.
This commit is contained in:
committed by
aleb_the_flash
parent
d723c14d84
commit
2ec3f54532
@@ -33,6 +33,7 @@ and this project adheres to
|
||||
- 🐛(frontend) hoist mute confirmation dialog to VideoConference level
|
||||
- 🐛(frontend) fix joined notification tile no longer rendering properly
|
||||
- 🐛(frontend) handle device-in-use errors on Chrome / Windows 10
|
||||
- 🐛(frontend) handle Firefox/Windows AbortError on device start
|
||||
|
||||
## [1.27.0] - 2026-08-14
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
type RoomOptions,
|
||||
VideoPresets,
|
||||
} from 'livekit-client'
|
||||
import { getMediaDeviceFailure } from '@/features/rooms/livekit/utils/mediaPermissions'
|
||||
import { keys } from '@/api/queryKeys'
|
||||
import { queryClient } from '@/api/queryClient'
|
||||
import { Screen } from '@/layout/Screen'
|
||||
@@ -231,7 +232,7 @@ export const Conference = ({
|
||||
backgroundColor: 'primaryDark.50 !important',
|
||||
})}
|
||||
onError={(e) => {
|
||||
const failure = MediaDeviceFailure.getFailure(e)
|
||||
const failure = getMediaDeviceFailure(e)
|
||||
if (failure && failure !== MediaDeviceFailure.Other) return
|
||||
|
||||
// connect() was aborted by a disconnect() before the join completed
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
type PermissionKind,
|
||||
} from '@/stores/permissions'
|
||||
import {
|
||||
getMediaDeviceFailure,
|
||||
noteDeviceReady,
|
||||
onMediaPermissionError,
|
||||
} from '../utils/mediaPermissions'
|
||||
@@ -83,7 +84,7 @@ function useWarmupPermissions(): WarmupState {
|
||||
bothReady()
|
||||
} catch (error) {
|
||||
if (
|
||||
MediaDeviceFailure.getFailure(error as Error) ===
|
||||
getMediaDeviceFailure(error as Error) ===
|
||||
MediaDeviceFailure.PermissionDenied &&
|
||||
!isSystemPermissionError(error)
|
||||
) {
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
syncDeviceAvailability,
|
||||
} from '@/stores/deviceAvailability'
|
||||
import { captureMediaEvent } from '@/features/analytics/telemetry'
|
||||
import { getMediaDeviceFailure } from '../utils/mediaPermissions'
|
||||
import { getOS } from '@/utils/os'
|
||||
|
||||
type MediaDeviceAlert = {
|
||||
@@ -63,7 +64,7 @@ export const useWatchMediaDeviceErrors = (): MediaDeviceAlert & {
|
||||
|
||||
useEffect(() => {
|
||||
const onDeviceError = (error: Error, kind?: MediaDeviceKind) => {
|
||||
const failure = MediaDeviceFailure.getFailure(error)
|
||||
const failure = getMediaDeviceFailure(error)
|
||||
if (!failure) return
|
||||
if (failure != MediaDeviceFailure.Other) {
|
||||
void captureMediaEvent('media-device-error', {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { isWeb } from '@livekit/components-core'
|
||||
import { MediaDeviceFailure, Track } from 'livekit-client'
|
||||
import { getMediaDeviceFailure } from '../utils/mediaPermissions'
|
||||
import React, { useState } from 'react'
|
||||
import {
|
||||
ConnectionStateToast,
|
||||
@@ -101,7 +102,7 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
|
||||
}
|
||||
}
|
||||
|
||||
if (MediaDeviceFailure.getFailure(error) != MediaDeviceFailure.Other) {
|
||||
if (getMediaDeviceFailure(error) !== MediaDeviceFailure.Other) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,27 @@ import { getOS } from '@/utils/os'
|
||||
*/
|
||||
export type MediaPath = 'join_preview' | 'room'
|
||||
|
||||
/**
|
||||
* LiveKit only maps NotReadableError/TrackStartError to DeviceInUse.
|
||||
* Firefox reports a device held by another app as
|
||||
* `AbortError: Starting videoinput failed` (or audioinput), which LiveKit
|
||||
* classifies as Other. Normalise it here; every classification in the app
|
||||
* should go through this instead of MediaDeviceFailure.getFailure.
|
||||
*/
|
||||
export const getMediaDeviceFailure = (
|
||||
error: Error
|
||||
): MediaDeviceFailure | undefined => {
|
||||
const failure = MediaDeviceFailure.getFailure(error)
|
||||
if (
|
||||
failure === MediaDeviceFailure.Other &&
|
||||
error.name === 'AbortError' &&
|
||||
/^Starting (video|audio)input failed/i.test(error.message)
|
||||
) {
|
||||
return MediaDeviceFailure.DeviceInUse
|
||||
}
|
||||
return failure
|
||||
}
|
||||
|
||||
export const PERMISSION_KIND: Record<
|
||||
'audioinput' | 'videoinput',
|
||||
PermissionKind
|
||||
@@ -40,7 +61,7 @@ export const onMediaPermissionError = (
|
||||
kind?: PermissionKind,
|
||||
path: MediaPath = 'join_preview'
|
||||
) => {
|
||||
const failure = MediaDeviceFailure.getFailure(e)
|
||||
const failure = getMediaDeviceFailure(e)
|
||||
|
||||
if (failure === MediaDeviceFailure.PermissionDenied) {
|
||||
void classifyPermissionError(e, kind).then((scope) => {
|
||||
|
||||
Reference in New Issue
Block a user