UNPKG

@100mslive/roomkit-react

Version:

![Banner](https://github.com/100mslive/web-sdks/blob/06c65259912db6ccd8617f2ecb6fef51429251ec/prebuilt-banner.png)

209 lines (198 loc) 6.69 kB
import React, { Fragment, useRef, useState } from 'react'; import { DeviceType, selectIsLocalVideoEnabled, selectLocalVideoTrackID, selectVideoTrackByID, useDevices, useHMSStore, } from '@100mslive/react-sdk'; import { MicOnIcon, SpeakerIcon, VideoOnIcon } from '@100mslive/react-icons'; import { Box, Button, Dropdown, Flex, StyledVideoTile, Text, Video } from '../../../'; import { DialogDropdownTrigger } from '../../primitives/DropdownTrigger'; import { useUISettings } from '../AppData/useUISettings'; import { useAudioOutputTest } from '../hooks/useAudioOutputTest'; import { useDropdownSelection } from '../hooks/useDropdownSelection'; import { settingOverflow } from './common'; import { TEST_AUDIO_URL, UI_SETTINGS } from '../../common/constants'; /** * wrap the button on click of whom settings should open, this component will take care of the rest, * it'll give the user options to change input/output device as well as check speaker. * There is also another controlled way of using this by passing in open and onOpenChange. */ const Settings = ({ setHide }) => { const { allDevices, selectedDeviceIDs, updateDevice } = useDevices(); const { videoInput, audioInput, audioOutput } = allDevices; const videoTrackId = useHMSStore(selectLocalVideoTrackID); const isVideoOn = useHMSStore(selectIsLocalVideoEnabled); // don't show speaker selector where the API is not supported, and use // a generic word("Audio") for Mic. In some cases(Chrome Android for example) this changes both mic and speaker keeping them in sync. const shouldShowAudioOutput = 'setSinkId' in HTMLMediaElement.prototype; const mirrorLocalVideo = useUISettings(UI_SETTINGS.mirrorLocalVideo); const trackSelector = selectVideoTrackByID(videoTrackId); const track = useHMSStore(trackSelector); /** * Chromium browsers return an audioOutput with empty label when no permissions are given */ const audioOutputFiltered = audioOutput?.filter(item => !!item.label) ?? []; if (!videoInput?.length && !audioInput?.length && !audioOutputFiltered?.length) { setHide(true); } return ( <Box className={settingOverflow()}> {videoInput?.length ? ( <Fragment> {isVideoOn && ( <StyledVideoTile.Container css={{ w: '90%', px: '$10', height: '$48', bg: 'transparent', m: '$10 auto', }} > <Video trackId={videoTrackId} mirror={track?.facingMode !== 'environment' && mirrorLocalVideo} /> </StyledVideoTile.Container> )} <DeviceSelector title="Video" devices={videoInput} icon={<VideoOnIcon />} selection={selectedDeviceIDs.videoInput} onChange={deviceId => updateDevice({ deviceId, deviceType: DeviceType.videoInput, }) } /> </Fragment> ) : null} {audioInput?.length ? ( <DeviceSelector title={shouldShowAudioOutput ? 'Microphone' : 'Audio'} icon={<MicOnIcon />} devices={audioInput} selection={selectedDeviceIDs.audioInput} onChange={deviceId => updateDevice({ deviceId, deviceType: DeviceType.audioInput, }) } /> ) : null} {audioOutputFiltered?.length && shouldShowAudioOutput ? ( <DeviceSelector title="Speaker" icon={<SpeakerIcon />} devices={audioOutput} selection={selectedDeviceIDs.audioOutput} onChange={deviceId => updateDevice({ deviceId, deviceType: DeviceType.audioOutput, }) } > <TestAudio id={selectedDeviceIDs.audioOutput} /> </DeviceSelector> ) : null} </Box> ); }; const DeviceSelector = ({ title, devices, selection, onChange, icon, children = null }) => { const [open, setOpen] = useState(false); const selectionBg = useDropdownSelection(); const ref = useRef(null); return ( <Box css={{ mb: '$10' }}> <Text css={{ mb: '$4' }}>{title}</Text> <Flex align="center" css={{ gap: '$4', '@md': { flexDirection: children ? 'column' : 'row', alignItems: children ? 'start' : 'center', }, }} > <Box css={{ position: 'relative', flex: '1 1 0', minWidth: 0, w: '100%', maxWidth: '100%', '@md': { mb: children ? '$8' : 0, }, }} > <Dropdown.Root open={open} onOpenChange={setOpen}> <DialogDropdownTrigger ref={ref} icon={icon} title={devices.find(({ deviceId }) => deviceId === selection)?.label || 'Select device from list'} open={open} /> <Dropdown.Portal> <Dropdown.Content align="start" sideOffset={8} css={{ w: ref.current?.clientWidth, zIndex: 1001 }}> {devices.map(device => { return ( <Dropdown.Item key={device.label} onSelect={() => onChange(device.deviceId)} css={{ px: '$9', bg: device.deviceId === selection ? selectionBg : undefined, }} > {device.label} </Dropdown.Item> ); })} </Dropdown.Content> </Dropdown.Portal> </Dropdown.Root> </Box> {children} </Flex> </Box> ); }; const TestAudio = ({ id }) => { const { playing, setPlaying, audioRef } = useAudioOutputTest({ deviceId: id }); return ( <> <Button variant="standard" css={{ flexShrink: 0, p: '$6 $9', '@md': { w: '100%', }, }} onClick={() => audioRef.current?.play()} disabled={playing} > <SpeakerIcon /> &nbsp;Test{' '} <Text as="span" css={{ display: 'none', '@md': { display: 'inline' } }}> &nbsp; speaker </Text> </Button> <audio ref={audioRef} src={TEST_AUDIO_URL} onEnded={() => setPlaying(false)} onPlay={() => setPlaying(true)} css={{ display: 'none' }} /> </> ); }; export default Settings;