Skip to content

Commit

Permalink
✨(front) add switch camera on mobile
Browse files Browse the repository at this point in the history
We want to have a more simple control bar and give the user to switch
its back / front camera from the responsive menu.
  • Loading branch information
NathanVss committed Dec 20, 2024
1 parent e507668 commit 9594594
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { Button } from '@/primitives'
import { useMediaDeviceSelect } from '@livekit/components-react'
import { RiCameraSwitchLine } from '@remixicon/react'
import { useEffect, 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<ButtonProps>) => {
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 => {
if (!activeDeviceId) {
return null
}
const activeDevice = devices.find(
(device) => device.deviceId === activeDeviceId
)
if (!activeDevice) {
return null
}
const facingMode = getDeviceFacingMode(activeDevice)
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<FacingMode | null>()

/**
* Before setting the initial value of facingMode we need to wait for devices to
* be loaded ( because in detectCurrentFacingMode we need to find the active device
* in the devices list ), which is not the case at first render.
*/
useEffect(() => {
if (devices.length == 0) {
return
}
if (facingMode) {
return
}

devices.forEach((device) => console.log(device.label))

setFacingMode(guessCurrentFacingMode())
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [devices])

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
}
const device = getUserDevice(target)
if (device) {
setActiveMediaDevice(device.deviceId)
setFacingMode(target)
} else {
console.error('Cannot get user device with facingMode ' + target)
}
}
return (
<Button
onPress={(e) => {
toggle()
props.onPress?.(e)
}}
variant="primaryTextDark"
aria-label={t('options.items.switch_camera')}
tooltip={t('options.items.switch_camera')}
description={true}
>
<RiCameraSwitchLine size={20} />
</Button>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -176,6 +177,7 @@ export function MobileControlBar({
>
<RiSettings3Line size={20} />
</Button>
<CameraSwitchButton onPress={() => setIsMenuOpened(false)} />
</div>
</div>
</ResponsiveMenu>
Expand Down
3 changes: 2 additions & 1 deletion src/frontend/src/locales/en/rooms.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
3 changes: 2 additions & 1 deletion src/frontend/src/locales/fr/rooms.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down

0 comments on commit 9594594

Please sign in to comment.