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 18, 2024
1 parent c6fdeaf commit 6804b4a
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -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<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 => {
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<FacingMode | null>(
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 (
<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 @@ -67,10 +67,12 @@ type SelectToggleDeviceProps<T extends ToggleSource> =
UseTrackToggleProps<T> & {
onActiveDeviceChange: (deviceId: string) => void
source: SelectToggleSource
hideMenu?: boolean
}

export const SelectToggleDevice = <T extends ToggleSource>({
onActiveDeviceChange,
hideMenu,
...props
}: SelectToggleDeviceProps<T>) => {
const config = selectToggleDeviceConfig[props.source]
Expand All @@ -81,7 +83,7 @@ export const SelectToggleDevice = <T extends ToggleSource>({
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}` })

Expand All @@ -92,29 +94,41 @@ export const SelectToggleDevice = <T extends ToggleSource>({
gap: '1px',
})}
>
<ToggleDevice {...trackProps} config={config} />
<Menu>
<Button
tooltip={selectLabel}
aria-label={selectLabel}
groupPosition="right"
square
variant={trackProps.enabled ? 'primaryDark' : 'error2'}
>
<RiArrowDownSLine />
</Button>
<MenuList
items={devices.map((d) => ({
value: d.deviceId,
label: d.label,
}))}
selectedItem={activeDeviceId}
onAction={(value) => {
setActiveMediaDevice(value as string)
onActiveDeviceChange(value as string)
}}
/>
</Menu>
<ToggleDevice
{...trackProps}
config={config}
toggleButtonProps={{
...(hideMenu
? {
groupPosition: undefined,
}
: {}),
}}
/>
{!hideMenu && (
<Menu>
<Button
tooltip={selectLabel}
aria-label={selectLabel}
groupPosition="right"
square
variant={trackProps.enabled ? 'primaryDark' : 'error2'}
>
<RiArrowDownSLine />
</Button>
<MenuList
items={devices.map((d) => ({
value: d.deviceId,
label: d.label,
}))}
selectedItem={activeDeviceId}
onAction={(value) => {
setActiveMediaDevice(value as string)
onActiveDeviceChange(value as string)
}}
/>
</Menu>
)}
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<ToggleButtonProps>
}

export const ToggleDevice = ({
config,
enabled,
toggle,
toggleButtonProps,
}: ToggleDeviceProps) => {
const { t } = useTranslation('rooms', { keyPrefix: 'join' })

Expand Down Expand Up @@ -64,6 +67,7 @@ export const ToggleDevice = ({
aria-label={toggleLabel}
tooltip={toggleLabel}
groupPosition="left"
{...toggleButtonProps}
>
<Icon />
</ToggleButton>
Expand Down
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 @@ -55,7 +56,7 @@ export function MobileControlBar({
className={css({
display: 'flex',
justifyContent: 'space-between',
width: '422px',
width: '330px',
})}
>
<LeaveButton />
Expand All @@ -68,6 +69,7 @@ export function MobileControlBar({
onActiveDeviceChange={(deviceId) =>
saveAudioInputDeviceId(deviceId ?? '')
}
hideMenu={true}
/>
<SelectToggleDevice
source={Track.Source.Camera}
Expand All @@ -78,6 +80,7 @@ export function MobileControlBar({
onActiveDeviceChange={(deviceId) =>
saveVideoInputDeviceId(deviceId ?? '')
}
hideMenu={true}
/>
<HandToggle />
<Button
Expand Down Expand Up @@ -174,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 6804b4a

Please sign in to comment.