From ba21936c96633451be2e3df19b87f63c37dd71bb Mon Sep 17 00:00:00 2001 From: Jeremy Kahn Date: Mon, 31 Oct 2022 21:40:44 -0500 Subject: [PATCH] feat: [#19] display which peers are speaking to the room --- src/components/Room/useRoom.ts | 35 +++++++++++++++++++++++++++---- src/components/Shell/PeerList.tsx | 28 +++++++++++++++++++------ src/components/Shell/Shell.tsx | 8 ++++++- src/contexts/ShellContext.ts | 6 +++++- src/models/chat.ts | 6 ++++++ src/models/network.ts | 1 + 6 files changed, 72 insertions(+), 12 deletions(-) diff --git a/src/components/Room/useRoom.ts b/src/components/Room/useRoom.ts index d4e9cb9..e78bd6e 100644 --- a/src/components/Room/useRoom.ts +++ b/src/components/Room/useRoom.ts @@ -7,10 +7,12 @@ import { ShellContext } from 'contexts/ShellContext' import { SettingsContext } from 'contexts/SettingsContext' import { PeerActions } from 'models/network' import { + AudioState, Message, ReceivedMessage, UnsentMessage, isMessageReceived, + Peer, } from 'models/chat' import { funAnimalName } from 'fun-animal-names' import { getPeerName } from 'components/PeerNameDisplay' @@ -97,6 +99,11 @@ export function useRoom( const [sendPeerMessage, receivePeerMessage] = usePeerRoomAction(peerRoom, PeerActions.MESSAGE) + const [sendAudioChange, receiveAudioChange] = usePeerRoomAction( + peerRoom, + PeerActions.AUDIO_CHANGE + ) + const sendMessage = async (message: string) => { if (isMessageSending) return @@ -125,12 +132,12 @@ export function useRoom( if (peerIndex === -1) { shellContext.setPeerList([ ...shellContext.peerList, - { peerId: peerId, userId: userId }, + { peerId: peerId, userId: userId, audioState: AudioState.STOPPED }, ]) } else { - const peerListClone = [...shellContext.peerList] - peerListClone[peerIndex].userId = userId - shellContext.setPeerList(peerListClone) + const newPeerList = [...shellContext.peerList] + newPeerList[peerIndex].userId = userId + shellContext.setPeerList(newPeerList) } }) @@ -158,6 +165,20 @@ export function useRoom( setMessageLog([...messageLog, { ...message, timeReceived: Date.now() }]) }) + receiveAudioChange((audioState, peerId) => { + const newPeerList = shellContext.peerList.map(peer => { + const newPeer: Peer = { ...peer } + + if (peer.peerId === peerId) { + newPeer.audioState = audioState + } + + return newPeer + }) + + shellContext.setPeerList(newPeerList) + }) + peerRoom.onPeerJoin((peerId: string) => { shellContext.showAlert(`Someone has joined the room`, { severity: 'success', @@ -239,6 +260,8 @@ export function useRoom( }) peerRoom.addStream(newSelfStream) + sendAudioChange(AudioState.PLAYING) + shellContext.setAudioState(AudioState.PLAYING) setAudioStream(newSelfStream) } } else { @@ -249,6 +272,8 @@ export function useRoom( } peerRoom.removeStream(audioStream, peerRoom.getPeers()) + sendAudioChange(AudioState.STOPPED) + shellContext.setAudioState(AudioState.STOPPED) setAudioStream(null) } } @@ -259,6 +284,8 @@ export function useRoom( peerRoom, audioStream, selectedAudioDeviceId, + sendAudioChange, + shellContext, ]) const handleAudioDeviceSelect = async (audioDevice: MediaDeviceInfo) => { diff --git a/src/components/Shell/PeerList.tsx b/src/components/Shell/PeerList.tsx index 73d690e..051d0b7 100644 --- a/src/components/Shell/PeerList.tsx +++ b/src/components/Shell/PeerList.tsx @@ -1,24 +1,27 @@ import { PropsWithChildren } from 'react' import MuiDrawer from '@mui/material/Drawer' import List from '@mui/material/List' +import ListItemIcon from '@mui/material/ListItemIcon' +import ListItemText from '@mui/material/ListItemText' import Divider from '@mui/material/Divider' import IconButton from '@mui/material/IconButton' import ChevronRightIcon from '@mui/icons-material/ChevronRight' +import VolumeUp from '@mui/icons-material/VolumeUp' import ListItemButton from '@mui/material/ListItemButton' -import Typography from '@mui/material/Typography' import { PeerListHeader } from 'components/Shell/PeerListHeader' import { PeerNameDisplay } from 'components/PeerNameDisplay' -import { Peer } from 'models/chat' +import { AudioState, Peer } from 'models/chat' -export const peerListWidth = 240 +export const peerListWidth = 300 export interface PeerListProps extends PropsWithChildren { userId: string isPeerListOpen: boolean onPeerListClose: () => void peerList: Peer[] + audioState: AudioState } export const PeerList = ({ @@ -26,6 +29,7 @@ export const PeerList = ({ isPeerListOpen, onPeerListClose, peerList, + audioState, }: PeerListProps) => { return ( - + {audioState === AudioState.PLAYING && ( + + + + )} + {userId} (you) - + {peerList.map((peer: Peer) => ( - {peer.userId} + {peer.audioState === AudioState.PLAYING && ( + + + + )} + + {peer.userId} + ))} diff --git a/src/components/Shell/Shell.tsx b/src/components/Shell/Shell.tsx index 1638fb2..6d667a0 100644 --- a/src/components/Shell/Shell.tsx +++ b/src/components/Shell/Shell.tsx @@ -15,7 +15,7 @@ import { AlertColor } from '@mui/material/Alert' import { ShellContext } from 'contexts/ShellContext' import { SettingsContext } from 'contexts/SettingsContext' import { AlertOptions } from 'models/shell' -import { Peer } from 'models/chat' +import { AudioState, Peer } from 'models/chat' import { ErrorBoundary } from 'components/ErrorBoundary' import { Drawer } from './Drawer' @@ -42,6 +42,7 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => { const [isPeerListOpen, setIsPeerListOpen] = useState(false) const [peerList, setPeerList] = useState([]) // except you const [tabHasFocus, setTabHasFocus] = useState(true) + const [audioState, setAudioState] = useState(AudioState.STOPPED) const showAlert = useCallback< (message: string, options?: AlertOptions) => void @@ -63,6 +64,8 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => { setIsPeerListOpen, peerList, setPeerList, + audioState, + setAudioState, }), [ isPeerListOpen, @@ -73,6 +76,8 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => { setNumberOfPeers, setTitle, showAlert, + audioState, + setAudioState, ] ) @@ -203,6 +208,7 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => { isPeerListOpen={isPeerListOpen} onPeerListClose={handlePeerListClick} peerList={peerList} + audioState={audioState} /> diff --git a/src/contexts/ShellContext.ts b/src/contexts/ShellContext.ts index 5df57a9..34efa6a 100644 --- a/src/contexts/ShellContext.ts +++ b/src/contexts/ShellContext.ts @@ -1,7 +1,7 @@ import { createContext, Dispatch, SetStateAction } from 'react' import { AlertOptions } from 'models/shell' -import { Peer } from 'models/chat' +import { AudioState, Peer } from 'models/chat' interface ShellContextProps { numberOfPeers: number @@ -14,6 +14,8 @@ interface ShellContextProps { setIsPeerListOpen: Dispatch> peerList: Peer[] setPeerList: Dispatch> + audioState: AudioState + setAudioState: Dispatch> } export const ShellContext = createContext({ @@ -27,4 +29,6 @@ export const ShellContext = createContext({ setIsPeerListOpen: () => {}, peerList: [], setPeerList: () => {}, + audioState: AudioState.STOPPED, + setAudioState: () => {}, }) diff --git a/src/models/chat.ts b/src/models/chat.ts index f7bb210..793ad5b 100644 --- a/src/models/chat.ts +++ b/src/models/chat.ts @@ -5,9 +5,15 @@ export interface UnsentMessage { authorId: string } +export enum AudioState { + PLAYING = 'PLAYING', + STOPPED = 'STOPPED', +} + export interface Peer { peerId: string userId: string + audioState: AudioState } export interface ReceivedMessage extends UnsentMessage { diff --git a/src/models/network.ts b/src/models/network.ts index c33fdfe..92576a1 100644 --- a/src/models/network.ts +++ b/src/models/network.ts @@ -3,4 +3,5 @@ export enum PeerActions { MESSAGE = 'MESSAGE', MESSAGE_TRANSCRIPT = 'MSG_XSCRIPT', PEER_NAME = 'PEER_NAME', + AUDIO_CHANGE = 'AUDIO_CHANGE', }