Braian Mellor c4618f1d25
fix: ui-control-buttons (#16)
* fix: ui control buttons

* feat: hide device menu on mobile
2023-09-12 17:35:23 -03:00

252 lines
9.3 KiB
TypeScript

import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { supportsScreenSharing } from '@livekit/components-core'
import {
ChatToggle,
DisconnectButton,
MediaDeviceMenu,
StartAudio,
TrackToggle,
useLocalParticipantPermissions,
useRoomContext,
useTrackToggle
} from '@livekit/components-react'
import classNames from 'classnames'
import { LocalAudioTrack, LocalVideoTrack, Track } from 'livekit-client'
import { t } from 'decentraland-dapps/dist/modules/translation/utils'
import { useMobileMediaQuery } from 'decentraland-ui/dist/components/Media/Media'
import { Button, Popup } from 'decentraland-ui'
import { useLayoutContext } from '../../../hooks/useLayoutContext'
import { useMediaQuery } from '../../../hooks/useMediaQuery'
import { usePreviewTracks } from '../../../hooks/usePreviewTracks'
import { mergeProps } from '../../../utils/mergeProps'
import { CameraIcon, ChatIcon, MicrophoneIcon, PhoneIcon, ShareScreenIcon } from '../../Icons'
import PeoplePanelToggleButton from './PeoplePanelToggleButton'
import { ControlBarProps, DEFAULT_USER_CHOICES } from './ControlBar.types'
import styles from './ControlBar.module.css'
/**
* The ControlBar prefab component gives the user the basic user interface
* to control their media devices and leave the room.
*
* @remarks
* This component is build with other LiveKit components like `TrackToggle`,
* `DeviceSelectorButton`, `DisconnectButton` and `StartAudio`.
*
* @example
* ```tsx
* <LiveKitRoom>
* <ControlBar />
* </LiveKitRoom>
* ```
* @public
*/
export function ControlBar({ variation, controls, ...props }: ControlBarProps) {
const [isChatOpen, setIsChatOpen] = useState(false)
const [confirmStopSharing, setConfirmStopSharing] = useState(false)
const layoutContext = useLayoutContext()
const {
options: { videoCaptureDefaults, audioCaptureDefaults }
} = useRoomContext()
const isTooLittleSpace = useMediaQuery(`(max-width: ${isChatOpen ? 1000 : 760}px)`)
const isMobile = useMobileMediaQuery()
const defaultVariation = isTooLittleSpace ? 'minimal' : 'verbose'
variation ??= defaultVariation
const visibleControls = { leave: true, ...controls }
const localPermissions = useLocalParticipantPermissions()
if (!localPermissions) {
visibleControls.camera = false
visibleControls.chat = false
visibleControls.microphone = false
visibleControls.screenShare = false
visibleControls.peoplePanel = false
} else {
visibleControls.camera ??= localPermissions.canPublish
visibleControls.microphone ??= localPermissions.canPublish
visibleControls.screenShare ??= localPermissions.canPublish
visibleControls.chat ??= localPermissions.canPublishData && controls?.chat
}
const showIcon = useMemo(() => variation === 'minimal' || variation === 'verbose', [variation])
const showText = useMemo(() => variation === 'textOnly' || variation === 'verbose', [variation])
const browserSupportsScreenSharing = supportsScreenSharing()
const handleStopScreenShare = useCallback(() => {
toggleScreenShare()
setConfirmStopSharing(false)
}, [])
const htmlProps = mergeProps({ className: styles.ControlBarContainer }, props)
const [videoEnabled, setVideoEnabled] = useState<boolean>(visibleControls.camera ?? DEFAULT_USER_CHOICES.videoEnabled)
const initialVideoDeviceId = (videoCaptureDefaults?.deviceId as string) ?? DEFAULT_USER_CHOICES.videoDeviceId
const [videoDeviceId, setVideoDeviceId] = useState<string>(initialVideoDeviceId)
const initialAudioDeviceId = (audioCaptureDefaults?.deviceId as string) ?? DEFAULT_USER_CHOICES.audioDeviceId
const [audioEnabled, setAudioEnabled] = useState<boolean>(visibleControls.microphone ?? DEFAULT_USER_CHOICES.audioEnabled)
const [audioDeviceId, setAudioDeviceId] = useState<string>(initialAudioDeviceId)
const { enabled: isScreenShareEnabled, toggle: toggleScreenShare } = useTrackToggle({
source: Track.Source.ScreenShare,
captureOptions: { audio: true, selfBrowserSurface: 'include' }
})
const handleOpen = () => {
if (!isScreenShareEnabled) {
toggleScreenShare()
} else {
setConfirmStopSharing(true)
}
}
const handleClose = () => {
setConfirmStopSharing(false)
}
const tracks = usePreviewTracks(
{
audio: audioEnabled ? { deviceId: initialAudioDeviceId } : false,
video: videoEnabled ? { deviceId: initialVideoDeviceId } : false
},
err => console.error('Error while setting up the pre-join configurations', err)
)
const videoEl = useRef(null)
const videoTrack = useMemo(() => tracks?.filter(track => track.kind === Track.Kind.Video)[0] as LocalVideoTrack, [tracks])
const audioTrack = useMemo(() => tracks?.filter(track => track.kind === Track.Kind.Audio)[0] as LocalAudioTrack, [tracks])
useEffect(() => {
if (layoutContext?.widget.state?.showChat !== undefined) {
setIsChatOpen(layoutContext?.widget.state?.showChat)
}
}, [layoutContext?.widget.state?.showChat])
useEffect(() => {
if (videoEl.current && videoTrack) {
videoTrack.unmute()
videoTrack.attach(videoEl.current)
}
return () => {
videoTrack?.detach()
}
}, [videoTrack])
return (
<div {...htmlProps}>
<div className={styles.ControlBarCenterButtonGroup}>
{visibleControls.microphone && (
<div className={styles.controlBarButtonGroup}>
<TrackToggle
className={classNames(styles.controlBarButton, { [styles.disabled]: !audioEnabled })}
source={Track.Source.Microphone}
showIcon={false}
initialState={audioEnabled}
onChange={enabled => setAudioEnabled(enabled)}
>
<MicrophoneIcon className={styles.controlMic} enabled={audioEnabled} />
</TrackToggle>
{!isMobile && (
<div className={`lk-button-group-menu ${styles.controlBarMenuButton}`}>
<MediaDeviceMenu
initialSelection={audioDeviceId}
kind="audioinput"
disabled={!audioTrack}
tracks={{ audioinput: audioTrack }}
onActiveDeviceChange={(_, id) => setAudioDeviceId(id)}
/>
</div>
)}
</div>
)}
{visibleControls.camera && (
<div className={styles.controlBarButtonGroup}>
<TrackToggle
className={classNames(styles.controlBarButton, { [styles.disabled]: !videoEnabled })}
source={Track.Source.Camera}
showIcon={false}
initialState={videoEnabled}
onChange={enabled => setVideoEnabled(enabled)}
>
<CameraIcon className={styles.controlCamera} enabled={videoEnabled} />
</TrackToggle>
{!isMobile && (
<div className={`lk-button-group-menu ${styles.controlBarMenuButton}`}>
<MediaDeviceMenu
kind="videoinput"
initialSelection={videoDeviceId}
disabled={!videoTrack}
tracks={{ videoinput: videoTrack }}
onActiveDeviceChange={(_, id) => setVideoDeviceId(id)}
/>
</div>
)}
</div>
)}
{visibleControls.screenShare && browserSupportsScreenSharing && (
<Popup
position="top left"
open={confirmStopSharing}
on="click"
closeOnTriggerBlur
onOpen={handleOpen}
onClose={handleClose}
content={
<Button className={styles.stopSharingButton} onClick={handleStopScreenShare}>
<div className={styles.stopSharingCloseButton} />
{t('control_bar.stop_sharing')}
</Button>
}
trigger={
<button className={classNames('lk-button', styles.controlBarButton, { [styles.screenShareEnabled]: isScreenShareEnabled })}>
<ShareScreenIcon className={styles.controlShare} enabled={isScreenShareEnabled} />
</button>
}
basic
style={{
backgroundColor: 'transparent',
padding: 0
}}
/>
)}
{isMobile && (
<div className={styles.ControlBarRightButtonGroup}>
{visibleControls.chat && (
<ChatToggle className={styles.chatToggleButton}>
{showIcon && <ChatIcon className={styles.chatToggleIcon} />}
{showText && 'Chat'}
</ChatToggle>
)}
{visibleControls.peoplePanel && <PeoplePanelToggleButton />}
</div>
)}
{visibleControls.leave && (
<DisconnectButton className={styles.disconnectButton}>
{showIcon && <PhoneIcon />}
{!isMobile && t('control_bar.leave')}
</DisconnectButton>
)}
</div>
<StartAudio label="Start Audio" />
{!isMobile && (
<div className={styles.ControlBarRightButtonGroup}>
{visibleControls.chat && (
<ChatToggle className={styles.chatToggleButton}>
{showIcon && <ChatIcon className={styles.chatToggleIcon} />}
{showText && 'Chat'}
</ChatToggle>
)}
{visibleControls.peoplePanel && <PeoplePanelToggleButton />}
</div>
)}
</div>
)
}