mirror of
https://github.com/suitenumerique/meet.git
synced 2026-10-11 15:50:47 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 531774fbda | |||
| 86d97b427c | |||
| b0bb5bc61b | |||
| 3773e43d93 |
@@ -18,6 +18,7 @@ and this project adheres to
|
|||||||
- ⚡️(backend) reduce domain queries on the application token endpoint
|
- ⚡️(backend) reduce domain queries on the application token endpoint
|
||||||
- ♻️️️(backend) use a dedicated auth scheme for LiveKit token auth
|
- ♻️️️(backend) use a dedicated auth scheme for LiveKit token auth
|
||||||
- ♻️(all) stop relying on cookies for the lobby flow
|
- ♻️(all) stop relying on cookies for the lobby flow
|
||||||
|
- ♿️(frontend) show a visible focus outline on menu items #1797
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
@@ -25,6 +26,9 @@ and this project adheres to
|
|||||||
- 🔒️(frontend) upgrade tiff to fix CVE-2026-4775
|
- 🔒️(frontend) upgrade tiff to fix CVE-2026-4775
|
||||||
- 🔒️(backend) bind accepted lobby entries to the current username
|
- 🔒️(backend) bind accepted lobby entries to the current username
|
||||||
- 🔒️(backend) sign lobby participant id
|
- 🔒️(backend) sign lobby participant id
|
||||||
|
- 🐛(frontend) omit the default speaker id sent to LiveKit
|
||||||
|
- 🐛(frontend) play the speaker test when no output is picked
|
||||||
|
- 🐛(frontend) keep the admin panel in place when changing a setting #1804
|
||||||
|
|
||||||
## [1.34.0] - 2026-10-07
|
## [1.34.0] - 2026-10-07
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import { userStore } from '@/stores/user'
|
|||||||
import { WatchMediaDeviceErrors } from './WatchMediaDeviceErrors'
|
import { WatchMediaDeviceErrors } from './WatchMediaDeviceErrors'
|
||||||
import { MeetDevtools } from '@/features/devtools'
|
import { MeetDevtools } from '@/features/devtools'
|
||||||
import { VOICE_AUDIO_CONSTRAINTS } from '@/features/rooms/livekit/utils/constants'
|
import { VOICE_AUDIO_CONSTRAINTS } from '@/features/rooms/livekit/utils/constants'
|
||||||
|
import { resolveAudioOutputDeviceId } from '@/features/rooms/utils/resolveAudioOutputDeviceId'
|
||||||
|
|
||||||
export const Conference = ({
|
export const Conference = ({
|
||||||
roomId,
|
roomId,
|
||||||
@@ -120,7 +121,7 @@ export const Conference = ({
|
|||||||
...VOICE_AUDIO_CONSTRAINTS,
|
...VOICE_AUDIO_CONSTRAINTS,
|
||||||
},
|
},
|
||||||
audioOutput: {
|
audioOutput: {
|
||||||
deviceId: userConfig.audioOutputDeviceId ?? undefined,
|
deviceId: resolveAudioOutputDeviceId(userConfig.audioOutputDeviceId),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
// do not rely on the userConfig object directly as its reference may change on every render
|
// do not rely on the userConfig object directly as its reference may change on every render
|
||||||
|
|||||||
+8
-5
@@ -4,6 +4,7 @@ import { RiVolumeUpLine } from '@remixicon/react'
|
|||||||
import { styled } from '@/styled-system/jsx'
|
import { styled } from '@/styled-system/jsx'
|
||||||
import { Button } from '@/primitives'
|
import { Button } from '@/primitives'
|
||||||
import { canTestAudioOutput } from '@/features/rooms/utils/canTestAudioOutput'
|
import { canTestAudioOutput } from '@/features/rooms/utils/canTestAudioOutput'
|
||||||
|
import { resolveAudioOutputDeviceId } from '@/features/rooms/utils/resolveAudioOutputDeviceId'
|
||||||
|
|
||||||
// Speaker test in the audiooutput menu footer (Meet-style UX). Outputs have
|
// Speaker test in the audiooutput menu footer (Meet-style UX). Outputs have
|
||||||
// no track: the test plays a bundled file through the selected sink, and
|
// no track: the test plays a bundled file through the selected sink, and
|
||||||
@@ -66,6 +67,8 @@ export const OutputSoundTester = ({
|
|||||||
const audioRef = useRef<HTMLAudioElement>(null)
|
const audioRef = useRef<HTMLAudioElement>(null)
|
||||||
const [isPlaying, setIsPlaying] = useState(false)
|
const [isPlaying, setIsPlaying] = useState(false)
|
||||||
|
|
||||||
|
const outputId = resolveAudioOutputDeviceId(sinkId)
|
||||||
|
|
||||||
const latestSinkIdRef = useRef(sinkId)
|
const latestSinkIdRef = useRef(sinkId)
|
||||||
latestSinkIdRef.current = sinkId
|
latestSinkIdRef.current = sinkId
|
||||||
|
|
||||||
@@ -79,15 +82,15 @@ export const OutputSoundTester = ({
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!sinkId || !canTestAudioOutput()) return
|
if (!outputId || !canTestAudioOutput()) return
|
||||||
audioRef.current?.setSinkId(sinkId).catch(() => {
|
audioRef.current?.setSinkId(outputId).catch(() => {
|
||||||
// Re-routing failed (stale or unplugged device): stop the test rather
|
// Re-routing failed (stale or unplugged device): stop the test rather
|
||||||
// than keep playing through the previous sink.
|
// than keep playing through the previous sink.
|
||||||
if (latestSinkIdRef.current === sinkId) {
|
if (latestSinkIdRef.current === sinkId) {
|
||||||
stopPlayback()
|
stopPlayback()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, [sinkId, stopPlayback])
|
}, [outputId, sinkId, stopPlayback])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const audio = audioRef.current
|
const audio = audioRef.current
|
||||||
@@ -108,8 +111,8 @@ export const OutputSoundTester = ({
|
|||||||
// Confirm routing before starting: a no-op when already routed,
|
// Confirm routing before starting: a no-op when already routed,
|
||||||
// but rejects on a stale device id, so the test never plays
|
// but rejects on a stale device id, so the test never plays
|
||||||
// through the wrong sink.
|
// through the wrong sink.
|
||||||
if (sinkId && canTestAudioOutput()) {
|
if (outputId && canTestAudioOutput()) {
|
||||||
await audio.setSinkId(sinkId)
|
await audio.setSinkId(outputId)
|
||||||
}
|
}
|
||||||
await audio.play()
|
await audio.play()
|
||||||
setIsPlaying(true)
|
setIsPlaying(true)
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* `'default'` means "use the system output". Firefox has no device with that
|
||||||
|
* id, so setSinkId('default') rejects. Omit it and the browser keeps the OS
|
||||||
|
* default, which is the same result.
|
||||||
|
*/
|
||||||
|
export const resolveAudioOutputDeviceId = (
|
||||||
|
deviceId: string | undefined
|
||||||
|
): string | undefined => {
|
||||||
|
if (!deviceId || deviceId === 'default') return undefined
|
||||||
|
return deviceId
|
||||||
|
}
|
||||||
@@ -9,6 +9,8 @@ import { type StyledVariantProps } from '@/styled-system/types'
|
|||||||
const StyledRadio = styled(RACRadio, {
|
const StyledRadio = styled(RACRadio, {
|
||||||
base: {
|
base: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
// anchor the visually hidden input, so it scrolls with the radio
|
||||||
|
position: 'relative',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 0.375,
|
gap: 0.375,
|
||||||
forcedColorAdjust: 'none',
|
forcedColorAdjust: 'none',
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import { RiCheckLine, RiCloseFill } from '@remixicon/react'
|
|||||||
const StyledSwitch = styled(RACSwitch, {
|
const StyledSwitch = styled(RACSwitch, {
|
||||||
base: {
|
base: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
// anchor the visually hidden input, so it scrolls with the switch
|
||||||
|
position: 'relative',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: '0.571rem',
|
gap: '0.571rem',
|
||||||
color: 'black',
|
color: 'black',
|
||||||
|
|||||||
@@ -25,12 +25,15 @@ export const menuRecipe = sva({
|
|||||||
'&[data-focused]': {
|
'&[data-focused]': {
|
||||||
color: 'primary.text',
|
color: 'primary.text',
|
||||||
backgroundColor: 'primaryDark.100',
|
backgroundColor: 'primaryDark.100',
|
||||||
outline: 'none!',
|
|
||||||
},
|
},
|
||||||
'&[data-hovered]': {
|
'&[data-hovered]': {
|
||||||
color: 'primary.text',
|
color: 'primary.text',
|
||||||
backgroundColor: 'primaryDark.100',
|
backgroundColor: 'primaryDark.100',
|
||||||
outline: 'none!',
|
},
|
||||||
|
'&[data-focus-visible]': {
|
||||||
|
outline: '2px solid',
|
||||||
|
outlineColor: 'focusRing',
|
||||||
|
outlineOffset: '2px',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -49,6 +52,10 @@ export const menuRecipe = sva({
|
|||||||
dark: {
|
dark: {
|
||||||
item: {
|
item: {
|
||||||
color: 'white',
|
color: 'white',
|
||||||
|
'&[data-focus-visible]': {
|
||||||
|
outlineColor: 'white!',
|
||||||
|
outlineOffset: '2px!',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user