* 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