feat: [closes #77] Peer audio volume control (#90)

* feat: add AudioVolume component
* feat: show volume slider label value
* feat: update audio volume icon
* feat: mute/unmute when volume icon is clicked
* feat: show peer dividers
This commit is contained in:
Jeremy Kahn 2023-02-26 18:26:53 -06:00 committed by GitHub
parent 99a9ab7838
commit 870a13eac1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 81 additions and 12 deletions

View File

@ -0,0 +1,58 @@
import { useState, useEffect } from 'react'
import Slider from '@mui/material/Slider'
import Box from '@mui/material/Box'
import ListItemIcon from '@mui/material/ListItemIcon'
import VolumeUp from '@mui/icons-material/VolumeUp'
import VolumeDown from '@mui/icons-material/VolumeDown'
import VolumeMute from '@mui/icons-material/VolumeMute'
interface AudioVolumeProps {
audioEl: HTMLAudioElement
}
export const AudioVolume = ({ audioEl }: AudioVolumeProps) => {
const [audioVolume, setAudioVolume] = useState(audioEl.volume)
useEffect(() => {
audioEl.volume = audioVolume
}, [audioEl, audioVolume])
const handleIconClick = () => {
if (audioVolume === 0) {
setAudioVolume(1)
} else {
setAudioVolume(0)
}
}
const handleSliderChange = (_event: Event, value: number | number[]) => {
value = Array.isArray(value) ? value[0] : value
setAudioVolume(value / 100)
}
const formatLabelValue = () => `${Math.round(audioVolume * 100)}%`
let VolumeIcon = VolumeUp
if (audioVolume === 0) {
VolumeIcon = VolumeMute
} else if (audioVolume < 0.5) {
VolumeIcon = VolumeDown
}
return (
<Box sx={{ display: 'flex', pt: 1, pr: 3, alignItems: 'center' }}>
<ListItemIcon>
<VolumeIcon sx={{ cursor: 'pointer' }} onClick={handleIconClick} />
</ListItemIcon>
<Slider
aria-label="Volume"
getAriaValueText={formatLabelValue}
valueLabelFormat={formatLabelValue}
valueLabelDisplay="auto"
onChange={handleSliderChange}
value={audioVolume * 100}
></Slider>
</Box>
)
}

View File

@ -0,0 +1 @@
export * from './AudioVolume'

View File

@ -14,9 +14,6 @@ interface UseRoomAudioConfig {
export function useRoomAudio({ peerRoom }: UseRoomAudioConfig) { export function useRoomAudio({ peerRoom }: UseRoomAudioConfig) {
const shellContext = useContext(ShellContext) const shellContext = useContext(ShellContext)
const [isSpeakingToRoom, setIsSpeakingToRoom] = useState(false) const [isSpeakingToRoom, setIsSpeakingToRoom] = useState(false)
const [peerAudios, setPeerAudios] = useState<
Record<string, HTMLAudioElement>
>({})
const [audioStream, setAudioStream] = useState<MediaStream | null>() const [audioStream, setAudioStream] = useState<MediaStream | null>()
const [audioDevices, setAudioDevices] = useState<MediaDeviceInfo[]>([]) const [audioDevices, setAudioDevices] = useState<MediaDeviceInfo[]>([])
@ -24,7 +21,8 @@ export function useRoomAudio({ peerRoom }: UseRoomAudioConfig) {
string | null string | null
>(null) >(null)
const { peerList, setPeerList, setAudioState } = shellContext const { peerList, setPeerList, setAudioState, peerAudios, setPeerAudios } =
shellContext
useEffect(() => { useEffect(() => {
;(async () => { ;(async () => {

View File

@ -10,6 +10,7 @@ import VolumeUp from '@mui/icons-material/VolumeUp'
import ListItem from '@mui/material/ListItem' import ListItem from '@mui/material/ListItem'
import { PeerListHeader } from 'components/Shell/PeerListHeader' import { PeerListHeader } from 'components/Shell/PeerListHeader'
import { AudioVolume } from 'components/AudioVolume'
import { PeerNameDisplay } from 'components/PeerNameDisplay' import { PeerNameDisplay } from 'components/PeerNameDisplay'
import { AudioState, Peer } from 'models/chat' import { AudioState, Peer } from 'models/chat'
@ -23,6 +24,7 @@ export interface PeerListProps extends PropsWithChildren {
onPeerListClose: () => void onPeerListClose: () => void
peerList: Peer[] peerList: Peer[]
audioState: AudioState audioState: AudioState
peerAudios: Record<string, HTMLAudioElement>
} }
export const PeerList = ({ export const PeerList = ({
@ -31,6 +33,7 @@ export const PeerList = ({
onPeerListClose, onPeerListClose,
peerList, peerList,
audioState, audioState,
peerAudios,
}: PeerListProps) => { }: PeerListProps) => {
return ( return (
<MuiDrawer <MuiDrawer
@ -57,7 +60,7 @@ export const PeerList = ({
</PeerListHeader> </PeerListHeader>
<Divider /> <Divider />
<List> <List>
<ListItem> <ListItem divider={true}>
{audioState === AudioState.PLAYING && ( {audioState === AudioState.PLAYING && (
<ListItemIcon> <ListItemIcon>
<VolumeUp /> <VolumeUp />
@ -68,20 +71,17 @@ export const PeerList = ({
</ListItemText> </ListItemText>
</ListItem> </ListItem>
{peerList.map((peer: Peer) => ( {peerList.map((peer: Peer) => (
<ListItem key={peer.peerId}> <ListItem key={peer.peerId} divider={true}>
<PeerDownloadFileButton peer={peer} /> <PeerDownloadFileButton peer={peer} />
<ListItemText> <ListItemText>
<PeerNameDisplay>{peer.userId}</PeerNameDisplay> <PeerNameDisplay>{peer.userId}</PeerNameDisplay>
{peer.peerId in peerAudios && (
<AudioVolume audioEl={peerAudios[peer.peerId]} />
)}
</ListItemText> </ListItemText>
{peer.audioState === AudioState.PLAYING && (
<ListItemIcon>
<VolumeUp />
</ListItemIcon>
)}
</ListItem> </ListItem>
))} ))}
</List> </List>
<Divider />
</MuiDrawer> </MuiDrawer>
) )
} }

View File

@ -56,6 +56,9 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
const [screenState, setScreenState] = useState<ScreenShareState>( const [screenState, setScreenState] = useState<ScreenShareState>(
ScreenShareState.NOT_SHARING ScreenShareState.NOT_SHARING
) )
const [peerAudios, setPeerAudios] = useState<
Record<string, HTMLAudioElement>
>({})
const showAlert = useCallback< const showAlert = useCallback<
(message: string, options?: AlertOptions) => void (message: string, options?: AlertOptions) => void
>((message, options) => { >((message, options) => {
@ -89,6 +92,8 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
setVideoState, setVideoState,
screenState, screenState,
setScreenState, setScreenState,
peerAudios,
setPeerAudios,
}), }),
[ [
isPeerListOpen, isPeerListOpen,
@ -112,6 +117,8 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
setVideoState, setVideoState,
screenState, screenState,
setScreenState, setScreenState,
peerAudios,
setPeerAudios,
] ]
) )
@ -322,6 +329,7 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
onPeerListClose={handlePeerListClick} onPeerListClose={handlePeerListClick}
peerList={peerList} peerList={peerList}
audioState={audioState} audioState={audioState}
peerAudios={peerAudios}
/> />
<QRCodeDialog <QRCodeDialog
isOpen={isQRCodeDialogOpen} isOpen={isQRCodeDialogOpen}

View File

@ -26,6 +26,8 @@ interface ShellContextProps {
setVideoState: Dispatch<SetStateAction<VideoState>> setVideoState: Dispatch<SetStateAction<VideoState>>
screenState: ScreenShareState screenState: ScreenShareState
setScreenState: Dispatch<SetStateAction<ScreenShareState>> setScreenState: Dispatch<SetStateAction<ScreenShareState>>
peerAudios: Record<string, HTMLAudioElement>
setPeerAudios: Dispatch<SetStateAction<Record<string, HTMLAudioElement>>>
} }
export const ShellContext = createContext<ShellContextProps>({ export const ShellContext = createContext<ShellContextProps>({
@ -51,4 +53,6 @@ export const ShellContext = createContext<ShellContextProps>({
setVideoState: () => {}, setVideoState: () => {},
screenState: ScreenShareState.NOT_SHARING, screenState: ScreenShareState.NOT_SHARING,
setScreenState: () => {}, setScreenState: () => {},
peerAudios: {},
setPeerAudios: () => {},
}) })