* feat(ui): present mic volume icon * feat(ui): improve mic volume display * refactor(ui): nest mic audio as a channel * fix(ui): prevent volume control from reappearing for returning peers * refactor(audio): update specific audio channel states * refactor(audio): use enum for audio channel name * refactor(types): improve audio type names * feat(audio): wire up screen share audio * refactor(networking): always provide stream metadata * fix(audio): remove screen audio when stream ends * fix(audio): stop audio when removing it * feat(audio): show appropriate icon for channel * fix(audio): clean up audio for leaving peers consistently * fix(audio): use up-to-date peerAudios reference * refactor(audio): simplify audio state updating * refactor(audio): use functional setState to update peer list * refactor(variables): rename peerAudios to peerAudioChannels * refactor(types): consolidate stream types * refactor(types): require stream type metadata
This commit is contained in:
		
							parent
							
								
									89abe718db
								
							
						
					
					
						commit
						05b4615af9
					
				| @ -1,16 +1,25 @@ | |||||||
| import { useState, useEffect } from 'react' | import { useState, useEffect } from 'react' | ||||||
| import Slider from '@mui/material/Slider' | import Slider from '@mui/material/Slider' | ||||||
| import Box from '@mui/material/Box' | import Box from '@mui/material/Box' | ||||||
|  | import Paper from '@mui/material/Paper' | ||||||
| import ListItemIcon from '@mui/material/ListItemIcon' | import ListItemIcon from '@mui/material/ListItemIcon' | ||||||
| import VolumeUp from '@mui/icons-material/VolumeUp' | import VolumeUpIcon from '@mui/icons-material/VolumeUp' | ||||||
| import VolumeDown from '@mui/icons-material/VolumeDown' | import VolumeDownIcon from '@mui/icons-material/VolumeDown' | ||||||
| import VolumeMute from '@mui/icons-material/VolumeMute' | import VolumeMuteIcon from '@mui/icons-material/VolumeMute' | ||||||
|  | import MicIcon from '@mui/icons-material/Mic' | ||||||
|  | import LaptopWindowsIcon from '@mui/icons-material/LaptopWindows' | ||||||
|  | import Tooltip from '@mui/material/Tooltip' | ||||||
|  | import { AudioChannelName } from 'models/chat' | ||||||
| 
 | 
 | ||||||
