mirror of
https://github.com/suitenumerique/meet.git
synced 2026-08-13 12:17:24 +00:00
wip soundtester
This commit is contained in:
+90
@@ -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
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user