✨(join) add audio output selection
new button to select audio output and pass it to the conference. this is in its own commit because we might not want to add this directly in the code: we can choose output in the join screen but not in the conference screen for now. this might be a bit misleading and better to not have it entirely for now?
This commit is contained in:
@@ -1 +1,2 @@
|
||||
export { useAudioOutputs } from './utils/useAudioOutputs'
|
||||
export { usePersistedMediaDeviceSelect } from './utils/usePersistedMediaDeviceSelect'
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
const getOutputDevices = () => {
|
||||
return navigator.mediaDevices
|
||||
.getUserMedia({ audio: true, video: false })
|
||||
.then(() => navigator.mediaDevices.enumerateDevices())
|
||||
.then((devices) => devices.filter(({ kind }) => kind === 'audiooutput'))
|
||||
}
|
||||
|
||||
/**
|
||||
* custom hook to fetch audio outputs
|
||||
*
|
||||
* this is used instead of livekit's useMediaDevices because the livekit integrated one seems to request
|
||||
* outputs in a weird order, resulting in empty results in firefox if we didn't ask for input before
|
||||
*/
|
||||
export const useAudioOutputs = () => {
|
||||
const [audioOutputs, setAudioOutputs] = useState<MediaDeviceInfo[]>([])
|
||||
useEffect(() => {
|
||||
const retrieveOutputDevices = () => {
|
||||
getOutputDevices()
|
||||
.then(setAudioOutputs)
|
||||
.catch((error) => {
|
||||
console.error('Audio outputs retrieval error :', error)
|
||||
})
|
||||
}
|
||||
retrieveOutputDevices()
|
||||
const onDeviceChange = () => {
|
||||
retrieveOutputDevices()
|
||||
}
|
||||
navigator?.mediaDevices?.addEventListener('devicechange', onDeviceChange)
|
||||
return () => {
|
||||
navigator.mediaDevices.removeEventListener('devicechange', onDeviceChange)
|
||||
}
|
||||
}, [])
|
||||
return audioOutputs
|
||||
}
|
||||
@@ -73,9 +73,16 @@ export const Conference = ({
|
||||
audioCaptureDefaults: {
|
||||
deviceId: userConfig.devices.micDeviceId ?? undefined,
|
||||
},
|
||||
audioOutput: {
|
||||
deviceId: userConfig.devices.speakerDeviceId ?? undefined,
|
||||
},
|
||||
}
|
||||
// do not rely on the userConfig object directly as its reference may change on every render
|
||||
}, [userConfig.devices.cameraDeviceId, userConfig.devices.micDeviceId])
|
||||
}, [
|
||||
userConfig.devices.cameraDeviceId,
|
||||
userConfig.devices.micDeviceId,
|
||||
userConfig.devices.speakerDeviceId,
|
||||
])
|
||||
|
||||
const room = useMemo(() => new Room(roomOptions), [roomOptions])
|
||||
|
||||
|
||||
@@ -27,7 +27,10 @@ import { Track, LocalVideoTrack, LocalAudioTrack } from 'livekit-client'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { usePersistedMediaDeviceSelect } from '@/features/devices'
|
||||
import {
|
||||
usePersistedMediaDeviceSelect,
|
||||
useAudioOutputs,
|
||||
} from '@/features/devices'
|
||||
import { settingsStore, type SettingsState } from '@/features/settings'
|
||||
import { css } from '@/styled-system/css'
|
||||
|
||||
@@ -102,6 +105,7 @@ export const HomemadeJoin = ({
|
||||
track: videoTrack,
|
||||
requestPermissions: true,
|
||||
})
|
||||
const speakerDevices = useAudioOutputs()
|
||||
|
||||
useEffect(() => {
|
||||
if (settingsStore.devices.micDeviceId) {
|
||||
@@ -171,6 +175,27 @@ export const HomemadeJoin = ({
|
||||
)}
|
||||
</Center>
|
||||
<HStack gap={1} justify="center" flexWrap={'wrap'}>
|
||||
{/* audio output dropdown */}
|
||||
<Menu>
|
||||
<Button
|
||||
tooltip={t('chooseSpeaker')}
|
||||
aria-label={t('chooseSpeaker')}
|
||||
>
|
||||
<RiVolumeUpLine />
|
||||
<RiArrowDropDownLine />
|
||||
</Button>
|
||||
<MenuList
|
||||
items={speakerDevices.map((d) => ({
|
||||
value: d.deviceId,
|
||||
label: d.label,
|
||||
}))}
|
||||
selectedItem={settingsSnap.devices.speakerDeviceId}
|
||||
onAction={(value) => {
|
||||
settingsStore.devices.speakerDeviceId = value as string
|
||||
}}
|
||||
/>
|
||||
</Menu>
|
||||
|
||||
{/* audio input toggle + dropdown */}
|
||||
<HStack gap={0}>
|
||||
<ToggleButton
|
||||
|
||||
@@ -4,6 +4,10 @@ import { devtools } from 'valtio/utils'
|
||||
export type SettingsState = {
|
||||
username: string | undefined
|
||||
devices: {
|
||||
/**
|
||||
* MediaDeviceInfo id
|
||||
*/
|
||||
speakerDeviceId: string | undefined
|
||||
/**
|
||||
* MediaDeviceInfo id
|
||||
*/
|
||||
@@ -28,6 +32,7 @@ export const settingsStore = proxy<SettingsState>(
|
||||
: {
|
||||
username: undefined,
|
||||
devices: {
|
||||
speakerDeviceId: undefined,
|
||||
micDeviceId: undefined,
|
||||
cameraDeviceId: undefined,
|
||||
enableMic: false,
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"camlabel": "",
|
||||
"chooseCamera": "",
|
||||
"chooseMic": "",
|
||||
"chooseSpeaker": "",
|
||||
"heading": "",
|
||||
"joinLabel": "",
|
||||
"joinMeeting": "",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"camlabel": "",
|
||||
"chooseCamera": "Select camera",
|
||||
"chooseMic": "Select microphone",
|
||||
"chooseSpeaker": "Select speakers",
|
||||
"heading": "Verify your settings",
|
||||
"joinLabel": "",
|
||||
"joinMeeting": "Join meeting",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"camlabel": "",
|
||||
"chooseCamera": "Choisir la webcam",
|
||||
"chooseMic": "Choisir le micro",
|
||||
"chooseSpeaker": "Choisir la sortie audio",
|
||||
"heading": "Vérifiez vos paramètres",
|
||||
"joinLabel": "",
|
||||
"joinMeeting": "Rejoindre la réjoindre",
|
||||
|
||||
Reference in New Issue
Block a user