diff --git a/src/frontend/src/features/rooms/components/Join.tsx b/src/frontend/src/features/rooms/components/Join.tsx
index e65b78f6..fdd6a2b3 100644
--- a/src/frontend/src/features/rooms/components/Join.tsx
+++ b/src/frontend/src/features/rooms/components/Join.tsx
@@ -7,7 +7,7 @@ import { LocalVideoTrack, Track } from 'livekit-client'
import { H } from '@/primitives/H'
import { Field } from '@/primitives/Field'
import { Button, Dialog, Text, Form } from '@/primitives'
-import { HStack, VStack } from '@/styled-system/jsx'
+import { VStack } from '@/styled-system/jsx'
import { Heading } from 'react-aria-components'
import { RiImageCircleAiFill } from '@remixicon/react'
import {
@@ -27,6 +27,7 @@ import { Spinner } from '@/primitives/Spinner'
import { ApiAccessLevel } from '../api/ApiRoom'
import { useLoginHint } from '@/hooks/useLoginHint'
import { ToggleDeviceJoin } from '@/features/rooms/components/ToggleDeviceJoin'
+import { SelectDeviceJoin } from '@/features/rooms/components/SelectDeviceJoin'
const onError = (e: Error) => console.error('ERROR', e)
@@ -119,6 +120,7 @@ export const Join = ({
audioEnabled,
videoEnabled,
audioDeviceId,
+ audioOutputDeviceId,
videoDeviceId,
processorSerialized,
username,
@@ -126,11 +128,14 @@ export const Join = ({
saveAudioInputEnabled,
saveVideoInputEnabled,
saveAudioInputDeviceId,
+ saveAudioOutputDeviceId,
saveVideoInputDeviceId,
saveUsername,
saveProcessorSerialized,
} = usePersistentUserChoices()
+ // fix - permission issue, when permission not set doesn't refresh
+ // fix - when mic change, camera blink
const tracks = usePreviewTracks(
{
audio: { deviceId: audioDeviceId },
@@ -351,6 +356,7 @@ export const Join = ({
-
- saveAudioInputEnabled(enabled)}
- onDeviceError={(error) => console.error(error)}
- onActiveDeviceChange={(deviceId) =>
- saveAudioInputDeviceId(deviceId ?? '')
- }
- variant="tertiary"
- />
- saveVideoInputEnabled(enabled)}
- onDeviceError={(error) => console.error(error)}
- onActiveDeviceChange={(deviceId) =>
- saveVideoInputDeviceId(deviceId ?? '')
- }
- variant="tertiary"
- />
-
+
diff --git a/src/frontend/src/features/rooms/components/SelectDeviceJoin.tsx b/src/frontend/src/features/rooms/components/SelectDeviceJoin.tsx
new file mode 100644
index 00000000..38071066
--- /dev/null
+++ b/src/frontend/src/features/rooms/components/SelectDeviceJoin.tsx
@@ -0,0 +1,69 @@
+import { RiMicLine, RiVideoOnLine, RiVolumeDownLine } from '@remixicon/react'
+import { Select } from '@/primitives/Select.tsx'
+import { useMemo } from 'react'
+import { useMediaDeviceSelect } from '@livekit/components-react'
+import { useTranslation } from 'react-i18next'
+
+type DeviceItems = Array<{ value: string; label: string }>
+
+export const SelectDeviceJoin = ({
+ id,
+ onSubmit,
+ kind,
+}: {
+ id?: string
+ onSubmit?: (id: string) => void
+ kind: MediaDeviceKind
+}) => {
+ const { t } = useTranslation('rooms', { keyPrefix: 'join' })
+
+ const config = useMemo(() => {
+ switch (kind) {
+ case 'audioinput':
+ return {
+ icon: RiMicLine,
+ }
+ case 'audiooutput':
+ return {
+ icon: RiVolumeDownLine,
+ }
+ case 'videoinput':
+ return {
+ icon: RiVideoOnLine,
+ }
+ }
+ }, [kind])
+
+ const getDefaultSelectedKey = (items: DeviceItems) => {
+ if (!items || items.length === 0) return
+ const defaultItem =
+ items.find((item) => item.value === 'default') || items[0]
+ return defaultItem.value
+ }
+
+ const {
+ devices: devices,
+ activeDeviceId: activeDeviceId,
+ setActiveMediaDevice: setActiveMediaDevice,
+ } = useMediaDeviceSelect({ kind, requestPermissions: false })
+
+ const items: DeviceItems = devices.map((d) => ({
+ value: d.deviceId,
+ label: d.label,
+ }))
+
+ return (
+