♻️(frontend) decouple audio/video controls for reorganization clarity
Temporary state separating audio and video controls to improve clarity and prepare for device selection/toggle component reorganization. Work in progress to better structure device-related components before implementing final unified control architecture.
This commit is contained in:
committed by
aleb_the_flash
parent
59e0643dde
commit
40cedba8ae
@@ -0,0 +1,124 @@
|
|||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import {
|
||||||
|
useMediaDeviceSelect,
|
||||||
|
useTrackToggle,
|
||||||
|
UseTrackToggleProps,
|
||||||
|
} from '@livekit/components-react'
|
||||||
|
import { Button, Menu, MenuList } from '@/primitives'
|
||||||
|
import { RiArrowUpSLine, RiMicLine, RiMicOffLine } from '@remixicon/react'
|
||||||
|
import { LocalAudioTrack, LocalVideoTrack, Track } from 'livekit-client'
|
||||||
|
|
||||||
|
import { ToggleDevice } from '@/features/rooms/livekit/components/controls/ToggleDevice.tsx'
|
||||||
|
import { css } from '@/styled-system/css'
|
||||||
|
import { usePersistentUserChoices } from '../../../hooks/usePersistentUserChoices'
|
||||||
|
import { useSnapshot } from 'valtio'
|
||||||
|
import { permissionsStore } from '@/stores/permissions'
|
||||||
|
import { ToggleDeviceConfig } from '../../../config/ToggleDeviceConfig'
|
||||||
|
import Source = Track.Source
|
||||||
|
import * as React from 'react'
|
||||||
|
|
||||||
|
type AudioDevicesControlProps = Omit<
|
||||||
|
UseTrackToggleProps<Source.Microphone>,
|
||||||
|
'source' | 'onChange'
|
||||||
|
> & {
|
||||||
|
track?: LocalAudioTrack | LocalVideoTrack
|
||||||
|
hideMenu?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AudioDevicesControl = ({
|
||||||
|
track,
|
||||||
|
hideMenu,
|
||||||
|
...props
|
||||||
|
}: AudioDevicesControlProps) => {
|
||||||
|
const config: ToggleDeviceConfig = {
|
||||||
|
kind: 'audioinput',
|
||||||
|
iconOn: RiMicLine,
|
||||||
|
iconOff: RiMicOffLine,
|
||||||
|
shortcut: {
|
||||||
|
key: 'd',
|
||||||
|
ctrlKey: true,
|
||||||
|
},
|
||||||
|
longPress: {
|
||||||
|
key: 'Space',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const { t } = useTranslation('rooms', { keyPrefix: 'join' })
|
||||||
|
|
||||||
|
const { saveAudioInputDeviceId, saveAudioInputEnabled } =
|
||||||
|
usePersistentUserChoices()
|
||||||
|
|
||||||
|
const onChange = React.useCallback(
|
||||||
|
(enabled: boolean, isUserInitiated: boolean) =>
|
||||||
|
isUserInitiated ? saveAudioInputEnabled(enabled) : null,
|
||||||
|
[saveAudioInputEnabled]
|
||||||
|
)
|
||||||
|
|
||||||
|
const trackProps = useTrackToggle({
|
||||||
|
source: Source.Microphone,
|
||||||
|
onChange,
|
||||||
|
...props,
|
||||||
|
})
|
||||||
|
|
||||||
|
const permissions = useSnapshot(permissionsStore)
|
||||||
|
const isPermissionDeniedOrPrompted =
|
||||||
|
permissions.isMicrophoneDenied || permissions.isMicrophonePrompted
|
||||||
|
|
||||||
|
const { devices, activeDeviceId, setActiveMediaDevice } =
|
||||||
|
useMediaDeviceSelect({ kind: 'audioinput', track })
|
||||||
|
|
||||||
|
const selectLabel = t('audioinput.choose')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={css({
|
||||||
|
display: 'flex',
|
||||||
|
gap: '1px',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<ToggleDevice
|
||||||
|
{...trackProps}
|
||||||
|
config={config}
|
||||||
|
variant="primaryDark"
|
||||||
|
toggle={trackProps.toggle}
|
||||||
|
isPermissionDeniedOrPrompted={isPermissionDeniedOrPrompted}
|
||||||
|
toggleButtonProps={{
|
||||||
|
...(hideMenu
|
||||||
|
? {
|
||||||
|
groupPosition: undefined,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{!hideMenu && (
|
||||||
|
<Menu variant="dark">
|
||||||
|
<Button
|
||||||
|
isDisabled={isPermissionDeniedOrPrompted}
|
||||||
|
tooltip={selectLabel}
|
||||||
|
aria-label={selectLabel}
|
||||||
|
groupPosition="right"
|
||||||
|
square
|
||||||
|
variant={
|
||||||
|
trackProps.enabled && !isPermissionDeniedOrPrompted
|
||||||
|
? 'primaryDark'
|
||||||
|
: 'error2'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<RiArrowUpSLine />
|
||||||
|
</Button>
|
||||||
|
<MenuList
|
||||||
|
items={devices.map((d) => ({
|
||||||
|
value: d.deviceId,
|
||||||
|
label: d.label,
|
||||||
|
}))}
|
||||||
|
selectedItem={activeDeviceId}
|
||||||
|
onAction={(value) => {
|
||||||
|
setActiveMediaDevice(value as string)
|
||||||
|
saveAudioInputDeviceId(value as string)
|
||||||
|
}}
|
||||||
|
variant="dark"
|
||||||
|
/>
|
||||||
|
</Menu>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import {
|
||||||
|
useMediaDeviceSelect,
|
||||||
|
useTrackToggle,
|
||||||
|
UseTrackToggleProps,
|
||||||
|
} from '@livekit/components-react'
|
||||||
|
import { Button, Menu, MenuList } from '@/primitives'
|
||||||
|
import { RiArrowUpSLine, RiVideoOffLine, RiVideoOnLine } from '@remixicon/react'
|
||||||
|
import { LocalVideoTrack, Track, VideoCaptureOptions } from 'livekit-client'
|
||||||
|
|
||||||
|
import { ToggleDevice } from '@/features/rooms/livekit/components/controls/ToggleDevice'
|
||||||
|
import { css } from '@/styled-system/css'
|
||||||
|
import { usePersistentUserChoices } from '../../../hooks/usePersistentUserChoices'
|
||||||
|
import { BackgroundProcessorFactory } from '../../blur'
|
||||||
|
import { useSnapshot } from 'valtio'
|
||||||
|
import { permissionsStore } from '@/stores/permissions'
|
||||||
|
import { ToggleDeviceConfig } from '../../../config/ToggleDeviceConfig'
|
||||||
|
import Source = Track.Source
|
||||||
|
import * as React from 'react'
|
||||||
|
|
||||||
|
type VideoDeviceControlProps = Omit<
|
||||||
|
UseTrackToggleProps<Source.Camera>,
|
||||||
|
'source' | 'onChange'
|
||||||
|
> & {
|
||||||
|
track?: LocalVideoTrack
|
||||||
|
hideMenu?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const VideoDeviceControl = ({
|
||||||
|
track,
|
||||||
|
hideMenu,
|
||||||
|
...props
|
||||||
|
}: VideoDeviceControlProps) => {
|
||||||
|
const config: ToggleDeviceConfig = {
|
||||||
|
kind: 'videoinput',
|
||||||
|
iconOn: RiVideoOnLine,
|
||||||
|
iconOff: RiVideoOffLine,
|
||||||
|
shortcut: {
|
||||||
|
key: 'e',
|
||||||
|
ctrlKey: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const { t } = useTranslation('rooms', { keyPrefix: 'join' })
|
||||||
|
|
||||||
|
const { userChoices, saveVideoInputDeviceId, saveVideoInputEnabled } =
|
||||||
|
usePersistentUserChoices()
|
||||||
|
|
||||||
|
const onChange = React.useCallback(
|
||||||
|
(enabled: boolean, isUserInitiated: boolean) =>
|
||||||
|
isUserInitiated ? saveVideoInputEnabled(enabled) : null,
|
||||||
|
[saveVideoInputEnabled]
|
||||||
|
)
|
||||||
|
|
||||||
|
const trackProps = useTrackToggle({
|
||||||
|
source: Source.Camera,
|
||||||
|
onChange,
|
||||||
|
...props,
|
||||||
|
})
|
||||||
|
|
||||||
|
const permissions = useSnapshot(permissionsStore)
|
||||||
|
|
||||||
|
const isPermissionDeniedOrPrompted =
|
||||||
|
permissions.isCameraDenied || permissions.isCameraPrompted
|
||||||
|
|
||||||
|
const toggle = () => {
|
||||||
|
/**
|
||||||
|
* We need to make sure that we apply the in-memory processor when re-enabling the camera.
|
||||||
|
* Before, we had the following bug:
|
||||||
|
* 1 - Configure a processor on join screen
|
||||||
|
* 2 - Turn off camera on join screen
|
||||||
|
* 3 - Join the room
|
||||||
|
* 4 - Turn on the camera
|
||||||
|
* 5 - No processor is applied to the camera
|
||||||
|
* Expected: The processor is applied.
|
||||||
|
*
|
||||||
|
* See https://github.com/numerique-gouv/meet/pull/309#issuecomment-2622404121
|
||||||
|
*/
|
||||||
|
const processor = BackgroundProcessorFactory.deserializeProcessor(
|
||||||
|
userChoices.processorSerialized
|
||||||
|
)
|
||||||
|
|
||||||
|
const toggle = trackProps.toggle as (
|
||||||
|
forceState: boolean,
|
||||||
|
captureOptions: VideoCaptureOptions
|
||||||
|
) => Promise<void>
|
||||||
|
|
||||||
|
toggle(!trackProps.enabled, {
|
||||||
|
processor: processor,
|
||||||
|
} as VideoCaptureOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { devices, activeDeviceId, setActiveMediaDevice } =
|
||||||
|
useMediaDeviceSelect({ kind: 'videoinput', track })
|
||||||
|
|
||||||
|
const selectLabel = t('videoinput.choose')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={css({
|
||||||
|
display: 'flex',
|
||||||
|
gap: '1px',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<ToggleDevice
|
||||||
|
{...trackProps}
|
||||||
|
config={config}
|
||||||
|
variant="primaryDark"
|
||||||
|
toggle={toggle}
|
||||||
|
isPermissionDeniedOrPrompted={isPermissionDeniedOrPrompted}
|
||||||
|
toggleButtonProps={{
|
||||||
|
...(hideMenu
|
||||||
|
? {
|
||||||
|
groupPosition: undefined,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{!hideMenu && (
|
||||||
|
<Menu variant="dark">
|
||||||
|
<Button
|
||||||
|
isDisabled={isPermissionDeniedOrPrompted}
|
||||||
|
tooltip={selectLabel}
|
||||||
|
aria-label={selectLabel}
|
||||||
|
groupPosition="right"
|
||||||
|
square
|
||||||
|
variant={
|
||||||
|
trackProps.enabled && !isPermissionDeniedOrPrompted
|
||||||
|
? 'primaryDark'
|
||||||
|
: 'error2'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<RiArrowUpSLine />
|
||||||
|
</Button>
|
||||||
|
<MenuList
|
||||||
|
items={devices.map((d) => ({
|
||||||
|
value: d.deviceId,
|
||||||
|
label: d.label,
|
||||||
|
}))}
|
||||||
|
selectedItem={activeDeviceId}
|
||||||
|
onAction={(value) => {
|
||||||
|
setActiveMediaDevice(value as string)
|
||||||
|
saveVideoInputDeviceId(value as string)
|
||||||
|
}}
|
||||||
|
variant="dark"
|
||||||
|
/>
|
||||||
|
</Menu>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import {
|
|
||||||
useMediaDeviceSelect,
|
|
||||||
useTrackToggle,
|
|
||||||
UseTrackToggleProps,
|
|
||||||
} from '@livekit/components-react'
|
|
||||||
import { Button, Menu, MenuList } from '@/primitives'
|
|
||||||
import { RiArrowUpSLine } from '@remixicon/react'
|
|
||||||
import {
|
|
||||||
LocalAudioTrack,
|
|
||||||
LocalVideoTrack,
|
|
||||||
Track,
|
|
||||||
VideoCaptureOptions,
|
|
||||||
} from 'livekit-client'
|
|
||||||
|
|
||||||
import { ToggleDevice } from '@/features/rooms/livekit/components/controls/ToggleDevice.tsx'
|
|
||||||
import { css } from '@/styled-system/css'
|
|
||||||
import { ButtonRecipeProps } from '@/primitives/buttonRecipe'
|
|
||||||
import { useEffect, useMemo } from 'react'
|
|
||||||
import { usePersistentUserChoices } from '../../hooks/usePersistentUserChoices'
|
|
||||||
import { BackgroundProcessorFactory } from '../blur'
|
|
||||||
import { useSnapshot } from 'valtio'
|
|
||||||
import { permissionsStore } from '@/stores/permissions'
|
|
||||||
import {
|
|
||||||
TOGGLE_DEVICE_CONFIG,
|
|
||||||
ToggleSource,
|
|
||||||
} from '../../config/ToggleDeviceConfig'
|
|
||||||
|
|
||||||
type SelectToggleDeviceProps<T extends ToggleSource> =
|
|
||||||
UseTrackToggleProps<T> & {
|
|
||||||
track?: LocalAudioTrack | LocalVideoTrack
|
|
||||||
initialDeviceId?: string
|
|
||||||
onActiveDeviceChange: (deviceId: string) => void
|
|
||||||
source: ToggleSource
|
|
||||||
variant?: NonNullable<ButtonRecipeProps>['variant']
|
|
||||||
menuVariant?: 'dark' | 'light'
|
|
||||||
hideMenu?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SelectToggleDevice = <T extends ToggleSource>({
|
|
||||||
track,
|
|
||||||
initialDeviceId,
|
|
||||||
onActiveDeviceChange,
|
|
||||||
hideMenu,
|
|
||||||
variant = 'primaryDark',
|
|
||||||
menuVariant = 'light',
|
|
||||||
...props
|
|
||||||
}: SelectToggleDeviceProps<T>) => {
|
|
||||||
const config = TOGGLE_DEVICE_CONFIG[props.source]
|
|
||||||
if (!config) {
|
|
||||||
throw new Error('Invalid source')
|
|
||||||
}
|
|
||||||
const { t } = useTranslation('rooms', { keyPrefix: 'join' })
|
|
||||||
const trackProps = useTrackToggle(props)
|
|
||||||
|
|
||||||
const { userChoices } = usePersistentUserChoices()
|
|
||||||
|
|
||||||
const permissions = useSnapshot(permissionsStore)
|
|
||||||
const isPermissionDeniedOrPrompted = useMemo(() => {
|
|
||||||
switch (config.kind) {
|
|
||||||
case 'audioinput':
|
|
||||||
return (
|
|
||||||
permissions.isMicrophoneDenied || permissions.isMicrophonePrompted
|
|
||||||
)
|
|
||||||
case 'videoinput':
|
|
||||||
return permissions.isCameraDenied || permissions.isCameraPrompted
|
|
||||||
}
|
|
||||||
}, [permissions, config.kind])
|
|
||||||
|
|
||||||
const toggle = () => {
|
|
||||||
if (props.source === Track.Source.Camera) {
|
|
||||||
/**
|
|
||||||
* We need to make sure that we apply the in-memory processor when re-enabling the camera.
|
|
||||||
* Before, we had the following bug:
|
|
||||||
* 1 - Configure a processor on join screen
|
|
||||||
* 2 - Turn off camera on join screen
|
|
||||||
* 3 - Join the room
|
|
||||||
* 4 - Turn on the camera
|
|
||||||
* 5 - No processor is applied to the camera
|
|
||||||
* Expected: The processor is applied.
|
|
||||||
*
|
|
||||||
* See https://github.com/numerique-gouv/meet/pull/309#issuecomment-2622404121
|
|
||||||
*/
|
|
||||||
const processor = BackgroundProcessorFactory.deserializeProcessor(
|
|
||||||
userChoices.processorSerialized
|
|
||||||
)
|
|
||||||
|
|
||||||
const toggle = trackProps.toggle as (
|
|
||||||
forceState: boolean,
|
|
||||||
captureOptions: VideoCaptureOptions
|
|
||||||
) => Promise<void>
|
|
||||||
|
|
||||||
toggle(!trackProps.enabled, {
|
|
||||||
processor: processor,
|
|
||||||
} as VideoCaptureOptions)
|
|
||||||
} else {
|
|
||||||
trackProps.toggle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { devices, activeDeviceId, setActiveMediaDevice } =
|
|
||||||
useMediaDeviceSelect({ kind: config.kind, track })
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When providing only track outside of a room context, activeDeviceId is undefined.
|
|
||||||
* So we need to initialize it with the initialDeviceId.
|
|
||||||
* nb: I don't understand why useMediaDeviceSelect cannot infer it from track device id.
|
|
||||||
*/
|
|
||||||
useEffect(() => {
|
|
||||||
if (initialDeviceId !== undefined) {
|
|
||||||
setActiveMediaDevice(initialDeviceId)
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [setActiveMediaDevice])
|
|
||||||
|
|
||||||
const selectLabel = t('choose', { keyPrefix: `join.${config.kind}` })
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={css({
|
|
||||||
display: 'flex',
|
|
||||||
gap: '1px',
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<ToggleDevice
|
|
||||||
{...trackProps}
|
|
||||||
config={config}
|
|
||||||
variant={variant}
|
|
||||||
toggle={toggle}
|
|
||||||
isPermissionDeniedOrPrompted={isPermissionDeniedOrPrompted}
|
|
||||||
toggleButtonProps={{
|
|
||||||
...(hideMenu
|
|
||||||
? {
|
|
||||||
groupPosition: undefined,
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{!hideMenu && (
|
|
||||||
<Menu variant={menuVariant}>
|
|
||||||
<Button
|
|
||||||
isDisabled={isPermissionDeniedOrPrompted}
|
|
||||||
tooltip={selectLabel}
|
|
||||||
aria-label={selectLabel}
|
|
||||||
groupPosition="right"
|
|
||||||
square
|
|
||||||
variant={
|
|
||||||
trackProps.enabled && !isPermissionDeniedOrPrompted
|
|
||||||
? variant
|
|
||||||
: 'error2'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<RiArrowUpSLine />
|
|
||||||
</Button>
|
|
||||||
<MenuList
|
|
||||||
items={devices.map((d) => ({
|
|
||||||
value: d.deviceId,
|
|
||||||
label: d.label,
|
|
||||||
}))}
|
|
||||||
selectedItem={activeDeviceId}
|
|
||||||
onAction={(value) => {
|
|
||||||
setActiveMediaDevice(value as string)
|
|
||||||
onActiveDeviceChange(value as string)
|
|
||||||
}}
|
|
||||||
variant={menuVariant}
|
|
||||||
/>
|
|
||||||
</Menu>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,6 @@ import { MobileControlBar } from './MobileControlBar'
|
|||||||
import { DesktopControlBar } from './DesktopControlBar'
|
import { DesktopControlBar } from './DesktopControlBar'
|
||||||
import { SettingsDialogProvider } from '../../components/controls/SettingsDialogContext'
|
import { SettingsDialogProvider } from '../../components/controls/SettingsDialogContext'
|
||||||
import { useIsMobile } from '@/utils/useIsMobile'
|
import { useIsMobile } from '@/utils/useIsMobile'
|
||||||
import { usePersistentUserChoices } from '../../hooks/usePersistentUserChoices'
|
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export type ControlBarControls = {
|
export type ControlBarControls = {
|
||||||
@@ -48,53 +47,16 @@ export interface ControlBarProps extends React.HTMLAttributes<HTMLDivElement> {
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export function ControlBar({ onDeviceError }: ControlBarProps) {
|
export function ControlBar({ onDeviceError }: ControlBarProps) {
|
||||||
const {
|
|
||||||
saveAudioInputEnabled,
|
|
||||||
saveVideoInputEnabled,
|
|
||||||
saveAudioInputDeviceId,
|
|
||||||
saveVideoInputDeviceId,
|
|
||||||
} = usePersistentUserChoices()
|
|
||||||
|
|
||||||
const microphoneOnChange = React.useCallback(
|
|
||||||
(enabled: boolean, isUserInitiated: boolean) =>
|
|
||||||
isUserInitiated ? saveAudioInputEnabled(enabled) : null,
|
|
||||||
[saveAudioInputEnabled]
|
|
||||||
)
|
|
||||||
|
|
||||||
const cameraOnChange = React.useCallback(
|
|
||||||
(enabled: boolean, isUserInitiated: boolean) =>
|
|
||||||
isUserInitiated ? saveVideoInputEnabled(enabled) : null,
|
|
||||||
[saveVideoInputEnabled]
|
|
||||||
)
|
|
||||||
|
|
||||||
const barProps = {
|
|
||||||
onDeviceError,
|
|
||||||
microphoneOnChange,
|
|
||||||
cameraOnChange,
|
|
||||||
saveAudioInputDeviceId,
|
|
||||||
saveVideoInputDeviceId,
|
|
||||||
}
|
|
||||||
|
|
||||||
const isMobile = useIsMobile()
|
const isMobile = useIsMobile()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsDialogProvider>
|
<SettingsDialogProvider>
|
||||||
{isMobile ? (
|
{isMobile ? (
|
||||||
<MobileControlBar {...barProps} />
|
<MobileControlBar onDeviceError={onDeviceError} />
|
||||||
) : (
|
) : (
|
||||||
<DesktopControlBar {...barProps} />
|
<DesktopControlBar onDeviceError={onDeviceError} />
|
||||||
)}
|
)}
|
||||||
</SettingsDialogProvider>
|
</SettingsDialogProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ControlBarAuxProps {
|
export type ControlBarAuxProps = Pick<ControlBarProps, 'onDeviceError'>
|
||||||
onDeviceError: ControlBarProps['onDeviceError']
|
|
||||||
microphoneOnChange: (
|
|
||||||
enabled: boolean,
|
|
||||||
isUserInitiated: boolean
|
|
||||||
) => void | null
|
|
||||||
cameraOnChange: (enabled: boolean, isUserInitiated: boolean) => void | null
|
|
||||||
saveAudioInputDeviceId: (deviceId: string) => void
|
|
||||||
saveVideoInputDeviceId: (deviceId: string) => void
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { supportsScreenSharing } from '@livekit/components-core'
|
|||||||
import { ControlBarAuxProps } from './ControlBar'
|
import { ControlBarAuxProps } from './ControlBar'
|
||||||
import { css } from '@/styled-system/css'
|
import { css } from '@/styled-system/css'
|
||||||
import { LeaveButton } from '../../components/controls/LeaveButton'
|
import { LeaveButton } from '../../components/controls/LeaveButton'
|
||||||
import { SelectToggleDevice } from '../../components/controls/SelectToggleDevice'
|
|
||||||
import { Track } from 'livekit-client'
|
import { Track } from 'livekit-client'
|
||||||
import { ReactionsToggle } from '../../components/controls/ReactionsToggle'
|
import { ReactionsToggle } from '../../components/controls/ReactionsToggle'
|
||||||
import { HandToggle } from '../../components/controls/HandToggle'
|
import { HandToggle } from '../../components/controls/HandToggle'
|
||||||
@@ -11,14 +10,10 @@ import { OptionsButton } from '../../components/controls/Options/OptionsButton'
|
|||||||
import { StartMediaButton } from '../../components/controls/StartMediaButton'
|
import { StartMediaButton } from '../../components/controls/StartMediaButton'
|
||||||
import { MoreOptions } from './MoreOptions'
|
import { MoreOptions } from './MoreOptions'
|
||||||
import { useRef } from 'react'
|
import { useRef } from 'react'
|
||||||
|
import { VideoDeviceControl } from '../../components/controls/Device/VideoDeviceControl'
|
||||||
|
import { AudioDevicesControl } from '../../components/controls/Device/AudioDevicesControl'
|
||||||
|
|
||||||
export function DesktopControlBar({
|
export function DesktopControlBar({ onDeviceError }: ControlBarAuxProps) {
|
||||||
onDeviceError,
|
|
||||||
microphoneOnChange,
|
|
||||||
cameraOnChange,
|
|
||||||
saveAudioInputDeviceId,
|
|
||||||
saveVideoInputDeviceId,
|
|
||||||
}: ControlBarAuxProps) {
|
|
||||||
const browserSupportsScreenSharing = supportsScreenSharing()
|
const browserSupportsScreenSharing = supportsScreenSharing()
|
||||||
const desktopControlBarEl = useRef<HTMLDivElement>(null)
|
const desktopControlBarEl = useRef<HTMLDivElement>(null)
|
||||||
return (
|
return (
|
||||||
@@ -53,27 +48,15 @@ export function DesktopControlBar({
|
|||||||
gap: '0.65rem',
|
gap: '0.65rem',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<SelectToggleDevice
|
<AudioDevicesControl
|
||||||
source={Track.Source.Microphone}
|
|
||||||
onChange={microphoneOnChange}
|
|
||||||
onDeviceError={(error) =>
|
onDeviceError={(error) =>
|
||||||
onDeviceError?.({ source: Track.Source.Microphone, error })
|
onDeviceError?.({ source: Track.Source.Microphone, error })
|
||||||
}
|
}
|
||||||
onActiveDeviceChange={(deviceId) =>
|
|
||||||
saveAudioInputDeviceId(deviceId ?? '')
|
|
||||||
}
|
|
||||||
menuVariant="dark"
|
|
||||||
/>
|
/>
|
||||||
<SelectToggleDevice
|
<VideoDeviceControl
|
||||||
source={Track.Source.Camera}
|
|
||||||
onChange={cameraOnChange}
|
|
||||||
onDeviceError={(error) =>
|
onDeviceError={(error) =>
|
||||||
onDeviceError?.({ source: Track.Source.Camera, error })
|
onDeviceError?.({ source: Track.Source.Camera, error })
|
||||||
}
|
}
|
||||||
onActiveDeviceChange={(deviceId) =>
|
|
||||||
saveVideoInputDeviceId(deviceId ?? '')
|
|
||||||
}
|
|
||||||
menuVariant="dark"
|
|
||||||
/>
|
/>
|
||||||
<ReactionsToggle />
|
<ReactionsToggle />
|
||||||
{browserSupportsScreenSharing && (
|
{browserSupportsScreenSharing && (
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { ControlBarAuxProps } from './ControlBar'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { css } from '@/styled-system/css'
|
import { css } from '@/styled-system/css'
|
||||||
import { LeaveButton } from '../../components/controls/LeaveButton'
|
import { LeaveButton } from '../../components/controls/LeaveButton'
|
||||||
import { SelectToggleDevice } from '../../components/controls/SelectToggleDevice'
|
|
||||||
import { Track } from 'livekit-client'
|
import { Track } from 'livekit-client'
|
||||||
import { HandToggle } from '../../components/controls/HandToggle'
|
import { HandToggle } from '../../components/controls/HandToggle'
|
||||||
import { Button } from '@/primitives/Button'
|
import { Button } from '@/primitives/Button'
|
||||||
@@ -24,14 +23,10 @@ import { ResponsiveMenu } from './ResponsiveMenu'
|
|||||||
import { ToolsToggle } from '../../components/controls/ToolsToggle'
|
import { ToolsToggle } from '../../components/controls/ToolsToggle'
|
||||||
import { CameraSwitchButton } from '../../components/controls/CameraSwitchButton'
|
import { CameraSwitchButton } from '../../components/controls/CameraSwitchButton'
|
||||||
import { useConfig } from '@/api/useConfig'
|
import { useConfig } from '@/api/useConfig'
|
||||||
|
import { AudioDevicesControl } from '../../components/controls/Device/AudioDevicesControl'
|
||||||
|
import { VideoDeviceControl } from '../../components/controls/Device/VideoDeviceControl'
|
||||||
|
|
||||||
export function MobileControlBar({
|
export function MobileControlBar({ onDeviceError }: ControlBarAuxProps) {
|
||||||
onDeviceError,
|
|
||||||
microphoneOnChange,
|
|
||||||
cameraOnChange,
|
|
||||||
saveAudioInputDeviceId,
|
|
||||||
saveVideoInputDeviceId,
|
|
||||||
}: ControlBarAuxProps) {
|
|
||||||
const { t } = useTranslation('rooms')
|
const { t } = useTranslation('rooms')
|
||||||
const [isMenuOpened, setIsMenuOpened] = React.useState(false)
|
const [isMenuOpened, setIsMenuOpened] = React.useState(false)
|
||||||
const browserSupportsScreenSharing = supportsScreenSharing()
|
const browserSupportsScreenSharing = supportsScreenSharing()
|
||||||
@@ -62,27 +57,15 @@ export function MobileControlBar({
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<LeaveButton />
|
<LeaveButton />
|
||||||
<SelectToggleDevice
|
<AudioDevicesControl
|
||||||
source={Track.Source.Microphone}
|
|
||||||
onChange={microphoneOnChange}
|
|
||||||
onDeviceError={(error) =>
|
onDeviceError={(error) =>
|
||||||
onDeviceError?.({ source: Track.Source.Microphone, error })
|
onDeviceError?.({ source: Track.Source.Microphone, error })
|
||||||
}
|
}
|
||||||
onActiveDeviceChange={(deviceId) =>
|
|
||||||
saveAudioInputDeviceId(deviceId ?? '')
|
|
||||||
}
|
|
||||||
hideMenu={true}
|
|
||||||
/>
|
/>
|
||||||
<SelectToggleDevice
|
<VideoDeviceControl
|
||||||
source={Track.Source.Camera}
|
|
||||||
onChange={cameraOnChange}
|
|
||||||
onDeviceError={(error) =>
|
onDeviceError={(error) =>
|
||||||
onDeviceError?.({ source: Track.Source.Camera, error })
|
onDeviceError?.({ source: Track.Source.Camera, error })
|
||||||
}
|
}
|
||||||
onActiveDeviceChange={(deviceId) =>
|
|
||||||
saveVideoInputDeviceId(deviceId ?? '')
|
|
||||||
}
|
|
||||||
hideMenu={true}
|
|
||||||
/>
|
/>
|
||||||
<HandToggle />
|
<HandToggle />
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
Reference in New Issue
Block a user