From 6804b4aa8e5d94c7f3a79389a698bbb502aeb59b Mon Sep 17 00:00:00 2001 From: Nathan Vasse Date: Wed, 18 Dec 2024 17:13:57 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(front)=20add=20switch=20camera=20on?= =?UTF-8?q?=20mobile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We want to have a more simple control bar and give the user to switch its back / front camera from the responsive menu. --- .../controls/CameraSwitchButton.tsx | 123 ++++++++++++++++++ .../controls/SelectToggleDevice.tsx | 62 +++++---- .../components/controls/ToggleDevice.tsx | 4 + .../prefabs/ControlBar/MobileControlBar.tsx | 6 +- src/frontend/src/locales/en/rooms.json | 3 +- src/frontend/src/locales/fr/rooms.json | 3 +- 6 files changed, 174 insertions(+), 27 deletions(-) create mode 100644 src/frontend/src/features/rooms/livekit/components/controls/CameraSwitchButton.tsx diff --git a/src/frontend/src/features/rooms/livekit/components/controls/CameraSwitchButton.tsx b/src/frontend/src/features/rooms/livekit/components/controls/CameraSwitchButton.tsx new file mode 100644 index 00000000..e75f1f13 --- /dev/null +++ b/src/frontend/src/features/rooms/livekit/components/controls/CameraSwitchButton.tsx @@ -0,0 +1,123 @@ +import { Button } from '@/primitives' +import { useMediaDeviceSelect } from '@livekit/components-react' +import { RiCameraSwitchLine } from '@remixicon/react' +import { useState } from 'react' +import { ButtonProps } from 'react-aria-components' +import { useTranslation } from 'react-i18next' + +enum FacingMode { + USER = 'user', + ENVIRONMENT = 'environment', +} + +export const CameraSwitchButton = (props: Partial) => { + const { t } = useTranslation('rooms') + + const { devices, activeDeviceId, setActiveMediaDevice } = + useMediaDeviceSelect({ + kind: 'videoinput', + requestPermissions: true, + }) + + // getCapabilities type is not available. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const getDeviceFacingMode = (device: any): string[] => { + if (!device.getCapabilities) { + return [] + } + const capabilities = device.getCapabilities() + if (!capabilities) { + return [] + } + if (typeof capabilities.facingMode !== 'object') { + return [] + } + return capabilities.facingMode + } + + const detectCurrentFacingMode = (): FacingMode | null => { + console.log('detectCurrentFacingMode') + if (!activeDeviceId) { + return null + } + const activeDevice = devices.find( + (device) => device.deviceId === activeDeviceId + ) + console.log('activeDevice', activeDevice, activeDeviceId) + if (!activeDevice) { + return null + } + const facingMode = getDeviceFacingMode(activeDevice) + console.log('facingMode', facingMode) + if (facingMode.indexOf(FacingMode.USER) >= 0) { + return FacingMode.USER + } + if (facingMode.indexOf(FacingMode.ENVIRONMENT) >= 0) { + return FacingMode.ENVIRONMENT + } + return null + } + + const guessCurrentFacingMode = () => { + const facingMode = detectCurrentFacingMode() + if (facingMode) { + return facingMode + } + // We consider by default if we have no clue that the user camera is used. + return FacingMode.USER + } + + const [facingMode, setFacingMode] = useState( + guessCurrentFacingMode() + ) + + const getUserDevice = ( + facingMode: FacingMode + ): MediaDeviceInfo | undefined => { + return devices.find((device) => { + return getDeviceFacingMode(device).indexOf(facingMode) >= 0 + }) + } + + const toggle = () => { + let target: FacingMode + if (facingMode === FacingMode.USER) { + target = FacingMode.ENVIRONMENT + } else { + target = FacingMode.USER + } + console.log('toggle', facingMode, 'target', target) + const device = getUserDevice(target) + console.log('device', device, device?.label, device?.kind) + if (device) { + setActiveMediaDevice(device.deviceId) + setFacingMode(target) + } else { + console.error('Cannot get user device with facingMode ' + target) + } + } + + console.log('facingMode', facingMode) + console.log('detectCurrentFacingMode', detectCurrentFacingMode()) + console.log('guessCurrentFacingMode', guessCurrentFacingMode()) + console.log('getUserDevice(target) user', getUserDevice(FacingMode.USER)) + console.log( + 'getUserDevice(target) ENVIRONMENT', + getUserDevice(FacingMode.ENVIRONMENT) + ) + + return ( + + ) +} diff --git a/src/frontend/src/features/rooms/livekit/components/controls/SelectToggleDevice.tsx b/src/frontend/src/features/rooms/livekit/components/controls/SelectToggleDevice.tsx index 55b93712..15049b42 100644 --- a/src/frontend/src/features/rooms/livekit/components/controls/SelectToggleDevice.tsx +++ b/src/frontend/src/features/rooms/livekit/components/controls/SelectToggleDevice.tsx @@ -67,10 +67,12 @@ type SelectToggleDeviceProps = UseTrackToggleProps & { onActiveDeviceChange: (deviceId: string) => void source: SelectToggleSource + hideMenu?: boolean } export const SelectToggleDevice = ({ onActiveDeviceChange, + hideMenu, ...props }: SelectToggleDeviceProps) => { const config = selectToggleDeviceConfig[props.source] @@ -81,7 +83,7 @@ export const SelectToggleDevice = ({ const trackProps = useTrackToggle(props) const { devices, activeDeviceId, setActiveMediaDevice } = - useMediaDeviceSelect({ kind: config.kind }) + useMediaDeviceSelect({ kind: config.kind, requestPermissions: true }) const selectLabel = t('choose', { keyPrefix: `join.${config.kind}` }) @@ -92,29 +94,41 @@ export const SelectToggleDevice = ({ gap: '1px', })} > - - - - ({ - value: d.deviceId, - label: d.label, - }))} - selectedItem={activeDeviceId} - onAction={(value) => { - setActiveMediaDevice(value as string) - onActiveDeviceChange(value as string) - }} - /> - + + {!hideMenu && ( + + + ({ + value: d.deviceId, + label: d.label, + }))} + selectedItem={activeDeviceId} + onAction={(value) => { + setActiveMediaDevice(value as string) + onActiveDeviceChange(value as string) + }} + /> + + )} ) } diff --git a/src/frontend/src/features/rooms/livekit/components/controls/ToggleDevice.tsx b/src/frontend/src/features/rooms/livekit/components/controls/ToggleDevice.tsx index 9ea69cfe..9930a2ef 100644 --- a/src/frontend/src/features/rooms/livekit/components/controls/ToggleDevice.tsx +++ b/src/frontend/src/features/rooms/livekit/components/controls/ToggleDevice.tsx @@ -7,17 +7,20 @@ import { SelectToggleDeviceConfig } from './SelectToggleDevice' import useLongPress from '@/features/shortcuts/useLongPress' import { ActiveSpeaker } from '@/features/rooms/components/ActiveSpeaker' import { useIsSpeaking, useLocalParticipant } from '@livekit/components-react' +import { ToggleButtonProps } from '@/primitives/ToggleButton' export type ToggleDeviceProps = { enabled: boolean toggle: () => void config: SelectToggleDeviceConfig + toggleButtonProps?: Partial } export const ToggleDevice = ({ config, enabled, toggle, + toggleButtonProps, }: ToggleDeviceProps) => { const { t } = useTranslation('rooms', { keyPrefix: 'join' }) @@ -64,6 +67,7 @@ export const ToggleDevice = ({ aria-label={toggleLabel} tooltip={toggleLabel} groupPosition="left" + {...toggleButtonProps} > diff --git a/src/frontend/src/features/rooms/livekit/prefabs/ControlBar/MobileControlBar.tsx b/src/frontend/src/features/rooms/livekit/prefabs/ControlBar/MobileControlBar.tsx index c1f38ad7..ba198391 100644 --- a/src/frontend/src/features/rooms/livekit/prefabs/ControlBar/MobileControlBar.tsx +++ b/src/frontend/src/features/rooms/livekit/prefabs/ControlBar/MobileControlBar.tsx @@ -23,6 +23,7 @@ import { LinkButton } from '@/primitives' import { useSettingsDialog } from '../../components/controls/SettingsDialogContext' import { ResponsiveMenu } from './ResponsiveMenu' import { TranscriptToggle } from '../../components/controls/TranscriptToggle' +import { CameraSwitchButton } from '../../components/controls/CameraSwitchButton' export function MobileControlBar({ onDeviceError, @@ -55,7 +56,7 @@ export function MobileControlBar({ className={css({ display: 'flex', justifyContent: 'space-between', - width: '422px', + width: '330px', })} > @@ -68,6 +69,7 @@ export function MobileControlBar({ onActiveDeviceChange={(deviceId) => saveAudioInputDeviceId(deviceId ?? '') } + hideMenu={true} /> saveVideoInputDeviceId(deviceId ?? '') } + hideMenu={true} /> + setIsMenuOpened(false)} /> diff --git a/src/frontend/src/locales/en/rooms.json b/src/frontend/src/locales/en/rooms.json index 1f618510..5d8696a2 100644 --- a/src/frontend/src/locales/en/rooms.json +++ b/src/frontend/src/locales/en/rooms.json @@ -82,7 +82,8 @@ "feedbacks": "Give us feedbacks", "settings": "Settings", "username": "Update Your Name", - "effects": "Apply effects" + "effects": "Apply effects", + "switch_camera": "Switch camera" } }, "effects": { diff --git a/src/frontend/src/locales/fr/rooms.json b/src/frontend/src/locales/fr/rooms.json index 872d4c7c..476409ad 100644 --- a/src/frontend/src/locales/fr/rooms.json +++ b/src/frontend/src/locales/fr/rooms.json @@ -82,7 +82,8 @@ "feedbacks": "Partager votre avis", "settings": "Paramètres", "username": "Choisir votre nom", - "effects": "Appliquer des effets" + "effects": "Appliquer des effets", + "switch_camera": "Changer de caméra" } }, "effects": {