| interface AudioVolumeProps { | interface AudioVolumeProps { | ||||||
|   audioEl: HTMLAudioElement |   audioEl: HTMLAudioElement | ||||||
|  |   audioChannelName: AudioChannelName | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const AudioVolume = ({ audioEl }: AudioVolumeProps) => { | export const AudioVolume = ({ | ||||||
|  |   audioEl, | ||||||
|  |   audioChannelName, | ||||||
|  | }: AudioVolumeProps) => { | ||||||
|   const [audioVolume, setAudioVolume] = useState(audioEl.volume) |   const [audioVolume, setAudioVolume] = useState(audioEl.volume) | ||||||
| 
 | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
| @ -32,27 +41,48 @@ export const AudioVolume = ({ audioEl }: AudioVolumeProps) => { | |||||||
| 
 | 
 | ||||||
|   const formatLabelValue = () => `${Math.round(audioVolume * 100)}%` |   const formatLabelValue = () => `${Math.round(audioVolume * 100)}%` | ||||||
| 
 | 
 | ||||||
|   let VolumeIcon = VolumeUp |   let VolumeIcon = VolumeUpIcon | ||||||
| 
 | 
 | ||||||
|   if (audioVolume === 0) { |   if (audioVolume === 0) { | ||||||
|     VolumeIcon = VolumeMute |     VolumeIcon = VolumeMuteIcon | ||||||
|   } else if (audioVolume < 0.5) { |   } else if (audioVolume < 0.5) { | ||||||
|     VolumeIcon = VolumeDown |     VolumeIcon = VolumeDownIcon | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <Box sx={{ display: 'flex', pt: 1, pr: 3, alignItems: 'center' }}> |     <Paper | ||||||
|       <ListItemIcon> |       sx={{ | ||||||
|         <VolumeIcon sx={{ cursor: 'pointer' }} onClick={handleIconClick} /> |         alignItems: 'center', | ||||||
|  |         display: 'flex', | ||||||
|  |         mt: 1.5, | ||||||
|  |         pl: 2, | ||||||
|  |         pr: 3, | ||||||
|  |         py: 1, | ||||||
|  |       }} | ||||||
|  |     > | ||||||
|  |       <ListItemIcon sx={{ cursor: 'pointer' }} onClick={handleIconClick}> | ||||||
|  |         <VolumeIcon fontSize="small" /> | ||||||
|  |         {audioChannelName === AudioChannelName.MICROPHONE && ( | ||||||
|  |           <Tooltip title="Their microphone volume"> | ||||||
|  |             <MicIcon fontSize="small" sx={{ ml: 1, mr: 2 }} /> | ||||||
|  |           </Tooltip> | ||||||
|  |         )} | ||||||
|  |         {audioChannelName === AudioChannelName.SCREEN_SHARE && ( | ||||||
|  |           <Tooltip title="Their screen's volume"> | ||||||
|  |             <LaptopWindowsIcon fontSize="small" sx={{ ml: 1, mr: 2 }} /> | ||||||
|  |           </Tooltip> | ||||||
|  |         )} | ||||||
|       </ListItemIcon> |       </ListItemIcon> | ||||||
|       <Slider |       <Box display="flex" width={1}> | ||||||
|         aria-label="Volume" |         <Slider | ||||||
|         getAriaValueText={formatLabelValue} |           aria-label="Volume" | ||||||
|         valueLabelFormat={formatLabelValue} |           getAriaValueText={formatLabelValue} | ||||||
|         valueLabelDisplay="auto" |           valueLabelFormat={formatLabelValue} | ||||||
|         onChange={handleSliderChange} |           valueLabelDisplay="auto" | ||||||
|         value={audioVolume * 100} |           onChange={handleSliderChange} | ||||||
|       ></Slider> |           value={audioVolume * 100} | ||||||
|     </Box> |         ></Slider> | ||||||
|  |       </Box> | ||||||
|  |     </Paper> | ||||||
|   ) |   ) | ||||||
| } | } | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ import Paper from '@mui/material/Paper' | |||||||
| import Tooltip from '@mui/material/Tooltip' | import Tooltip from '@mui/material/Tooltip' | ||||||
| 
 | 
 | ||||||
| import { PeerNameDisplay } from 'components/PeerNameDisplay' | import { PeerNameDisplay } from 'components/PeerNameDisplay' | ||||||
| import { VideoStreamType } from 'models/chat' | import { StreamType } from 'models/chat' | ||||||
| 
 | 
 | ||||||
| import { SelectedPeerStream } from './RoomVideoDisplay' | import { SelectedPeerStream } from './RoomVideoDisplay' | ||||||
| 
 | 
 | ||||||
| @ -13,13 +13,13 @@ interface PeerVideoProps { | |||||||
|   numberOfVideos: number |   numberOfVideos: number | ||||||
|   onVideoClick?: ( |   onVideoClick?: ( | ||||||
|     userId: string, |     userId: string, | ||||||
|     videoStreamType: VideoStreamType, |     streamType: StreamType, | ||||||
|     videoStream: MediaStream |     videoStream: MediaStream | ||||||
|   ) => void |   ) => void | ||||||
|   selectedPeerStream: SelectedPeerStream | null |   selectedPeerStream: SelectedPeerStream | null | ||||||
|   userId: string |   userId: string | ||||||
|   videoStream: MediaStream |   videoStream: MediaStream | ||||||
|   videoStreamType: VideoStreamType |   streamType: StreamType | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Adapted from https://www.geeksforgeeks.org/find-the-next-perfect-square-greater-than-a-given-number/
 | // Adapted from https://www.geeksforgeeks.org/find-the-next-perfect-square-greater-than-a-given-number/
 | ||||||
| @ -37,7 +37,7 @@ export const PeerVideo = ({ | |||||||
|   userId, |   userId, | ||||||
|   selectedPeerStream, |   selectedPeerStream, | ||||||
|   videoStream, |   videoStream, | ||||||
|   videoStreamType, |   streamType, | ||||||
| }: PeerVideoProps) => { | }: PeerVideoProps) => { | ||||||
|   const videoRef = useRef<HTMLVideoElement>(null) |   const videoRef = useRef<HTMLVideoElement>(null) | ||||||
| 
 | 
 | ||||||
| @ -47,13 +47,14 @@ export const PeerVideo = ({ | |||||||
| 
 | 
 | ||||||
|     video.autoplay = true |     video.autoplay = true | ||||||
|     video.srcObject = videoStream |     video.srcObject = videoStream | ||||||
|  |     video.muted = true | ||||||
|   }, [videoRef, videoStream]) |   }, [videoRef, videoStream]) | ||||||
| 
 | 
 | ||||||
|   const cols = Math.sqrt(nextPerfectSquare(numberOfVideos - 1)) |   const cols = Math.sqrt(nextPerfectSquare(numberOfVideos - 1)) | ||||||
|   const rows = Math.ceil(numberOfVideos / cols) |   const rows = Math.ceil(numberOfVideos / cols) | ||||||
| 
 | 
 | ||||||
|   const handleVideoClick = () => { |   const handleVideoClick = () => { | ||||||
|     onVideoClick?.(userId, videoStreamType, videoStream) |     onVideoClick?.(userId, streamType, videoStream) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ import Paper from '@mui/material/Paper' | |||||||
| 
 | 
 | ||||||
| import { RoomContext } from 'contexts/RoomContext' | import { RoomContext } from 'contexts/RoomContext' | ||||||
| import { ShellContext } from 'contexts/ShellContext' | import { ShellContext } from 'contexts/ShellContext' | ||||||
| import { Peer, VideoStreamType } from 'models/chat' | import { Peer, StreamType } from 'models/chat' | ||||||
| 
 | 
 | ||||||
| import { PeerVideo } from './PeerVideo' | import { PeerVideo } from './PeerVideo' | ||||||
| 
 | 
 | ||||||
| @ -16,7 +16,7 @@ interface PeerWithVideo { | |||||||
| 
 | 
 | ||||||
| export interface SelectedPeerStream { | export interface SelectedPeerStream { | ||||||
|   peerId: string |   peerId: string | ||||||
|   videoStreamType: VideoStreamType |   streamType: StreamType | ||||||
|   videoStream: MediaStream |   videoStream: MediaStream | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -105,13 +105,13 @@ export const RoomVideoDisplay = ({ | |||||||
| 
 | 
 | ||||||
|   const handleVideoClick = ( |   const handleVideoClick = ( | ||||||
|     peerId: string, |     peerId: string, | ||||||
|     videoStreamType: VideoStreamType, |     streamType: StreamType, | ||||||
|     videoStream: MediaStream |     videoStream: MediaStream | ||||||
|   ) => { |   ) => { | ||||||
|     if (selectedPeerStream?.videoStream === videoStream) { |     if (selectedPeerStream?.videoStream === videoStream) { | ||||||
|       setSelectedPeerStream(null) |       setSelectedPeerStream(null) | ||||||
|     } else if (numberOfVideos > 1) { |     } else if (numberOfVideos > 1) { | ||||||
|       setSelectedPeerStream({ peerId, videoStreamType, videoStream }) |       setSelectedPeerStream({ peerId, streamType, videoStream }) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -139,7 +139,7 @@ export const RoomVideoDisplay = ({ | |||||||
|             userId={selectedPeerStream.peerId} |             userId={selectedPeerStream.peerId} | ||||||
|             selectedPeerStream={selectedPeerStream} |             selectedPeerStream={selectedPeerStream} | ||||||
|             videoStream={selectedPeerStream.videoStream} |             videoStream={selectedPeerStream.videoStream} | ||||||
|             videoStreamType={selectedPeerStream.videoStreamType} |             streamType={selectedPeerStream.streamType} | ||||||
|           /> |           /> | ||||||
|         </Box> |         </Box> | ||||||
|       )} |       )} | ||||||
| @ -168,7 +168,7 @@ export const RoomVideoDisplay = ({ | |||||||
|             userId={userId} |             userId={userId} | ||||||
|             selectedPeerStream={selectedPeerStream} |             selectedPeerStream={selectedPeerStream} | ||||||
|             videoStream={selfVideoStream} |             videoStream={selfVideoStream} | ||||||
|             videoStreamType={VideoStreamType.WEBCAM} |             streamType={StreamType.WEBCAM} | ||||||
|           /> |           /> | ||||||
|         )} |         )} | ||||||
|         {selfScreenStream && ( |         {selfScreenStream && ( | ||||||
| @ -179,7 +179,7 @@ export const RoomVideoDisplay = ({ | |||||||
|             userId={userId} |             userId={userId} | ||||||
|             selectedPeerStream={selectedPeerStream} |             selectedPeerStream={selectedPeerStream} | ||||||
|             videoStream={selfScreenStream} |             videoStream={selfScreenStream} | ||||||
|             videoStreamType={VideoStreamType.SCREEN_SHARE} |             streamType={StreamType.SCREEN_SHARE} | ||||||
|           /> |           /> | ||||||
|         )} |         )} | ||||||
|         {peersWithVideo.map(peerWithVideo => ( |         {peersWithVideo.map(peerWithVideo => ( | ||||||
| @ -191,7 +191,7 @@ export const RoomVideoDisplay = ({ | |||||||
|                 userId={peerWithVideo.peer.userId} |                 userId={peerWithVideo.peer.userId} | ||||||
|                 selectedPeerStream={selectedPeerStream} |                 selectedPeerStream={selectedPeerStream} | ||||||
|                 videoStream={peerWithVideo.videoStream} |                 videoStream={peerWithVideo.videoStream} | ||||||
|                 videoStreamType={VideoStreamType.WEBCAM} |                 streamType={StreamType.WEBCAM} | ||||||
|               /> |               /> | ||||||
|             )} |             )} | ||||||
|             {peerWithVideo.screenStream && ( |             {peerWithVideo.screenStream && ( | ||||||
| @ -201,7 +201,7 @@ export const RoomVideoDisplay = ({ | |||||||
|                 userId={peerWithVideo.peer.userId} |                 userId={peerWithVideo.peer.userId} | ||||||
|                 selectedPeerStream={selectedPeerStream} |                 selectedPeerStream={selectedPeerStream} | ||||||
|                 videoStream={peerWithVideo.screenStream} |                 videoStream={peerWithVideo.screenStream} | ||||||
|                 videoStreamType={VideoStreamType.SCREEN_SHARE} |                 streamType={StreamType.SCREEN_SHARE} | ||||||
|               /> |               /> | ||||||
|             )} |             )} | ||||||
|           </Fragment> |           </Fragment> | ||||||
|  | |||||||
| @ -23,6 +23,7 @@ import { | |||||||
|   TypingStatus, |   TypingStatus, | ||||||
|   Peer, |   Peer, | ||||||
|   PeerVerificationState, |   PeerVerificationState, | ||||||
|  |   AudioChannelName, | ||||||
| } from 'models/chat' | } from 'models/chat' | ||||||
| import { getPeerName, usePeerNameDisplay } from 'components/PeerNameDisplay' | import { getPeerName, usePeerNameDisplay } from 'components/PeerNameDisplay' | ||||||
| import { Audio } from 'lib/Audio' | import { Audio } from 'lib/Audio' | ||||||
| @ -269,7 +270,10 @@ export function useRoom( | |||||||
|           userId, |           userId, | ||||||
|           publicKey, |           publicKey, | ||||||
|           customUsername, |           customUsername, | ||||||
|           audioState: AudioState.STOPPED, |           audioChannelState: { | ||||||
|  |             [AudioChannelName.MICROPHONE]: AudioState.STOPPED, | ||||||
|  |             [AudioChannelName.SCREEN_SHARE]: AudioState.STOPPED, | ||||||
|  |           }, | ||||||
|           videoState: VideoState.STOPPED, |           videoState: VideoState.STOPPED, | ||||||
|           screenShareState: ScreenShareState.NOT_SHARING, |           screenShareState: ScreenShareState.NOT_SHARING, | ||||||
|           offeredFileId: null, |           offeredFileId: null, | ||||||
|  | |||||||
| @ -2,7 +2,13 @@ import { useContext, useEffect, useCallback, useState } from 'react' | |||||||
| 
 | 
 | ||||||
| import { ShellContext } from 'contexts/ShellContext' | import { ShellContext } from 'contexts/ShellContext' | ||||||
| import { PeerActions } from 'models/network' | import { PeerActions } from 'models/network' | ||||||
| import { AudioState, Peer } from 'models/chat' | import { | ||||||
|  |   AudioState, | ||||||
|  |   Peer, | ||||||
|  |   AudioChannelName, | ||||||
|  |   PeerAudioChannelState, | ||||||
|  |   StreamType, | ||||||
|  | } from 'models/chat' | ||||||
| import { PeerRoom, PeerHookType, PeerStreamType } from 'lib/PeerRoom' | import { PeerRoom, PeerHookType, PeerStreamType } from 'lib/PeerRoom' | ||||||
| 
 | 
 | ||||||
| interface UseRoomAudioConfig { | interface UseRoomAudioConfig { | ||||||
| @ -19,7 +25,7 @@ export function useRoomAudio({ peerRoom }: UseRoomAudioConfig) { | |||||||
|     string | null |     string | null | ||||||
|   >(null) |   >(null) | ||||||
| 
 | 
 | ||||||
|   const { peerList, setPeerList, setAudioState, peerAudios, setPeerAudios } = |   const { setPeerList, setAudioChannelState, setPeerAudioChannels } = | ||||||
|     shellContext |     shellContext | ||||||
| 
 | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
| @ -32,29 +38,46 @@ export function useRoomAudio({ peerRoom }: UseRoomAudioConfig) { | |||||||
|     })() |     })() | ||||||
|   }, [audioStream]) |   }, [audioStream]) | ||||||
| 
 | 
 | ||||||
|   const [sendAudioChange, receiveAudioChange] = peerRoom.makeAction<AudioState>( |   const [sendAudioChange, receiveAudioChange] = peerRoom.makeAction< | ||||||
|     PeerActions.AUDIO_CHANGE |     Partial<PeerAudioChannelState> | ||||||
|   ) |   >(PeerActions.AUDIO_CHANGE) | ||||||
| 
 | 
 | ||||||
|   receiveAudioChange((audioState, peerId) => { |   receiveAudioChange((peerAudioChannelState, peerId) => { | ||||||
|     const newPeerList = peerList.map(peer => { |     setPeerList(peerList => { | ||||||
|       const newPeer: Peer = { ...peer } |       return peerList.map(peer => { | ||||||
|  |         const newPeer: Peer = { ...peer } | ||||||
| 
 | 
 | ||||||
|       if (peer.peerId === peerId) { |         const microphoneAudioChannel = | ||||||
|         newPeer.audioState = audioState |           peerAudioChannelState[AudioChannelName.MICROPHONE] | ||||||
| 
 | 
 | ||||||
|         if (audioState === AudioState.STOPPED) { |         if (microphoneAudioChannel) { | ||||||
|           deletePeerAudio(peerId) |           if (peer.peerId === peerId) { | ||||||
|  |             newPeer.audioChannelState = { | ||||||
|  |               ...newPeer.audioChannelState, | ||||||
|  |               ...peerAudioChannelState, | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (microphoneAudioChannel === AudioState.STOPPED) { | ||||||
|  |               deletePeerAudio(peerId) | ||||||
|  |             } | ||||||
|  |           } | ||||||
|         } |         } | ||||||
|       } |  | ||||||
| 
 | 
 | ||||||
|       return newPeer |         return newPeer | ||||||
|  |       }) | ||||||
|     }) |     }) | ||||||
| 
 |  | ||||||
|     setPeerList(newPeerList) |  | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   peerRoom.onPeerStream(PeerStreamType.AUDIO, (stream, peerId) => { |   peerRoom.onPeerStream(PeerStreamType.AUDIO, (stream, peerId, metadata) => { | ||||||
|  |     if ( | ||||||
|  |       typeof metadata === 'object' && | ||||||
|  |       metadata !== null && | ||||||
|  |       'type' in metadata && | ||||||
|  |       metadata.type !== StreamType.MICROPHONE | ||||||
|  |     ) { | ||||||
|  |       return | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     const audioTracks = stream.getAudioTracks() |     const audioTracks = stream.getAudioTracks() | ||||||
| 
 | 
 | ||||||
|     if (audioTracks.length === 0) return |     if (audioTracks.length === 0) return | ||||||
| @ -63,7 +86,13 @@ export function useRoomAudio({ peerRoom }: UseRoomAudioConfig) { | |||||||
|     audio.srcObject = stream |     audio.srcObject = stream | ||||||
|     audio.autoplay = true |     audio.autoplay = true | ||||||
| 
 | 
 | ||||||
|     setPeerAudios({ ...peerAudios, [peerId]: audio }) |     setPeerAudioChannels(peerAudioChannels => ({ | ||||||
|  |       ...peerAudioChannels, | ||||||
|  |       [peerId]: { | ||||||
|  |         ...peerAudioChannels[peerId], | ||||||
|  |         [AudioChannelName.MICROPHONE]: audio, | ||||||
|  |       }, | ||||||
|  |     })) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   const cleanupAudio = useCallback(() => { |   const cleanupAudio = useCallback(() => { | ||||||
| @ -86,9 +115,19 @@ export function useRoomAudio({ peerRoom }: UseRoomAudioConfig) { | |||||||
|             video: false, |             video: false, | ||||||
|           }) |           }) | ||||||
| 
 | 
 | ||||||
|           peerRoom.addStream(newSelfStream) |           peerRoom.addStream(newSelfStream, null, { | ||||||
|           sendAudioChange(AudioState.PLAYING) |             type: StreamType.MICROPHONE, | ||||||
|           setAudioState(AudioState.PLAYING) |           }) | ||||||
|  | 
 | ||||||
|  |           sendAudioChange({ | ||||||
|  |             [AudioChannelName.MICROPHONE]: AudioState.PLAYING, | ||||||
|  |           }) | ||||||
|  | 
 | ||||||
|  |           setAudioChannelState(prevState => ({ | ||||||
|  |             ...prevState, | ||||||
|  |             [AudioChannelName.MICROPHONE]: AudioState.PLAYING, | ||||||
|  |           })) | ||||||
|  | 
 | ||||||
|           setAudioStream(newSelfStream) |           setAudioStream(newSelfStream) | ||||||
|         } |         } | ||||||
|       } else { |       } else { | ||||||
| @ -96,8 +135,16 @@ export function useRoomAudio({ peerRoom }: UseRoomAudioConfig) { | |||||||
|           cleanupAudio() |           cleanupAudio() | ||||||
| 
 | 
 | ||||||
|           peerRoom.removeStream(audioStream, peerRoom.getPeers()) |           peerRoom.removeStream(audioStream, peerRoom.getPeers()) | ||||||
|           sendAudioChange(AudioState.STOPPED) | 
 | ||||||
|           setAudioState(AudioState.STOPPED) |           sendAudioChange({ | ||||||
|  |             [AudioChannelName.MICROPHONE]: AudioState.STOPPED, | ||||||
|  |           }) | ||||||
|  | 
 | ||||||
|  |           setAudioChannelState(prevState => ({ | ||||||
|  |             ...prevState, | ||||||
|  |             [AudioChannelName.MICROPHONE]: AudioState.STOPPED, | ||||||
|  |           })) | ||||||
|  | 
 | ||||||
|           setAudioStream(null) |           setAudioStream(null) | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
| @ -106,11 +153,10 @@ export function useRoomAudio({ peerRoom }: UseRoomAudioConfig) { | |||||||
|     audioStream, |     audioStream, | ||||||
|     cleanupAudio, |     cleanupAudio, | ||||||
|     isSpeakingToRoom, |     isSpeakingToRoom, | ||||||
|     peerAudios, |  | ||||||
|     peerRoom, |     peerRoom, | ||||||
|     selectedAudioDeviceId, |     selectedAudioDeviceId, | ||||||
|     sendAudioChange, |     sendAudioChange, | ||||||
|     setAudioState, |     setAudioChannelState, | ||||||
|   ]) |   ]) | ||||||
| 
 | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
| @ -139,27 +185,45 @@ export function useRoomAudio({ peerRoom }: UseRoomAudioConfig) { | |||||||
|       video: false, |       video: false, | ||||||
|     }) |     }) | ||||||
| 
 | 
 | ||||||
|     peerRoom.addStream(newSelfStream) |     peerRoom.addStream(newSelfStream, null, { | ||||||
|  |       type: StreamType.MICROPHONE, | ||||||
|  |     }) | ||||||
|  | 
 | ||||||
|     setAudioStream(newSelfStream) |     setAudioStream(newSelfStream) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   const deletePeerAudio = (peerId: string) => { |   const deletePeerAudio = (peerId: string) => { | ||||||
|     const newPeerAudios = { ...peerAudios } |     setPeerAudioChannels(({ ...newPeerAudios }) => { | ||||||
|     delete newPeerAudios[peerId] |       if (!newPeerAudios[peerId]) { | ||||||
|     setPeerAudios(newPeerAudios) |         return newPeerAudios | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       const microphoneAudio = newPeerAudios[peerId][AudioChannelName.MICROPHONE] | ||||||
|  |       microphoneAudio?.pause() | ||||||
|  | 
 | ||||||
|  |       const { [AudioChannelName.MICROPHONE]: _, ...newPeerAudioChannels } = | ||||||
|  |         newPeerAudios[peerId] | ||||||
|  | 
 | ||||||
|  |       newPeerAudios[peerId] = newPeerAudioChannels | ||||||
|  | 
 | ||||||
|  |       return newPeerAudios | ||||||
|  |     }) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   const handleAudioForNewPeer = (peerId: string) => { |   const handleAudioForNewPeer = (peerId: string) => { | ||||||
|     if (audioStream) { |     if (audioStream) { | ||||||
|       peerRoom.addStream(audioStream, peerId) |       peerRoom.addStream(audioStream, peerId, { | ||||||
|  |         type: StreamType.MICROPHONE, | ||||||
|  |       }) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   const handleAudioForLeavingPeer = (peerId: string) => { |   const handleAudioForLeavingPeer = (peerId: string) => { | ||||||
|     if (audioStream) { |     if (audioStream) { | ||||||
|       peerRoom.removeStream(audioStream, peerId) |       peerRoom.removeStream(audioStream, peerId) | ||||||
|       deletePeerAudio(peerId) |  | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     deletePeerAudio(peerId) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   peerRoom.onPeerJoin(PeerHookType.AUDIO, (peerId: string) => { |   peerRoom.onPeerJoin(PeerHookType.AUDIO, (peerId: string) => { | ||||||
|  | |||||||
| @ -4,7 +4,13 @@ import { isRecord } from 'lib/type-guards' | |||||||
| import { RoomContext } from 'contexts/RoomContext' | import { RoomContext } from 'contexts/RoomContext' | ||||||
| import { ShellContext } from 'contexts/ShellContext' | import { ShellContext } from 'contexts/ShellContext' | ||||||
| import { PeerActions } from 'models/network' | import { PeerActions } from 'models/network' | ||||||
| import { ScreenShareState, Peer, VideoStreamType } from 'models/chat' | import { | ||||||
|  |   ScreenShareState, | ||||||
|  |   Peer, | ||||||
|  |   StreamType, | ||||||
|  |   AudioChannelName, | ||||||
|  |   AudioState, | ||||||
|  | } from 'models/chat' | ||||||
| import { PeerRoom, PeerHookType, PeerStreamType } from 'lib/PeerRoom' | import { PeerRoom, PeerHookType, PeerStreamType } from 'lib/PeerRoom' | ||||||
| 
 | 
 | ||||||
| interface UseRoomScreenShareConfig { | interface UseRoomScreenShareConfig { | ||||||
| @ -16,7 +22,13 @@ export function useRoomScreenShare({ peerRoom }: UseRoomScreenShareConfig) { | |||||||
|   const roomContext = useContext(RoomContext) |   const roomContext = useContext(RoomContext) | ||||||
|   const [isSharingScreen, setIsSharingScreen] = useState(false) |   const [isSharingScreen, setIsSharingScreen] = useState(false) | ||||||
| 
 | 
 | ||||||
|   const { peerList, setPeerList, setScreenState } = shellContext |   const { | ||||||
|  |     peerList, | ||||||
|  |     setPeerList, | ||||||
|  |     setScreenState, | ||||||
|  |     setAudioChannelState, | ||||||
|  |     setPeerAudioChannels, | ||||||
|  |   } = shellContext | ||||||
| 
 | 
 | ||||||
|   const { |   const { | ||||||
|     peerScreenStreams, |     peerScreenStreams, | ||||||
| @ -50,7 +62,7 @@ export function useRoomScreenShare({ peerRoom }: UseRoomScreenShareConfig) { | |||||||
|     const isScreenShareStream = |     const isScreenShareStream = | ||||||
|       isRecord(metadata) && |       isRecord(metadata) && | ||||||
|       'type' in metadata && |       'type' in metadata && | ||||||
|       metadata.type === VideoStreamType.SCREEN_SHARE |       metadata.type === StreamType.SCREEN_SHARE | ||||||
| 
 | 
 | ||||||
|     if (!isScreenShareStream) return |     if (!isScreenShareStream) return | ||||||
| 
 | 
 | ||||||
| @ -58,6 +70,33 @@ export function useRoomScreenShare({ peerRoom }: UseRoomScreenShareConfig) { | |||||||
|       ...peerScreenStreams, |       ...peerScreenStreams, | ||||||
|       [peerId]: stream, |       [peerId]: stream, | ||||||
|     }) |     }) | ||||||
|  | 
 | ||||||
|  |     const [audioStream] = stream.getAudioTracks() | ||||||
|  | 
 | ||||||
|  |     if (audioStream) { | ||||||
|  |       setAudioChannelState(prevState => ({ | ||||||
|  |         ...prevState, | ||||||
|  |         [AudioChannelName.SCREEN_SHARE]: AudioState.PLAYING, | ||||||
|  |       })) | ||||||
|  | 
 | ||||||
|  |       const audioTracks = stream.getAudioTracks() | ||||||
|  | 
 | ||||||
|  |       if (audioTracks.length > 0) { | ||||||
|  |         const audio = new Audio() | ||||||
|  |         audio.srcObject = stream | ||||||
|  |         audio.autoplay = true | ||||||
|  | 
 | ||||||
|  |         setPeerAudioChannels(peerAudioChannels => { | ||||||
|  |           return { | ||||||
|  |             ...peerAudioChannels, | ||||||
|  |             [peerId]: { | ||||||
|  |               ...peerAudioChannels[peerId], | ||||||
|  |               [AudioChannelName.SCREEN_SHARE]: audio, | ||||||
|  |             }, | ||||||
|  |           } | ||||||
|  |         }) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   const cleanupScreenStream = useCallback(() => { |   const cleanupScreenStream = useCallback(() => { | ||||||
| @ -78,8 +117,9 @@ export function useRoomScreenShare({ peerRoom }: UseRoomScreenShareConfig) { | |||||||
|     }) |     }) | ||||||
| 
 | 
 | ||||||
|     peerRoom.addStream(displayMedia, null, { |     peerRoom.addStream(displayMedia, null, { | ||||||
|       type: VideoStreamType.SCREEN_SHARE, |       type: StreamType.SCREEN_SHARE, | ||||||
|     }) |     }) | ||||||
|  | 
 | ||||||
|     setSelfScreenStream(displayMedia) |     setSelfScreenStream(displayMedia) | ||||||
|     sendScreenShare(ScreenShareState.SHARING) |     sendScreenShare(ScreenShareState.SHARING) | ||||||
|     setScreenState(ScreenShareState.SHARING) |     setScreenState(ScreenShareState.SHARING) | ||||||
| @ -119,15 +159,33 @@ export function useRoomScreenShare({ peerRoom }: UseRoomScreenShareConfig) { | |||||||
|   }, [setPeerScreenStreams]) |   }, [setPeerScreenStreams]) | ||||||
| 
 | 
 | ||||||
|   const deletePeerScreen = (peerId: string) => { |   const deletePeerScreen = (peerId: string) => { | ||||||
|     const newPeerScreens = { ...peerScreenStreams } |     setPeerScreenStreams(({ [peerId]: _, ...newPeerScreens }) => { | ||||||
|     delete newPeerScreens[peerId] |       return newPeerScreens | ||||||
|     setPeerScreenStreams(newPeerScreens) |     }) | ||||||
|  | 
 | ||||||
|  |     setPeerAudioChannels(({ ...newPeerAudios }) => { | ||||||
|  |       if (!newPeerAudios[peerId]) { | ||||||
|  |         return newPeerAudios | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       const screenShareAudio = | ||||||
|  |         newPeerAudios[peerId][AudioChannelName.SCREEN_SHARE] | ||||||
|  | 
 | ||||||
|  |       screenShareAudio?.pause() | ||||||
|  | 
 | ||||||
|  |       const { [AudioChannelName.SCREEN_SHARE]: _, ...newPeerAudioChannels } = | ||||||
|  |         newPeerAudios[peerId] | ||||||
|  | 
 | ||||||
|  |       newPeerAudios[peerId] = newPeerAudioChannels | ||||||
|  | 
 | ||||||
|  |       return newPeerAudios | ||||||
|  |     }) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   const handleScreenForNewPeer = (peerId: string) => { |   const handleScreenForNewPeer = (peerId: string) => { | ||||||
|     if (selfScreenStream) { |     if (selfScreenStream) { | ||||||
|       peerRoom.addStream(selfScreenStream, peerId, { |       peerRoom.addStream(selfScreenStream, peerId, { | ||||||
|         type: VideoStreamType.SCREEN_SHARE, |         type: StreamType.SCREEN_SHARE, | ||||||
|       }) |       }) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ import { useContext, useEffect, useCallback, useState } from 'react' | |||||||
| import { RoomContext } from 'contexts/RoomContext' | import { RoomContext } from 'contexts/RoomContext' | ||||||
| import { ShellContext } from 'contexts/ShellContext' | import { ShellContext } from 'contexts/ShellContext' | ||||||
| import { PeerActions } from 'models/network' | import { PeerActions } from 'models/network' | ||||||
| import { VideoState, Peer, VideoStreamType } from 'models/chat' | import { VideoState, Peer, StreamType } from 'models/chat' | ||||||
| import { PeerRoom, PeerHookType, PeerStreamType } from 'lib/PeerRoom' | import { PeerRoom, PeerHookType, PeerStreamType } from 'lib/PeerRoom' | ||||||
| import { isRecord } from 'lib/type-guards' | import { isRecord } from 'lib/type-guards' | ||||||
| 
 | 
 | ||||||
| @ -60,8 +60,9 @@ export function useRoomVideo({ peerRoom }: UseRoomVideoConfig) { | |||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
|         peerRoom.addStream(newSelfStream, null, { |         peerRoom.addStream(newSelfStream, null, { | ||||||
|           type: VideoStreamType.WEBCAM, |           type: StreamType.WEBCAM, | ||||||
|         }) |         }) | ||||||
|  | 
 | ||||||
|         setSelfVideoStream(newSelfStream) |         setSelfVideoStream(newSelfStream) | ||||||
|       } |       } | ||||||
|     })() |     })() | ||||||
| @ -93,7 +94,7 @@ export function useRoomVideo({ peerRoom }: UseRoomVideoConfig) { | |||||||
|     const isWebcamStream = |     const isWebcamStream = | ||||||
|       isRecord(metadata) && |       isRecord(metadata) && | ||||||
|       'type' in metadata && |       'type' in metadata && | ||||||
|       metadata.type === VideoStreamType.WEBCAM |       metadata.type === StreamType.WEBCAM | ||||||
| 
 | 
 | ||||||
|     if (!isWebcamStream) return |     if (!isWebcamStream) return | ||||||
| 
 | 
 | ||||||
| @ -124,8 +125,9 @@ export function useRoomVideo({ peerRoom }: UseRoomVideoConfig) { | |||||||
|           }) |           }) | ||||||
| 
 | 
 | ||||||
|           peerRoom.addStream(newSelfStream, null, { |           peerRoom.addStream(newSelfStream, null, { | ||||||
|             type: VideoStreamType.WEBCAM, |             type: StreamType.WEBCAM, | ||||||
|           }) |           }) | ||||||
|  | 
 | ||||||
|           sendVideoChange(VideoState.PLAYING) |           sendVideoChange(VideoState.PLAYING) | ||||||
|           setVideoState(VideoState.PLAYING) |           setVideoState(VideoState.PLAYING) | ||||||
|           setSelfVideoStream(newSelfStream) |           setSelfVideoStream(newSelfStream) | ||||||
| @ -193,7 +195,7 @@ export function useRoomVideo({ peerRoom }: UseRoomVideoConfig) { | |||||||
|       }, |       }, | ||||||
|     }) |     }) | ||||||
| 
 | 
 | ||||||
|     peerRoom.addStream(newSelfStream, null, { type: VideoStreamType.WEBCAM }) |     peerRoom.addStream(newSelfStream, null, { type: StreamType.WEBCAM }) | ||||||
|     setSelfVideoStream(newSelfStream) |     setSelfVideoStream(newSelfStream) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -206,7 +208,7 @@ export function useRoomVideo({ peerRoom }: UseRoomVideoConfig) { | |||||||
|   const handleVideoForNewPeer = (peerId: string) => { |   const handleVideoForNewPeer = (peerId: string) => { | ||||||
|     if (selfVideoStream) { |     if (selfVideoStream) { | ||||||
|       peerRoom.addStream(selfVideoStream, peerId, { |       peerRoom.addStream(selfVideoStream, peerId, { | ||||||
|         type: VideoStreamType.WEBCAM, |         type: StreamType.WEBCAM, | ||||||
|       }) |       }) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -9,7 +9,13 @@ import Box from '@mui/material/Box' | |||||||
| import CircularProgress from '@mui/material/CircularProgress' | import CircularProgress from '@mui/material/CircularProgress' | ||||||
| 
 | 
 | ||||||
| import { UserInfo } from 'components/UserInfo' | import { UserInfo } from 'components/UserInfo' | ||||||
| import { AudioState, Peer } from 'models/chat' | import { | ||||||
|  |   AudioState, | ||||||
|  |   Peer, | ||||||
|  |   AudioChannel, | ||||||
|  |   AudioChannelName, | ||||||
|  |   PeerAudioChannelState, | ||||||
|  | } from 'models/chat' | ||||||
| import { PeerConnectionType } from 'lib/PeerRoom' | import { PeerConnectionType } from 'lib/PeerRoom' | ||||||
| import { TrackerConnection } from 'lib/ConnectionTest' | import { TrackerConnection } from 'lib/ConnectionTest' | ||||||
| 
 | 
 | ||||||
| @ -25,8 +31,8 @@ export interface PeerListProps extends PropsWithChildren { | |||||||
|   onPeerListClose: () => void |   onPeerListClose: () => void | ||||||
|   peerList: Peer[] |   peerList: Peer[] | ||||||
|   peerConnectionTypes: Record<string, PeerConnectionType> |   peerConnectionTypes: Record<string, PeerConnectionType> | ||||||
|   audioState: AudioState |   peerAudioChannelState: PeerAudioChannelState | ||||||
|   peerAudios: Record<string, HTMLAudioElement> |   peerAudioChannels: Record<string, AudioChannel> | ||||||
|   connectionTestResults: IConnectionTestResults |   connectionTestResults: IConnectionTestResults | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -36,8 +42,8 @@ export const PeerList = ({ | |||||||
|   onPeerListClose, |   onPeerListClose, | ||||||
|   peerList, |   peerList, | ||||||
|   peerConnectionTypes, |   peerConnectionTypes, | ||||||
|   audioState, |   peerAudioChannelState, | ||||||
|   peerAudios, |   peerAudioChannels, | ||||||
|   connectionTestResults, |   connectionTestResults, | ||||||
| }: PeerListProps) => { | }: PeerListProps) => { | ||||||
|   return ( |   return ( | ||||||
| @ -49,7 +55,8 @@ export const PeerList = ({ | |||||||
|       <Divider /> |       <Divider /> | ||||||
|       <List> |       <List> | ||||||
|         <ListItem divider={true}> |         <ListItem divider={true}> | ||||||
|           {audioState === AudioState.PLAYING && ( |           {peerAudioChannelState[AudioChannelName.MICROPHONE] === | ||||||
|  |             AudioState.PLAYING && ( | ||||||
|             <ListItemIcon> |             <ListItemIcon> | ||||||
|               <VolumeUp /> |               <VolumeUp /> | ||||||
|             </ListItemIcon> |             </ListItemIcon> | ||||||
| @ -63,7 +70,7 @@ export const PeerList = ({ | |||||||
|             key={peer.peerId} |             key={peer.peerId} | ||||||
|             peer={peer} |             peer={peer} | ||||||
|             peerConnectionTypes={peerConnectionTypes} |             peerConnectionTypes={peerConnectionTypes} | ||||||
|             peerAudios={peerAudios} |             peerAudioChannels={peerAudioChannels} | ||||||
|           /> |           /> | ||||||
|         ))} |         ))} | ||||||
|         {peerList.length === 0 && |         {peerList.length === 0 && | ||||||
|  | |||||||
| @ -18,7 +18,12 @@ import EnhancedEncryptionIcon from '@mui/icons-material/EnhancedEncryption' | |||||||
| import { AudioVolume } from 'components/AudioVolume' | import { AudioVolume } from 'components/AudioVolume' | ||||||
| import { PeerNameDisplay } from 'components/PeerNameDisplay' | import { PeerNameDisplay } from 'components/PeerNameDisplay' | ||||||
| import { PublicKey } from 'components/PublicKey' | import { PublicKey } from 'components/PublicKey' | ||||||
| import { Peer, PeerVerificationState } from 'models/chat' | import { | ||||||
|  |   Peer, | ||||||
|  |   AudioChannel, | ||||||
|  |   AudioChannelName, | ||||||
|  |   PeerVerificationState, | ||||||
|  | } from 'models/chat' | ||||||
| import { PeerConnectionType } from 'lib/PeerRoom' | import { PeerConnectionType } from 'lib/PeerRoom' | ||||||
| 
 | 
 | ||||||
| import { PeerDownloadFileButton } from './PeerDownloadFileButton' | import { PeerDownloadFileButton } from './PeerDownloadFileButton' | ||||||
| @ -26,7 +31,7 @@ import { PeerDownloadFileButton } from './PeerDownloadFileButton' | |||||||
| interface PeerListItemProps { | interface PeerListItemProps { | ||||||
|   peer: Peer |   peer: Peer | ||||||
|   peerConnectionTypes: Record<string, PeerConnectionType> |   peerConnectionTypes: Record<string, PeerConnectionType> | ||||||
|   peerAudios: Record<string, HTMLAudioElement> |   peerAudioChannels: Record<string, AudioChannel> | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const verificationStateDisplayMap = { | const verificationStateDisplayMap = { | ||||||
| @ -52,8 +57,8 @@ const iconRightPadding = 1 | |||||||
| export const PeerListItem = ({ | export const PeerListItem = ({ | ||||||
|   peer, |   peer, | ||||||
|   peerConnectionTypes, |   peerConnectionTypes, | ||||||
|   peerAudios, |   peerAudioChannels, | ||||||
| }: PeerListItemProps): JSX.Element => { | }: PeerListItemProps) => { | ||||||
|   const [showPeerDialog, setShowPeerDialog] = useState(false) |   const [showPeerDialog, setShowPeerDialog] = useState(false) | ||||||
| 
 | 
 | ||||||
|   const hasPeerConnection = peer.peerId in peerConnectionTypes |   const hasPeerConnection = peer.peerId in peerConnectionTypes | ||||||
| @ -69,6 +74,11 @@ export const PeerListItem = ({ | |||||||
|     setShowPeerDialog(false) |     setShowPeerDialog(false) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   const microphoneAudio = | ||||||
|  |     peerAudioChannels[peer.peerId]?.[AudioChannelName.MICROPHONE] | ||||||
|  |   const screenShareAudio = | ||||||
|  |     peerAudioChannels[peer.peerId]?.[AudioChannelName.SCREEN_SHARE] | ||||||
|  | 
 | ||||||
|   return ( |   return ( | ||||||
|     <> |     <> | ||||||
|       <ListItem key={peer.peerId} divider={true}> |       <ListItem key={peer.peerId} divider={true}> | ||||||
| @ -124,8 +134,17 @@ export const PeerListItem = ({ | |||||||
|             </Box> |             </Box> | ||||||
|             <PeerNameDisplay>{peer.userId}</PeerNameDisplay> |             <PeerNameDisplay>{peer.userId}</PeerNameDisplay> | ||||||
|           </Box> |           </Box> | ||||||
|           {peer.peerId in peerAudios && ( |           {microphoneAudio && ( | ||||||
|             <AudioVolume audioEl={peerAudios[peer.peerId]} /> |             <AudioVolume | ||||||
|  |               audioEl={microphoneAudio} | ||||||
|  |               audioChannelName={AudioChannelName.MICROPHONE} | ||||||
|  |             /> | ||||||
|  |           )} | ||||||
|  |           {screenShareAudio && ( | ||||||
|  |             <AudioVolume | ||||||
|  |               audioEl={screenShareAudio} | ||||||
|  |               audioChannelName={AudioChannelName.SCREEN_SHARE} | ||||||
|  |             /> | ||||||
|           )} |           )} | ||||||
|         </ListItemText> |         </ListItemText> | ||||||
|       </ListItem> |       </ListItem> | ||||||
|  | |||||||
| @ -19,7 +19,15 @@ import { useWindowSize } from '@react-hook/window-size' | |||||||
| import { ShellContext } from 'contexts/ShellContext' | import { ShellContext } from 'contexts/ShellContext' | ||||||
| import { SettingsContext } from 'contexts/SettingsContext' | import { SettingsContext } from 'contexts/SettingsContext' | ||||||
| import { AlertOptions, QueryParamKeys } from 'models/shell' | import { AlertOptions, QueryParamKeys } from 'models/shell' | ||||||
| import { AudioState, ScreenShareState, VideoState, Peer } from 'models/chat' | import { | ||||||
|  |   AudioState, | ||||||
|  |   ScreenShareState, | ||||||
|  |   VideoState, | ||||||
|  |   Peer, | ||||||
|  |   AudioChannel, | ||||||
|  |   PeerAudioChannelState, | ||||||
|  |   AudioChannelName, | ||||||
|  | } from 'models/chat' | ||||||
| import { ErrorBoundary } from 'components/ErrorBoundary' | import { ErrorBoundary } from 'components/ErrorBoundary' | ||||||
| import { PeerConnectionType } from 'lib/PeerRoom' | import { PeerConnectionType } from 'lib/PeerRoom' | ||||||
| 
 | 
 | ||||||
| @ -86,7 +94,11 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => { | |||||||
|     Record<string, PeerConnectionType> |     Record<string, PeerConnectionType> | ||||||
|   >({}) |   >({}) | ||||||
|   const [tabHasFocus, setTabHasFocus] = useState(true) |   const [tabHasFocus, setTabHasFocus] = useState(true) | ||||||
|   const [audioState, setAudioState] = useState<AudioState>(AudioState.STOPPED) |   const [audioChannelState, setAudioChannelState] = | ||||||
|  |     useState<PeerAudioChannelState>({ | ||||||
|  |       [AudioChannelName.MICROPHONE]: AudioState.STOPPED, | ||||||
|  |       [AudioChannelName.SCREEN_SHARE]: AudioState.STOPPED, | ||||||
|  |     }) | ||||||
|   const [videoState, setVideoState] = useState<VideoState>(VideoState.STOPPED) |   const [videoState, setVideoState] = useState<VideoState>(VideoState.STOPPED) | ||||||
|   const [screenState, setScreenState] = useState<ScreenShareState>( |   const [screenState, setScreenState] = useState<ScreenShareState>( | ||||||
|     ScreenShareState.NOT_SHARING |     ScreenShareState.NOT_SHARING | ||||||
| @ -94,8 +106,8 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => { | |||||||
|   const [customUsername, setCustomUsername] = useState( |   const [customUsername, setCustomUsername] = useState( | ||||||
|     getUserSettings().customUsername |     getUserSettings().customUsername | ||||||
|   ) |   ) | ||||||
|   const [peerAudios, setPeerAudios] = useState< |   const [peerAudioChannels, setPeerAudioChannels] = useState< | ||||||
|     Record<string, HTMLAudioElement> |     Record<string, AudioChannel> | ||||||
|   >({}) |   >({}) | ||||||
| 
 | 
 | ||||||
|   const showAlert = useCallback((message: string, options?: AlertOptions) => { |   const showAlert = useCallback((message: string, options?: AlertOptions) => { | ||||||
| @ -144,14 +156,14 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => { | |||||||
|       setIsServerConnectionFailureDialogOpen, |       setIsServerConnectionFailureDialogOpen, | ||||||
|       peerConnectionTypes, |       peerConnectionTypes, | ||||||
|       setPeerConnectionTypes, |       setPeerConnectionTypes, | ||||||
|       audioState, |       audioChannelState, | ||||||
|       setAudioState, |       setAudioChannelState, | ||||||
|       videoState, |       videoState, | ||||||
|       setVideoState, |       setVideoState, | ||||||
|       screenState, |       screenState, | ||||||
|       setScreenState, |       setScreenState, | ||||||
|       peerAudios, |       peerAudioChannels, | ||||||
|       setPeerAudios, |       setPeerAudioChannels, | ||||||
|       customUsername, |       customUsername, | ||||||
|       setCustomUsername, |       setCustomUsername, | ||||||
|       connectionTestResults, |       connectionTestResults, | ||||||
| @ -174,14 +186,14 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => { | |||||||
|       setShowRoomControls, |       setShowRoomControls, | ||||||
|       setTitle, |       setTitle, | ||||||
|       showAlert, |       showAlert, | ||||||
|       audioState, |       audioChannelState, | ||||||
|       setAudioState, |       setAudioChannelState, | ||||||
|       videoState, |       videoState, | ||||||
|       setVideoState, |       setVideoState, | ||||||
|       screenState, |       screenState, | ||||||
|       setScreenState, |       setScreenState, | ||||||
|       peerAudios, |       peerAudioChannels, | ||||||
|       setPeerAudios, |       setPeerAudioChannels, | ||||||
|       customUsername, |       customUsername, | ||||||
|       setCustomUsername, |       setCustomUsername, | ||||||
|       connectionTestResults, |       connectionTestResults, | ||||||
| @ -393,8 +405,8 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => { | |||||||
|                   onPeerListClose={handlePeerListClick} |                   onPeerListClose={handlePeerListClick} | ||||||
|                   peerList={peerList} |                   peerList={peerList} | ||||||
|                   peerConnectionTypes={peerConnectionTypes} |                   peerConnectionTypes={peerConnectionTypes} | ||||||
|                   audioState={audioState} |                   peerAudioChannelState={audioChannelState} | ||||||
|                   peerAudios={peerAudios} |                   peerAudioChannels={peerAudioChannels} | ||||||
|                   connectionTestResults={connectionTestResults} |                   connectionTestResults={connectionTestResults} | ||||||
|                 /> |                 /> | ||||||
|                 {isEmbedded ? ( |                 {isEmbedded ? ( | ||||||
|  | |||||||
| @ -1,7 +1,15 @@ | |||||||
| import { createContext, Dispatch, SetStateAction } from 'react' | import { createContext, Dispatch, SetStateAction } from 'react' | ||||||
| 
 | 
 | ||||||
| import { AlertOptions } from 'models/shell' | import { AlertOptions } from 'models/shell' | ||||||
| import { AudioState, ScreenShareState, VideoState, Peer } from 'models/chat' | import { | ||||||
|  |   AudioState, | ||||||
|  |   ScreenShareState, | ||||||
|  |   VideoState, | ||||||
|  |   Peer, | ||||||
|  |   AudioChannel, | ||||||
|  |   PeerAudioChannelState, | ||||||
|  |   AudioChannelName, | ||||||
|  | } from 'models/chat' | ||||||
| import { PeerConnectionType } from 'lib/PeerRoom' | import { PeerConnectionType } from 'lib/PeerRoom' | ||||||
| import { ConnectionTestResults } from 'components/Shell/useConnectionTest' | import { ConnectionTestResults } from 'components/Shell/useConnectionTest' | ||||||
| import { TrackerConnection } from 'lib/ConnectionTest' | import { TrackerConnection } from 'lib/ConnectionTest' | ||||||
| @ -27,14 +35,14 @@ interface ShellContextProps { | |||||||
|   setPeerConnectionTypes: Dispatch< |   setPeerConnectionTypes: Dispatch< | ||||||
|     SetStateAction<Record<string, PeerConnectionType>> |     SetStateAction<Record<string, PeerConnectionType>> | ||||||
|   > |   > | ||||||
|   audioState: AudioState |   audioChannelState: PeerAudioChannelState | ||||||
|   setAudioState: Dispatch<SetStateAction<AudioState>> |   setAudioChannelState: Dispatch<SetStateAction<PeerAudioChannelState>> | ||||||
|   videoState: VideoState |   videoState: VideoState | ||||||
|   setVideoState: Dispatch<SetStateAction<VideoState>> |   setVideoState: Dispatch<SetStateAction<VideoState>> | ||||||
|   screenState: ScreenShareState |   screenState: ScreenShareState | ||||||
|   setScreenState: Dispatch<SetStateAction<ScreenShareState>> |   setScreenState: Dispatch<SetStateAction<ScreenShareState>> | ||||||
|   peerAudios: Record<string, HTMLAudioElement> |   peerAudioChannels: Record<string, AudioChannel> | ||||||
|   setPeerAudios: Dispatch<SetStateAction<Record<string, HTMLAudioElement>>> |   setPeerAudioChannels: Dispatch<SetStateAction<Record<string, AudioChannel>>> | ||||||
|   customUsername: string |   customUsername: string | ||||||
|   setCustomUsername: Dispatch<SetStateAction<string>> |   setCustomUsername: Dispatch<SetStateAction<string>> | ||||||
|   connectionTestResults: ConnectionTestResults |   connectionTestResults: ConnectionTestResults | ||||||
| @ -60,14 +68,17 @@ export const ShellContext = createContext<ShellContextProps>({ | |||||||
|   setIsServerConnectionFailureDialogOpen: () => {}, |   setIsServerConnectionFailureDialogOpen: () => {}, | ||||||
|   peerConnectionTypes: {}, |   peerConnectionTypes: {}, | ||||||
|   setPeerConnectionTypes: () => {}, |   setPeerConnectionTypes: () => {}, | ||||||
|   audioState: AudioState.STOPPED, |   audioChannelState: { | ||||||
|   setAudioState: () => {}, |     [AudioChannelName.MICROPHONE]: AudioState.STOPPED, | ||||||
|  |     [AudioChannelName.SCREEN_SHARE]: AudioState.STOPPED, | ||||||
|  |   }, | ||||||
|  |   setAudioChannelState: () => {}, | ||||||
|   videoState: VideoState.STOPPED, |   videoState: VideoState.STOPPED, | ||||||
|   setVideoState: () => {}, |   setVideoState: () => {}, | ||||||
|   screenState: ScreenShareState.NOT_SHARING, |   screenState: ScreenShareState.NOT_SHARING, | ||||||
|   setScreenState: () => {}, |   setScreenState: () => {}, | ||||||
|   peerAudios: {}, |   peerAudioChannels: {}, | ||||||
|   setPeerAudios: () => {}, |   setPeerAudioChannels: () => {}, | ||||||
|   customUsername: '', |   customUsername: '', | ||||||
|   setCustomUsername: () => {}, |   setCustomUsername: () => {}, | ||||||
|   connectionTestResults: { |   connectionTestResults: { | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ import { joinRoom, Room, BaseRoomConfig, DataPayload } from 'trystero' | |||||||
| import { RelayConfig } from 'trystero/torrent' | import { RelayConfig } from 'trystero/torrent' | ||||||
| 
 | 
 | ||||||
| import { sleep } from 'lib/sleep' | import { sleep } from 'lib/sleep' | ||||||
|  | import { StreamType } from 'models/chat' | ||||||
| 
 | 
 | ||||||
| export enum PeerHookType { | export enum PeerHookType { | ||||||
|   NEW_PEER = 'NEW_PEER', |   NEW_PEER = 'NEW_PEER', | ||||||
| @ -171,12 +172,16 @@ export class PeerRoom { | |||||||
|     return this.room.makeAction<T>(namespace) |     return this.room.makeAction<T>(namespace) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   addStream = (...args: Parameters<Room['addStream']>) => { |   addStream = ( | ||||||
|  |     stream: Parameters<Room['addStream']>[0], | ||||||
|  |     targetPeers: Parameters<Room['addStream']>[1], | ||||||
|  |     metadata: { type: StreamType } | ||||||
|  |   ) => { | ||||||
|     // New streams need to be added as a delayed queue to prevent race
 |     // New streams need to be added as a delayed queue to prevent race
 | ||||||
|     // conditions on the receiver's end where streams and their metadata get
 |     // conditions on the receiver's end where streams and their metadata get
 | ||||||
|     // mixed up.
 |     // mixed up.
 | ||||||
|     this.streamQueue.push( |     this.streamQueue.push( | ||||||
|       () => Promise.all(this.room.addStream(...args)), |       () => Promise.all(this.room.addStream(stream, targetPeers, metadata)), | ||||||
|       () => sleep(streamQueueAddDelay) |       () => sleep(streamQueueAddDelay) | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -31,9 +31,10 @@ export enum VideoState { | |||||||
|   STOPPED = 'STOPPED', |   STOPPED = 'STOPPED', | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export enum VideoStreamType { | export enum StreamType { | ||||||
|   WEBCAM = 'WEBCAM', |   WEBCAM = 'WEBCAM', | ||||||
|   SCREEN_SHARE = 'SCREEN_SHARE', |   SCREEN_SHARE = 'SCREEN_SHARE', | ||||||
|  |   MICROPHONE = 'MICROPHONE', | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export enum ScreenShareState { | export enum ScreenShareState { | ||||||
| @ -47,12 +48,21 @@ export enum PeerVerificationState { | |||||||
|   VERIFIED, |   VERIFIED, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | export enum AudioChannelName { | ||||||
|  |   MICROPHONE = 'microphone', | ||||||
|  |   SCREEN_SHARE = 'screen-share', | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export type AudioChannel = Partial<Record<AudioChannelName, HTMLAudioElement>> | ||||||
|  | 
 | ||||||
|  | export type PeerAudioChannelState = Record<AudioChannelName, AudioState> | ||||||
|  | 
 | ||||||
| export interface Peer { | export interface Peer { | ||||||
|   peerId: string |   peerId: string | ||||||
|   userId: string |   userId: string | ||||||
|   publicKey: CryptoKey |   publicKey: CryptoKey | ||||||
|   customUsername: string |   customUsername: string | ||||||
|   audioState: AudioState |   audioChannelState: PeerAudioChannelState | ||||||
|   videoState: VideoState |   videoState: VideoState | ||||||
|   screenShareState: ScreenShareState |   screenShareState: ScreenShareState | ||||||
|   offeredFileId: string | null |   offeredFileId: string | null | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user