✨(frontend) add the AudioTab component
First draft, it lacks important features, as a visual indicator when the mic is active, and a trigger to test the audio output. I made some heuristics (e.g. default output/input, etc..) to ship a first version of this setting tabs that should work good enough to create value for our current users. Please refer to the inline comments.
This commit is contained in:
committed by
aleb_the_flash
parent
74b296aa37
commit
4d5aec9a49
@@ -1,5 +1,5 @@
|
||||
import { Dialog, type DialogProps } from '@/primitives'
|
||||
import { Tab, Tabs, TabPanel, TabList } from '@/primitives/Tabs.tsx'
|
||||
import { Tab, Tabs, TabList } from '@/primitives/Tabs.tsx'
|
||||
import { css } from '@/styled-system/css'
|
||||
import { text } from '@/primitives/Text.tsx'
|
||||
import { Heading } from 'react-aria-components'
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from '@remixicon/react'
|
||||
import { AccountTab } from './tabs/AccountTab'
|
||||
import { GeneralTab } from '@/features/settings/components/tabs/GeneralTab.tsx'
|
||||
import { AudioTab } from '@/features/settings/components/tabs/AudioTab.tsx'
|
||||
|
||||
const tabsStyle = css({
|
||||
maxHeight: '40.625rem', // fixme size copied from meet settings modal
|
||||
@@ -68,9 +69,7 @@ export const SettingsDialogExtended = (props: SettingsDialogExtended) => {
|
||||
</div>
|
||||
<div className={tabPanelContainerStyle}>
|
||||
<AccountTab id="1" onOpenChange={props.onOpenChange} />
|
||||
<TabPanel flex id="2">
|
||||
There are your audio settings
|
||||
</TabPanel>
|
||||
<AudioTab id="2" />
|
||||
<GeneralTab id="3" />
|
||||
</div>
|
||||
</Tabs>
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
import { DialogProps, Field, H } from '@/primitives'
|
||||
|
||||
import { TabPanel, TabPanelProps } from '@/primitives/Tabs'
|
||||
import { useMediaDeviceSelect } from '@livekit/components-react'
|
||||
import { isSafari } from '@/utils/livekit'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export type AudioTabProps = Pick<DialogProps, 'onOpenChange'> &
|
||||
Pick<TabPanelProps, 'id'>
|
||||
|
||||
type DeviceItems = Array<{ value: string; label: string }>
|
||||
|
||||
export const AudioTab = ({ id }: AudioTabProps) => {
|
||||
const { t } = useTranslation('settings')
|
||||
|
||||
const {
|
||||
devices: devicesOut,
|
||||
activeDeviceId: activeDeviceIdOut,
|
||||
setActiveMediaDevice: setActiveMediaDeviceOut,
|
||||
} = useMediaDeviceSelect({ kind: 'audiooutput' })
|
||||
|
||||
const {
|
||||
devices: devicesIn,
|
||||
activeDeviceId: activeDeviceIdIn,
|
||||
setActiveMediaDevice: setActiveMediaDeviceIn,
|
||||
} = useMediaDeviceSelect({ kind: 'audioinput' })
|
||||
|
||||
const itemsOut: DeviceItems = devicesOut.map((d) => ({
|
||||
value: d.deviceId,
|
||||
label: d.label,
|
||||
}))
|
||||
|
||||
const itemsIn: DeviceItems = devicesIn.map((d) => ({
|
||||
value: d.deviceId,
|
||||
label: d.label,
|
||||
}))
|
||||
|
||||
// The Permissions API is not fully supported in Firefox and Safari, and attempting to use it for microphone permissions
|
||||
// may raise an error. As a workaround, we infer microphone permission status by checking if the list of audio input
|
||||
// devices (devicesIn) is non-empty. If the list has one or more devices, we assume the user has granted microphone access.
|
||||
const isMicEnabled = devicesIn?.length > 0
|
||||
|
||||
const disabledProps = isMicEnabled
|
||||
? {}
|
||||
: {
|
||||
placeholder: t('audio.permissionsRequired'),
|
||||
isDisabled: true,
|
||||
defaultSelectedKey: undefined,
|
||||
}
|
||||
|
||||
// No API to directly query the default audio device; this function heuristically finds it.
|
||||
// Returns the item with value 'default' if present; otherwise, returns the first item in the list.
|
||||
const getDefaultSelectedKey = (items: DeviceItems) => {
|
||||
if (!items || items.length === 0) return
|
||||
const defaultItem =
|
||||
items.find((item) => item.value === 'default') || items[0]
|
||||
return defaultItem.value
|
||||
}
|
||||
|
||||
return (
|
||||
<TabPanel padding={'md'} flex id={id}>
|
||||
<H lvl={2}>{t('audio.microphone.heading')}</H>
|
||||
<Field
|
||||
type="select"
|
||||
label={t('audio.microphone.label')}
|
||||
items={itemsIn}
|
||||
defaultSelectedKey={activeDeviceIdIn || getDefaultSelectedKey(itemsIn)}
|
||||
onSelectionChange={(key) => setActiveMediaDeviceIn(key as string)}
|
||||
{...disabledProps}
|
||||
/>
|
||||
{/* Safari has a known limitation where its implementation of 'enumerateDevices' does not include audio output devices.
|
||||
To prevent errors or an empty selection list, we only render the speakers selection field on non-Safari browsers. */}
|
||||
{!isSafari() && (
|
||||
<>
|
||||
<H lvl={2}>{t('audio.speakers.heading')}</H>
|
||||
<Field
|
||||
type="select"
|
||||
label={t('audio.speakers.label')}
|
||||
items={itemsOut}
|
||||
defaultSelectedKey={
|
||||
activeDeviceIdOut || getDefaultSelectedKey(itemsOut)
|
||||
}
|
||||
onSelectionChange={(key) => setActiveMediaDeviceOut(key as string)}
|
||||
{...disabledProps}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</TabPanel>
|
||||
)
|
||||
}
|
||||
@@ -6,6 +6,17 @@
|
||||
"nameLabel": "",
|
||||
"authentication": ""
|
||||
},
|
||||
"audio": {
|
||||
"microphone": {
|
||||
"heading": "",
|
||||
"label": ""
|
||||
},
|
||||
"speakers": {
|
||||
"heading": "",
|
||||
"label": ""
|
||||
},
|
||||
"permissionsRequired": ""
|
||||
},
|
||||
"dialog": {
|
||||
"heading": ""
|
||||
},
|
||||
|
||||
@@ -6,6 +6,17 @@
|
||||
"nameLabel": "Votre Nom",
|
||||
"authentication": "Authentication"
|
||||
},
|
||||
"audio": {
|
||||
"microphone": {
|
||||
"heading": "Microphone",
|
||||
"label": "Select your audio input"
|
||||
},
|
||||
"speakers": {
|
||||
"heading": "Speakers",
|
||||
"label": "Select your audio output"
|
||||
},
|
||||
"permissionsRequired": "Permissions required"
|
||||
},
|
||||
"dialog": {
|
||||
"heading": "Settings"
|
||||
},
|
||||
|
||||
@@ -6,6 +6,17 @@
|
||||
"nameLabel": "Votre Nom",
|
||||
"authentication": "Authentification"
|
||||
},
|
||||
"audio": {
|
||||
"microphone": {
|
||||
"heading": "Micro",
|
||||
"label": "Sélectionner votre entrée audio"
|
||||
},
|
||||
"speakers": {
|
||||
"heading": "Haut-parleurs",
|
||||
"label": "Sélectionner votre sortie audio"
|
||||
},
|
||||
"permissionsRequired": "Autorisations nécessaires"
|
||||
},
|
||||
"dialog": {
|
||||
"heading": "Paramètres"
|
||||
},
|
||||
|
||||
@@ -32,6 +32,12 @@ const StyledButton = styled(Button, {
|
||||
'&[data-pressed]': {
|
||||
backgroundColor: 'control.hover',
|
||||
},
|
||||
// fixme disabled style is being overridden by placeholder one and needs refinement.
|
||||
'&[data-disabled]': {
|
||||
color: 'default.subtle-text',
|
||||
borderColor: 'gray.200',
|
||||
boxShadow: '0 1px 2px rgba(0 0 0 / 0.02)',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user