mirror of
https://github.com/suitenumerique/meet.git
synced 2026-08-29 11:47:15 +00:00
🐛(frontend) harden speaker test against missing sinks and play errors
- Only call `setSinkId` when supported and the device is actually enumerated: LiveKit can fall back to a stale id on browsers (e.g. WebKit) that expose no such device, making `setSinkId` throw `NotFoundError`. - Await `audio.play()` and reset the playing state on failure, to avoid a stuck button and an unhandled rejection. - Use an absolute `/sounds/uprise.mp3` URL so the asset resolves regardless of the current SPA route.
This commit is contained in:
@@ -12,6 +12,7 @@ and this project adheres to
|
|||||||
|
|
||||||
- 📈(frontend) downgrade unreachable external home URL from error to event
|
- 📈(frontend) downgrade unreachable external home URL from error to event
|
||||||
- 🐛(frontend) handle 401 responses when syncing user preferences
|
- 🐛(frontend) handle 401 responses when syncing user preferences
|
||||||
|
- 🐛(frontend) harden speaker test against missing sinks and play errors
|
||||||
|
|
||||||
## [1.26.0] - 2026-08-12
|
## [1.26.0] - 2026-08-12
|
||||||
|
|
||||||
|
|||||||
@@ -3,27 +3,30 @@ import { useEffect, useRef, useState } from 'react'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useMediaDeviceSelect } from '@livekit/components-react'
|
import { useMediaDeviceSelect } from '@livekit/components-react'
|
||||||
import { reportError } from '@/features/analytics/telemetry'
|
import { reportError } from '@/features/analytics/telemetry'
|
||||||
|
import { canTestAudioOutput } from '@/features/rooms/utils/canTestAudioOutput'
|
||||||
|
|
||||||
export const SoundTester = () => {
|
export const SoundTester = () => {
|
||||||
const { t } = useTranslation('settings')
|
const { t } = useTranslation('settings')
|
||||||
const [isPlaying, setIsPlaying] = useState(false)
|
const [isPlaying, setIsPlaying] = useState(false)
|
||||||
const audioRef = useRef<HTMLAudioElement>(null)
|
const audioRef = useRef<HTMLAudioElement>(null)
|
||||||
|
|
||||||
const { activeDeviceId } = useMediaDeviceSelect({ kind: 'audiooutput' })
|
const { devices, activeDeviceId } = useMediaDeviceSelect({
|
||||||
|
kind: 'audiooutput',
|
||||||
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const updateActiveId = async (deviceId: string) => {
|
if (!canTestAudioOutput() || !activeDeviceId) return
|
||||||
try {
|
if (!devices.some((device) => device.deviceId === activeDeviceId)) return
|
||||||
await audioRef?.current?.setSinkId(deviceId)
|
audioRef.current?.setSinkId(activeDeviceId).catch((error) => {
|
||||||
} catch (error) {
|
if (error instanceof DOMException && error.name === 'NotFoundError') {
|
||||||
reportError(
|
return
|
||||||
'device_switch_failure',
|
|
||||||
new Error(`Error setting sinkId: ${error}`)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
reportError(
|
||||||
updateActiveId(activeDeviceId)
|
'device_switch_failure',
|
||||||
}, [activeDeviceId])
|
new Error(`Error setting sinkId: ${error}`)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}, [devices, activeDeviceId])
|
||||||
|
|
||||||
// prevent pausing the sound
|
// prevent pausing the sound
|
||||||
navigator.mediaSession.setActionHandler('pause', function () {})
|
navigator.mediaSession.setActionHandler('pause', function () {})
|
||||||
@@ -32,9 +35,13 @@ export const SoundTester = () => {
|
|||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
variant="secondaryText"
|
variant="secondaryText"
|
||||||
onPress={() => {
|
onPress={async () => {
|
||||||
audioRef?.current?.play()
|
try {
|
||||||
setIsPlaying(true)
|
await audioRef?.current?.play()
|
||||||
|
setIsPlaying(true)
|
||||||
|
} catch {
|
||||||
|
setIsPlaying(false)
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
size="sm"
|
size="sm"
|
||||||
isDisabled={isPlaying}
|
isDisabled={isPlaying}
|
||||||
@@ -48,7 +55,7 @@ export const SoundTester = () => {
|
|||||||
{/* eslint-disable jsx-a11y/media-has-caption */}
|
{/* eslint-disable jsx-a11y/media-has-caption */}
|
||||||
<audio
|
<audio
|
||||||
ref={audioRef}
|
ref={audioRef}
|
||||||
src="sounds/uprise.mp3"
|
src="/sounds/uprise.mp3"
|
||||||
onEnded={() => setIsPlaying(false)}
|
onEnded={() => setIsPlaying(false)}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import type { NotificationType } from '@/features/notifications/NotificationType
|
|||||||
// fixme - handle dynamic audio output changes
|
// fixme - handle dynamic audio output changes
|
||||||
export const useNotificationSound = () => {
|
export const useNotificationSound = () => {
|
||||||
const notificationsSnap = useSnapshot(notificationsStore)
|
const notificationsSnap = useSnapshot(notificationsStore)
|
||||||
const [play] = useSound('./sounds/notifications.mp3', {
|
const [play] = useSound('/sounds/notifications.mp3', {
|
||||||
sprite: {
|
sprite: {
|
||||||
participantJoined: [0, 1150],
|
participantJoined: [0, 1150],
|
||||||
handRaised: [1400, 180],
|
handRaised: [1400, 180],
|
||||||
|
|||||||
+1
-1
@@ -126,7 +126,7 @@ export const OutputSoundTester = ({
|
|||||||
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
|
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
|
||||||
<audio
|
<audio
|
||||||
ref={audioRef}
|
ref={audioRef}
|
||||||
src="sounds/uprise.mp3"
|
src="/sounds/uprise.mp3"
|
||||||
onEnded={() => setIsPlaying(false)}
|
onEnded={() => setIsPlaying(false)}
|
||||||
/>
|
/>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
|
|||||||
Reference in New Issue
Block a user