Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(front) add switch camera on mobile #280

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 { 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
}

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 @@ -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 @@ -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
Loading