diff --git a/src/frontend/src/features/rooms/livekit/components/controls/Device/AudioLevelGauge.tsx b/src/frontend/src/features/rooms/livekit/components/controls/Device/AudioLevelGauge.tsx index 124dd161..df67e68f 100644 --- a/src/frontend/src/features/rooms/livekit/components/controls/Device/AudioLevelGauge.tsx +++ b/src/frontend/src/features/rooms/livekit/components/controls/Device/AudioLevelGauge.tsx @@ -1,16 +1,10 @@ -import { useEffect, useRef, useState } from 'react' -import { - createAudioAnalyser, - LocalAudioTrack, - TrackEvent, -} from 'livekit-client' +import { useEffect, useState } from 'react' +import { LocalAudioTrack, TrackEvent } from 'livekit-client' +import { useTrackVolume } from '@livekit/components-react' import { useTranslation } from 'react-i18next' import { RiMicLine, RiMicOffLine } from '@remixicon/react' -import { Text } from '@/primitives' import { styled } from '@/styled-system/jsx' - -const LEVEL_BOOST = 0.8 -const DECAY_PER_FRAME = 0.04 +import { Text } from '@/primitives' const StyledContainer = styled('div', { base: { @@ -25,8 +19,8 @@ const StyledContainer = styled('div', { variants: { theme: { light: { - borderColor: 'colors.greyscale.250', - color: 'colors.greyscale.600', + borderColor: 'greyscale.250', + color: 'greyscale.600', }, dark: { borderColor: 'rgba(255 255 255 / 0.2)', @@ -46,10 +40,10 @@ const StyledGaugeContainer = styled('div', { variants: { theme: { light: { - backgroundColor: 'rgba(255 255 255 / 0.25)', + backgroundColor: 'greyscale.250', }, dark: { - backgroundColor: 'colors.greyscale.250', + backgroundColor: 'rgba(255 255 255 / 0.25)', }, }, }, @@ -76,18 +70,14 @@ const StyledGauge = styled('div', { }, }) +type Theme = 'light' | 'dark' + type AudioLevelGaugeProps = { track?: LocalAudioTrack - variant?: 'light' | 'dark' + variant?: Theme } -export const AudioLevelGauge = ({ - track, - variant = 'light', -}: AudioLevelGaugeProps) => { - const { t } = useTranslation('rooms', { keyPrefix: 'selectDevice' }) - const fillRef = useRef(null) - +const useIsTrackMuted = (track?: LocalAudioTrack) => { const [isMuted, setIsMuted] = useState(() => track?.isMuted ?? true) useEffect(() => { @@ -106,52 +96,43 @@ export const AudioLevelGauge = ({ } }, [track]) - useEffect(() => { - if (!track || isMuted || !track.mediaStreamTrack) return + return isMuted +} - let rafId: number - let smoothed = 0 - let calculateVolume: (() => number) | undefined - let cleanupAnalyser: (() => Promise) | undefined +const LevelBar = ({ + track, + theme, +}: { + track: LocalAudioTrack + theme: Theme +}) => { + const { t } = useTranslation('rooms', { keyPrefix: 'selectDevice' }) + const volume = useTrackVolume(track, { + fftSize: 256, + smoothingTimeConstant: 0.7, + }) + const level = Math.min(1, volume) - const setupAnalyser = () => { - cleanupAnalyser?.() - try { - const analyser = createAudioAnalyser(track, { - fftSize: 256, - smoothingTimeConstant: 0.7, - }) - calculateVolume = analyser.calculateVolume - cleanupAnalyser = analyser.cleanup - } catch (e) { - console.error('Failed to create audio analyser', e) - } - } - - setupAnalyser() - - track.on(TrackEvent.Restarted, setupAnalyser) - - const update = () => { - const volume = calculateVolume?.() ?? 0 - // Fast attack, slow release, like Google Meet. - smoothed = - volume > smoothed ? volume : Math.max(0, smoothed - DECAY_PER_FRAME) - const level = Math.min(1, smoothed * LEVEL_BOOST) - if (fillRef.current) { - fillRef.current.style.transform = `scaleX(${level})` - } - rafId = requestAnimationFrame(update) - } - rafId = requestAnimationFrame(update) - - return () => { - cancelAnimationFrame(rafId) - track.off(TrackEvent.Restarted, setupAnalyser) - cleanupAnalyser?.() - } - }, [track, isMuted]) + return ( + <> +