wip soundtester

This commit is contained in:
lebaudantoine
2026-08-09 15:19:43 +02:00
parent 1fca767855
commit 38530facba
7 changed files with 112 additions and 5 deletions
@@ -0,0 +1,90 @@
import { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { RiVolumeUpLine } from '@remixicon/react'
import { css } from '@/styled-system/css'
import { reportError } from '@/features/analytics/telemetry'
import { canTestAudioOutput } from '@/features/rooms/utils/canTestAudioOutput'
/**
* Speaker test rendered as the audiooutput menu footer — sibling of
* AudioLevelGauge for audioinput (Google Meet UX: the test lives inside
* the device dropdown, the popover stays open while it plays).
*
* No track exists for outputs: the test IS playing a bundled file through
* the selected sink via setSinkId (same mechanism as the settings
* SoundTester). Needs no permission, so the never-auto-prompt policy
* holds. Following `sinkId` mid-playback re-routes the sound live, which
* makes A/B-ing speakers work.
*/
type OutputSoundTesterProps = {
/** The device the test should play through (the select's current key). */
sinkId?: string
variant?: 'light' | 'dark'
}
export const OutputSoundTester = ({
sinkId,
variant = 'light',
}: OutputSoundTesterProps) => {
const { t } = useTranslation('rooms', { keyPrefix: 'selectDevice' })
const audioRef = useRef<HTMLAudioElement>(null)
const [isPlaying, setIsPlaying] = useState(false)
useEffect(() => {
if (!sinkId || !canTestAudioOutput()) return
audioRef.current?.setSinkId(sinkId).catch((error) => {
reportError('device_switch_failure', error, {
kind: 'audiooutput',
context: 'test sound setSinkId',
})
})
}, [sinkId])
return (
<div
className={css({
display: 'flex',
alignItems: 'center',
gap: '0.5rem',
paddingX: '0.75rem',
paddingY: '0.5rem',
borderTop: '1px solid',
borderColor: variant === 'dark' ? 'primaryDark.300' : 'gray.200',
})}
>
<button
type="button"
onClick={() => {
audioRef.current
?.play()
.then(() => setIsPlaying(true))
.catch(() => {})
}}
disabled={isPlaying}
className={css({
display: 'flex',
alignItems: 'center',
gap: '0.5rem',
width: 'full',
cursor: 'pointer',
fontSize: '0.875rem',
color: variant === 'dark' ? 'white' : 'control.text',
_disabled: {
cursor: 'default',
color: variant === 'dark' ? 'primaryDark.100' : 'primary',
},
})}
>
<RiVolumeUpLine size={18} aria-hidden />
{isPlaying ? t('audiooutput.testing') : t('audiooutput.test')}
</button>
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
<audio
ref={audioRef}
src="sounds/uprise.mp3"
onEnded={() => setIsPlaying(false)}
/>
</div>
)
}
@@ -7,6 +7,8 @@ import { useCannotUseDevice } from '../../../hooks/useCannotUseDevice'
import { useDeviceIcons } from '@/features/rooms/livekit/hooks/useDeviceIcons'
import type { LocalAudioTrack } from 'livekit-client'
import { AudioLevelGauge } from './AudioLevelGauge'
import { OutputSoundTester } from './OutputSoundTester'
import { canTestAudioOutput } from '@/features/rooms/utils/canTestAudioOutput'
type DeviceItems = Array<{ value: string; label: string }>
@@ -81,6 +83,11 @@ const SelectDevicePermissions = <T extends string | number>({
menuFooter={
kind === 'audioinput' ? (
<AudioLevelGauge track={track} variant={props.variant} />
) : kind === 'audiooutput' && canTestAudioOutput() ? (
<OutputSoundTester
sinkId={selectedKey as string}
variant={props.variant}
/>
) : undefined
}
{...props}
@@ -0,0 +1,3 @@
export const canTestAudioOutput = () =>
typeof HTMLMediaElement !== 'undefined' &&
'setSinkId' in HTMLMediaElement.prototype // Safari: no output routing
+3 -2
View File
@@ -40,7 +40,9 @@
},
"audiooutput": {
"choose": "Audioausgabe auswählen",
"permissionsNeeded": "Audioausgabe auswählen Berechtigung erforderlich"
"permissionsNeeded": "Audioausgabe auswählen Berechtigung erforderlich",
"test": "Lautsprecher testen",
"testing": "Testton wird abgespielt…"
}
},
"join": {
@@ -328,7 +330,6 @@
}
}
},
"faceLandmarks": {
"title": "Visuelle Effekte",
"glasses": {
+3 -1
View File
@@ -40,7 +40,9 @@
},
"audiooutput": {
"choose": "Select speaker",
"permissionsNeeded": "Select speaker - permission needed"
"permissionsNeeded": "Select speaker - permission needed",
"test": "Test speakers",
"testing": "Playing test sound…"
}
},
"join": {
+3 -1
View File
@@ -40,7 +40,9 @@
},
"audiooutput": {
"choose": "Choisir le haut-parleur",
"permissionsNeeded": "Choisir le haut-parleur - autorisations nécessaires"
"permissionsNeeded": "Choisir le haut-parleur - autorisations nécessaires",
"test": "Tester les haut-parleurs",
"testing": "Lecture du son de test…"
}
},
"join": {
+3 -1
View File
@@ -40,7 +40,9 @@
},
"audiooutput": {
"choose": "Selecteer luidspreker",
"permissionsNeeded": "Selecteer luidspreker - Toestemming vereist"
"permissionsNeeded": "Selecteer luidspreker - Toestemming vereist",
"test": "Luidsprekers testen",
"testing": "Testgeluid wordt afgespeeld…"
}
},
"join": {