diff --git a/src/frontend/src/features/rooms/components/Join.tsx b/src/frontend/src/features/rooms/components/Join.tsx
index 87354cc7..e4e2014a 100644
--- a/src/frontend/src/features/rooms/components/Join.tsx
+++ b/src/frontend/src/features/rooms/components/Join.tsx
@@ -5,7 +5,6 @@ import { Screen } from '@/layout/Screen'
import { useEffect, useMemo, useRef, useState } from 'react'
import { LocalVideoTrack, Track } from 'livekit-client'
import { H } from '@/primitives/H'
-import { SelectToggleDevice } from '../livekit/components/controls/SelectToggleDevice'
import { Field } from '@/primitives/Field'
import { Button, Dialog, Text, Form } from '@/primitives'
import { VStack } from '@/styled-system/jsx'
@@ -29,6 +28,8 @@ import { ApiAccessLevel } from '../api/ApiRoom'
import { useLoginHint } from '@/hooks/useLoginHint'
import { useSnapshot } from 'valtio'
import { openPermissionsDialog, permissionsStore } from '@/stores/permissions'
+import { ToggleDevice } from './join/ToggleDevice'
+import { SelectDevice } from './join/SelectDevice'
const onError = (e: Error) => console.error('ERROR', e)
@@ -502,6 +503,33 @@ export const Join = ({
)}
+
+ saveAudioInputEnabled(enabled)}
+ onDeviceError={(error) => console.error(error)}
+ />
+ saveVideoInputEnabled(enabled)}
+ onDeviceError={(error) => console.error(error)}
+ />
+
-
-
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/join/SelectDevice.tsx b/src/frontend/src/features/rooms/components/join/SelectDevice.tsx
new file mode 100644
index 00000000..67e17e58
--- /dev/null
+++ b/src/frontend/src/features/rooms/components/join/SelectDevice.tsx
@@ -0,0 +1,74 @@
+import {
+ RemixiconComponentType,
+ RiMicLine,
+ RiVideoOnLine,
+} from '@remixicon/react'
+import { useTranslation } from 'react-i18next'
+import { useMediaDeviceSelect } from '@livekit/components-react'
+import { useMemo } from 'react'
+import { Select } from '@/primitives/Select'
+
+type DeviceItems = Array<{ value: string; label: string }>
+
+type DeviceConfig = {
+ icon: RemixiconComponentType
+}
+
+type SelectDeviceProps = {
+ id?: string
+ onSubmit?: (id: string) => void
+ kind: MediaDeviceKind
+}
+
+export const SelectDevice = ({ id, onSubmit, kind }: SelectDeviceProps) => {
+ const { t } = useTranslation('rooms', { keyPrefix: 'join' })
+
+ const config = useMemo(() => {
+ switch (kind) {
+ case 'audioinput':
+ return {
+ icon: RiMicLine,
+ }
+ 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
+ .filter((d) => !!d.deviceId)
+ .map((d) => ({
+ value: d.deviceId,
+ label: d.label,
+ }))
+
+ return (
+