forked from Shiloh/remnantchat
* feat: [#67] stand up useRoomScreenShare hook * feat: [#67] stand up RoomScreenShareControls * feat: [#67] display screen share streams * fix: [#67] don't flip screen share preview * feat: don't display screen share controls in unsupported environments * fix: [#67] always remove media streams for exiting peers
This commit is contained in:
parent
f6314501a2
commit
75a804abbd
13
package-lock.json
generated
13
package-lock.json
generated
@ -34,7 +34,7 @@
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"sass": "^1.54.3",
|
||||
"trystero": "^0.11.4",
|
||||
"trystero": "github:jeremyckahn/trystero#bugfix/stream-metadata-type",
|
||||
"typeface-public-sans": "^1.1.13",
|
||||
"typeface-roboto": "^1.1.13",
|
||||
"typescript": "^4.7.4",
|
||||
@ -22977,9 +22977,9 @@
|
||||
"integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA=="
|
||||
},
|
||||
"node_modules/trystero": {
|
||||
"version": "0.11.4",
|
||||
"resolved": "https://registry.npmjs.org/trystero/-/trystero-0.11.4.tgz",
|
||||
"integrity": "sha512-x5SkPXlodoNhs1o2TLjarABfBLPtuR2iKovk3mtboCCr349eclpGrK3JPjs4Rp+coAMqC+l/ZcC39GCR9VCvlQ==",
|
||||
"version": "0.11.6",
|
||||
"resolved": "git+ssh://git@github.com/jeremyckahn/trystero.git#b270de4b79b2fef1ca550187b6c0fbe72d4e8118",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"firebase": "^9.6.5",
|
||||
"ipfs-core": "0.9.0",
|
||||
@ -41257,9 +41257,8 @@
|
||||
"integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA=="
|
||||
},
|
||||
"trystero": {
|
||||
"version": "0.11.4",
|
||||
"resolved": "https://registry.npmjs.org/trystero/-/trystero-0.11.4.tgz",
|
||||
"integrity": "sha512-x5SkPXlodoNhs1o2TLjarABfBLPtuR2iKovk3mtboCCr349eclpGrK3JPjs4Rp+coAMqC+l/ZcC39GCR9VCvlQ==",
|
||||
"version": "git+ssh://git@github.com/jeremyckahn/trystero.git#b270de4b79b2fef1ca550187b6c0fbe72d4e8118",
|
||||
"from": "trystero@github:jeremyckahn/trystero#bugfix/stream-metadata-type",
|
||||
"requires": {
|
||||
"firebase": "^9.6.5",
|
||||
"ipfs-core": "0.9.0",
|
||||
|
@ -30,7 +30,7 @@
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"sass": "^1.54.3",
|
||||
"trystero": "^0.11.4",
|
||||
"trystero": "github:jeremyckahn/trystero#bugfix/stream-metadata-type",
|
||||
"typeface-public-sans": "^1.1.13",
|
||||
"typeface-roboto": "^1.1.13",
|
||||
"typescript": "^4.7.4",
|
||||
|
@ -4,8 +4,8 @@ import Paper from '@mui/material/Paper'
|
||||
import { PeerNameDisplay } from 'components/PeerNameDisplay'
|
||||
|
||||
interface PeerVideoProps {
|
||||
isSelf?: boolean
|
||||
numberOfPeers: number
|
||||
isSelfVideo?: boolean
|
||||
numberOfVideos: number
|
||||
userId: string
|
||||
videoStream: MediaStream
|
||||
}
|
||||
@ -18,8 +18,8 @@ const nextPerfectSquare = (base: number) => {
|
||||
}
|
||||
|
||||
export const PeerVideo = ({
|
||||
isSelf,
|
||||
numberOfPeers,
|
||||
isSelfVideo,
|
||||
numberOfVideos,
|
||||
userId,
|
||||
videoStream,
|
||||
}: PeerVideoProps) => {
|
||||
@ -33,7 +33,7 @@ export const PeerVideo = ({
|
||||
video.srcObject = videoStream
|
||||
}, [videoRef, videoStream])
|
||||
|
||||
const sizePercent = 100 / Math.sqrt(nextPerfectSquare(numberOfPeers - 1))
|
||||
const sizePercent = 100 / Math.sqrt(nextPerfectSquare(numberOfVideos - 1))
|
||||
|
||||
return (
|
||||
<Paper
|
||||
@ -59,7 +59,7 @@ export const PeerVideo = ({
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
height: '100%',
|
||||
...(isSelf && {
|
||||
...(isSelfVideo && {
|
||||
transform: 'rotateY(180deg)',
|
||||
}),
|
||||
}}
|
||||
|
@ -15,6 +15,7 @@ import { ChatTranscript } from 'components/ChatTranscript'
|
||||
import { useRoom } from './useRoom'
|
||||
import { RoomAudioControls } from './RoomAudioControls'
|
||||
import { RoomVideoControls } from './RoomVideoControls'
|
||||
import { RoomScreenShareControls } from './RoomScreenShareControls'
|
||||
import { RoomVideoDisplay } from './RoomVideoDisplay'
|
||||
|
||||
export interface RoomProps {
|
||||
@ -93,6 +94,7 @@ export function Room({
|
||||
>
|
||||
<RoomAudioControls peerRoom={peerRoom} />
|
||||
<RoomVideoControls peerRoom={peerRoom} />
|
||||
<RoomScreenShareControls peerRoom={peerRoom} />
|
||||
</Box>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
|
58
src/components/Room/RoomScreenShareControls.tsx
Normal file
58
src/components/Room/RoomScreenShareControls.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
import Box from '@mui/material/Box'
|
||||
import ScreenShare from '@mui/icons-material/ScreenShare'
|
||||
import StopScreenShare from '@mui/icons-material/StopScreenShare'
|
||||
import Fab from '@mui/material/Fab'
|
||||
import Tooltip from '@mui/material/Tooltip'
|
||||
|
||||
import { PeerRoom } from 'services/PeerRoom/PeerRoom'
|
||||
|
||||
import { useRoomScreenShare } from './useRoomScreenShare'
|
||||
|
||||
export interface RoomVideoControlsProps {
|
||||
peerRoom: PeerRoom
|
||||
}
|
||||
|
||||
export function RoomScreenShareControls({ peerRoom }: RoomVideoControlsProps) {
|
||||
const { isSharingScreen, handleScreenShareStart, handleScreenShareStop } =
|
||||
useRoomScreenShare({
|
||||
peerRoom,
|
||||
})
|
||||
|
||||
const handleToggleScreenShareButtonClick = () => {
|
||||
if (isSharingScreen) {
|
||||
handleScreenShareStop()
|
||||
} else {
|
||||
handleScreenShareStart()
|
||||
}
|
||||
}
|
||||
|
||||
if (!window.navigator?.mediaDevices?.getDisplayMedia) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
px: 1,
|
||||
}}
|
||||
>
|
||||
<Tooltip
|
||||
title={
|
||||
isSharingScreen ? 'Stop sharing screen' : 'Share screen with room'
|
||||
}
|
||||
>
|
||||
<Fab
|
||||
color={isSharingScreen ? 'error' : 'success'}
|
||||
aria-label="share screen"
|
||||
onClick={handleToggleScreenShareButtonClick}
|
||||
>
|
||||
{isSharingScreen ? <StopScreenShare /> : <ScreenShare />}
|
||||
</Fab>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
)
|
||||
}
|
@ -66,7 +66,7 @@ export function RoomVideoControls({ peerRoom }: RoomVideoControlsProps) {
|
||||
<Tooltip title={isCameraEnabled ? 'Turn off camera' : 'Turn on camera'}>
|
||||
<Fab
|
||||
color={isCameraEnabled ? 'error' : 'success'}
|
||||
aria-label="call"
|
||||
aria-label="toggle camera"
|
||||
onClick={handleEnableCameraClick}
|
||||
>
|
||||
{isCameraEnabled ? <VideocamOff /> : <Videocam />}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useContext } from 'react'
|
||||
import { Fragment, useContext } from 'react'
|
||||
import Paper from '@mui/material/Paper'
|
||||
|
||||
import { RoomContext } from 'contexts/RoomContext'
|
||||
@ -7,7 +7,11 @@ import { Peer } from 'models/chat'
|
||||
|
||||
import { PeerVideo } from './PeerVideo'
|
||||
|
||||
type PeerWithVideo = { peer: Peer; videoStream: MediaStream }
|
||||
type PeerWithVideo = {
|
||||
peer: Peer
|
||||
videoStream?: MediaStream
|
||||
screenStream?: MediaStream
|
||||
}
|
||||
|
||||
export interface RoomVideoDisplayProps {
|
||||
userId: string
|
||||
@ -18,15 +22,23 @@ export const RoomVideoDisplay = ({ userId }: RoomVideoDisplayProps) => {
|
||||
const roomContext = useContext(RoomContext)
|
||||
|
||||
const { peerList } = shellContext
|
||||
const { peerVideoStreams, selfVideoStream } = roomContext
|
||||
const {
|
||||
peerVideoStreams,
|
||||
selfVideoStream,
|
||||
peerScreenStreams,
|
||||
selfScreenStream,
|
||||
} = roomContext
|
||||
|
||||
const peersWithVideo: PeerWithVideo[] = peerList.reduce(
|
||||
(acc: PeerWithVideo[], peer: Peer) => {
|
||||
const videoStream = peerVideoStreams[peer.peerId]
|
||||
if (videoStream) {
|
||||
const screenStream = peerScreenStreams[peer.peerId]
|
||||
|
||||
if (videoStream || screenStream) {
|
||||
acc.push({
|
||||
peer,
|
||||
videoStream,
|
||||
screenStream,
|
||||
})
|
||||
}
|
||||
|
||||
@ -35,7 +47,15 @@ export const RoomVideoDisplay = ({ userId }: RoomVideoDisplayProps) => {
|
||||
[]
|
||||
)
|
||||
|
||||
const numberOfPeers = (selfVideoStream ? 1 : 0) + peersWithVideo.length
|
||||
const numberOfVideos =
|
||||
(selfVideoStream ? 1 : 0) +
|
||||
(selfScreenStream ? 1 : 0) +
|
||||
peersWithVideo.reduce((sum, peerWithVideo) => {
|
||||
if (peerWithVideo.videoStream) sum++
|
||||
if (peerWithVideo.screenStream) sum++
|
||||
|
||||
return sum
|
||||
}, 0)
|
||||
|
||||
return (
|
||||
<Paper
|
||||
@ -46,7 +66,7 @@ export const RoomVideoDisplay = ({ userId }: RoomVideoDisplayProps) => {
|
||||
alignContent: 'center',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: numberOfPeers === 1 ? 'column' : 'row',
|
||||
flexDirection: numberOfVideos === 1 ? 'column' : 'row',
|
||||
flexGrow: 1,
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'center',
|
||||
@ -56,19 +76,36 @@ export const RoomVideoDisplay = ({ userId }: RoomVideoDisplayProps) => {
|
||||
>
|
||||
{selfVideoStream && (
|
||||
<PeerVideo
|
||||
isSelf
|
||||
numberOfPeers={numberOfPeers}
|
||||
isSelfVideo
|
||||
numberOfVideos={numberOfVideos}
|
||||
userId={userId}
|
||||
videoStream={selfVideoStream}
|
||||
/>
|
||||
)}
|
||||
{peersWithVideo.map(peerWithVideo => (
|
||||
{selfScreenStream && (
|
||||
<PeerVideo
|
||||
key={peerWithVideo.peer.peerId}
|
||||
numberOfPeers={numberOfPeers}
|
||||
userId={peerWithVideo.peer.userId}
|
||||
videoStream={peerWithVideo.videoStream}
|
||||
numberOfVideos={numberOfVideos}
|
||||
userId={userId}
|
||||
videoStream={selfScreenStream}
|
||||
/>
|
||||
)}
|
||||
{peersWithVideo.map(peerWithVideo => (
|
||||
<Fragment key={peerWithVideo.peer.peerId}>
|
||||
{peerWithVideo.videoStream && (
|
||||
<PeerVideo
|
||||
numberOfVideos={numberOfVideos}
|
||||
userId={peerWithVideo.peer.userId}
|
||||
videoStream={peerWithVideo.videoStream}
|
||||
/>
|
||||
)}
|
||||
{peerWithVideo.screenStream && (
|
||||
<PeerVideo
|
||||
numberOfVideos={numberOfVideos}
|
||||
userId={peerWithVideo.peer.userId}
|
||||
videoStream={peerWithVideo.screenStream}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
</Paper>
|
||||
)
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
ReceivedMessage,
|
||||
UnsentMessage,
|
||||
VideoState,
|
||||
ScreenShareState,
|
||||
isMessageReceived,
|
||||
} from 'models/chat'
|
||||
import { getPeerName } from 'components/PeerNameDisplay'
|
||||
@ -62,14 +63,34 @@ export function useRoom(
|
||||
Record<string, MediaStream>
|
||||
>({})
|
||||
|
||||
const [selfScreenStream, setSelfScreenStream] = useState<MediaStream | null>(
|
||||
null
|
||||
)
|
||||
const [peerScreenStreams, setPeerScreenStreams] = useState<
|
||||
Record<string, MediaStream>
|
||||
>({})
|
||||
|
||||
const roomContextValue = useMemo(
|
||||
() => ({
|
||||
selfVideoStream,
|
||||
setSelfVideoStream,
|
||||
peerVideoStreams,
|
||||
setPeerVideoStreams,
|
||||
selfScreenStream,
|
||||
setSelfScreenStream,
|
||||
peerScreenStreams,
|
||||
setPeerScreenStreams,
|
||||
}),
|
||||
[selfVideoStream, setSelfVideoStream, peerVideoStreams, setPeerVideoStreams]
|
||||
[
|
||||
selfVideoStream,
|
||||
setSelfVideoStream,
|
||||
peerVideoStreams,
|
||||
setPeerVideoStreams,
|
||||
selfScreenStream,
|
||||
setSelfScreenStream,
|
||||
peerScreenStreams,
|
||||
setPeerScreenStreams,
|
||||
]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
@ -131,6 +152,7 @@ export function useRoom(
|
||||
userId,
|
||||
audioState: AudioState.STOPPED,
|
||||
videoState: VideoState.STOPPED,
|
||||
screenShareState: ScreenShareState.NOT_SHARING,
|
||||
},
|
||||
])
|
||||
} else {
|
||||
@ -216,8 +238,11 @@ export function useRoom(
|
||||
}
|
||||
})
|
||||
|
||||
const showVideoDisplay =
|
||||
selfVideoStream || Object.values(peerVideoStreams).length > 0
|
||||
const showVideoDisplay = Boolean(
|
||||
selfVideoStream ||
|
||||
selfScreenStream ||
|
||||
Object.values({ ...peerVideoStreams, ...peerScreenStreams }).length > 0
|
||||
)
|
||||
|
||||
return {
|
||||
isMessageSending,
|
||||
|
159
src/components/Room/useRoomScreenShare.ts
Normal file
159
src/components/Room/useRoomScreenShare.ts
Normal file
@ -0,0 +1,159 @@
|
||||
import { useContext, useEffect, useCallback, useState } from 'react'
|
||||
|
||||
import { isRecord } from 'utils'
|
||||
import { RoomContext } from 'contexts/RoomContext'
|
||||
import { ShellContext } from 'contexts/ShellContext'
|
||||
import { PeerActions } from 'models/network'
|
||||
import { ScreenShareState, Peer, VideoStreamType } from 'models/chat'
|
||||
import { PeerRoom, PeerHookType, PeerStreamType } from 'services/PeerRoom'
|
||||
|
||||
import { usePeerRoomAction } from './usePeerRoomAction'
|
||||
|
||||
interface UseRoomScreenShareConfig {
|
||||
peerRoom: PeerRoom
|
||||
}
|
||||
|
||||
export function useRoomScreenShare({ peerRoom }: UseRoomScreenShareConfig) {
|
||||
const shellContext = useContext(ShellContext)
|
||||
const roomContext = useContext(RoomContext)
|
||||
const [isSharingScreen, setIsSharingScreen] = useState(false)
|
||||
|
||||
const { peerList, setPeerList, setScreenState } = shellContext
|
||||
|
||||
const {
|
||||
peerScreenStreams,
|
||||
selfScreenStream,
|
||||
setPeerScreenStreams,
|
||||
setSelfScreenStream,
|
||||
} = roomContext
|
||||
|
||||
const [sendScreenShare, receiveScreenShare] =
|
||||
usePeerRoomAction<ScreenShareState>(peerRoom, PeerActions.SCREEN_SHARE)
|
||||
|
||||
receiveScreenShare((screenState, peerId) => {
|
||||
const newPeerList = peerList.map(peer => {
|
||||
const newPeer: Peer = { ...peer }
|
||||
|
||||
if (peer.peerId === peerId) {
|
||||
newPeer.screenShareState = screenState
|
||||
|
||||
if (screenState === ScreenShareState.NOT_SHARING) {
|
||||
deletePeerScreen(peerId)
|
||||
}
|
||||
}
|
||||
|
||||
return newPeer
|
||||
})
|
||||
|
||||
setPeerList(newPeerList)
|
||||
})
|
||||
|
||||
peerRoom.onPeerStream(PeerStreamType.SCREEN, (stream, peerId, metadata) => {
|
||||
const isScreenShareStream =
|
||||
isRecord(metadata) &&
|
||||
'type' in metadata &&
|
||||
metadata.type === VideoStreamType.SCREEN_SHARE
|
||||
|
||||
if (!isScreenShareStream) return
|
||||
|
||||
setPeerScreenStreams({
|
||||
...peerScreenStreams,
|
||||
[peerId]: stream,
|
||||
})
|
||||
})
|
||||
|
||||
const cleanupScreenStream = useCallback(() => {
|
||||
if (!selfScreenStream) return
|
||||
|
||||
for (const screenStreamTrack of selfScreenStream.getTracks()) {
|
||||
screenStreamTrack.stop()
|
||||
selfScreenStream.removeTrack(screenStreamTrack)
|
||||
}
|
||||
}, [selfScreenStream])
|
||||
|
||||
const handleScreenShareStart = async () => {
|
||||
if (selfScreenStream) return
|
||||
|
||||
const displayMedia = await window.navigator.mediaDevices.getDisplayMedia({
|
||||
audio: true,
|
||||
video: true,
|
||||
})
|
||||
|
||||
peerRoom.addStream(displayMedia, null, {
|
||||
type: VideoStreamType.SCREEN_SHARE,
|
||||
})
|
||||
setSelfScreenStream(displayMedia)
|
||||
sendScreenShare(ScreenShareState.SHARING)
|
||||
setScreenState(ScreenShareState.SHARING)
|
||||
setIsSharingScreen(true)
|
||||
}
|
||||
|
||||
const handleScreenShareStop = () => {
|
||||
if (!selfScreenStream) return
|
||||
|
||||
cleanupScreenStream()
|
||||
peerRoom.removeStream(selfScreenStream, peerRoom.getPeers())
|
||||
sendScreenShare(ScreenShareState.NOT_SHARING)
|
||||
setScreenState(ScreenShareState.NOT_SHARING)
|
||||
setSelfScreenStream(null)
|
||||
setIsSharingScreen(false)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
cleanupScreenStream()
|
||||
}
|
||||
}, [cleanupScreenStream])
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (selfScreenStream) {
|
||||
setSelfScreenStream(null)
|
||||
setScreenState(ScreenShareState.NOT_SHARING)
|
||||
}
|
||||
}
|
||||
}, [selfScreenStream, setSelfScreenStream, setScreenState])
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
setPeerScreenStreams({})
|
||||
}
|
||||
}, [setPeerScreenStreams])
|
||||
|
||||
const deletePeerScreen = (peerId: string) => {
|
||||
const newPeerScreens = { ...peerScreenStreams }
|
||||
delete newPeerScreens[peerId]
|
||||
setPeerScreenStreams(newPeerScreens)
|
||||
}
|
||||
|
||||
const handleScreenForNewPeer = (peerId: string) => {
|
||||
if (selfScreenStream) {
|
||||
peerRoom.addStream(selfScreenStream, peerId, {
|
||||
type: VideoStreamType.SCREEN_SHARE,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleScreenForLeavingPeer = (peerId: string) => {
|
||||
if (selfScreenStream) {
|
||||
peerRoom.removeStream(selfScreenStream, peerId)
|
||||
}
|
||||
|
||||
deletePeerScreen(peerId)
|
||||
}
|
||||
|
||||
peerRoom.onPeerJoin(PeerHookType.SCREEN, (peerId: string) => {
|
||||
handleScreenForNewPeer(peerId)
|
||||
})
|
||||
|
||||
peerRoom.onPeerLeave(PeerHookType.SCREEN, (peerId: string) => {
|
||||
handleScreenForLeavingPeer(peerId)
|
||||
})
|
||||
|
||||
return {
|
||||
handleScreenShareStart,
|
||||
handleScreenShareStop,
|
||||
isSharingScreen,
|
||||
setIsSharingScreen,
|
||||
}
|
||||
}
|
@ -3,9 +3,11 @@ import { useContext, useEffect, useCallback, useState } from 'react'
|
||||
import { RoomContext } from 'contexts/RoomContext'
|
||||
import { ShellContext } from 'contexts/ShellContext'
|
||||
import { PeerActions } from 'models/network'
|
||||
import { VideoState, Peer } from 'models/chat'
|
||||
import { VideoState, Peer, VideoStreamType } from 'models/chat'
|
||||
import { PeerRoom, PeerHookType, PeerStreamType } from 'services/PeerRoom'
|
||||
|
||||
import { isRecord } from 'utils'
|
||||
|
||||
import { usePeerRoomAction } from './usePeerRoomAction'
|
||||
|
||||
interface UseRoomVideoConfig {
|
||||
@ -60,7 +62,9 @@ export function useRoomVideo({ peerRoom }: UseRoomVideoConfig) {
|
||||
},
|
||||
})
|
||||
|
||||
peerRoom.addStream(newSelfStream)
|
||||
peerRoom.addStream(newSelfStream, null, {
|
||||
type: VideoStreamType.WEBCAM,
|
||||
})
|
||||
setSelfVideoStream(newSelfStream)
|
||||
|
||||
setSelfVideoStream(newSelfStream)
|
||||
@ -91,10 +95,13 @@ export function useRoomVideo({ peerRoom }: UseRoomVideoConfig) {
|
||||
setPeerList(newPeerList)
|
||||
})
|
||||
|
||||
peerRoom.onPeerStream(PeerStreamType.VIDEO, (stream, peerId) => {
|
||||
const videoTracks = stream.getVideoTracks()
|
||||
peerRoom.onPeerStream(PeerStreamType.VIDEO, (stream, peerId, metadata) => {
|
||||
const isWebcamStream =
|
||||
isRecord(metadata) &&
|
||||
'type' in metadata &&
|
||||
metadata.type === VideoStreamType.WEBCAM
|
||||
|
||||
if (videoTracks.length === 0) return
|
||||
if (!isWebcamStream) return
|
||||
|
||||
setPeerVideoStreams({
|
||||
...peerVideoStreams,
|
||||
@ -122,7 +129,9 @@ export function useRoomVideo({ peerRoom }: UseRoomVideoConfig) {
|
||||
: true,
|
||||
})
|
||||
|
||||
peerRoom.addStream(newSelfStream)
|
||||
peerRoom.addStream(newSelfStream, null, {
|
||||
type: VideoStreamType.WEBCAM,
|
||||
})
|
||||
sendVideoChange(VideoState.PLAYING)
|
||||
setVideoState(VideoState.PLAYING)
|
||||
setSelfVideoStream(newSelfStream)
|
||||
@ -191,7 +200,7 @@ export function useRoomVideo({ peerRoom }: UseRoomVideoConfig) {
|
||||
},
|
||||
})
|
||||
|
||||
peerRoom.addStream(newSelfStream)
|
||||
peerRoom.addStream(newSelfStream, null, { type: VideoStreamType.WEBCAM })
|
||||
setSelfVideoStream(newSelfStream)
|
||||
}
|
||||
|
||||
@ -203,15 +212,18 @@ export function useRoomVideo({ peerRoom }: UseRoomVideoConfig) {
|
||||
|
||||
const handleVideoForNewPeer = (peerId: string) => {
|
||||
if (selfVideoStream) {
|
||||
peerRoom.addStream(selfVideoStream, peerId)
|
||||
peerRoom.addStream(selfVideoStream, peerId, {
|
||||
type: VideoStreamType.WEBCAM,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleVideoForLeavingPeer = (peerId: string) => {
|
||||
if (selfVideoStream) {
|
||||
peerRoom.removeStream(selfVideoStream, peerId)
|
||||
deletePeerVideo(peerId)
|
||||
}
|
||||
|
||||
deletePeerVideo(peerId)
|
||||
}
|
||||
|
||||
peerRoom.onPeerJoin(PeerHookType.VIDEO, (peerId: string) => {
|
||||
|
@ -15,7 +15,7 @@ import { AlertColor } from '@mui/material/Alert'
|
||||
import { ShellContext } from 'contexts/ShellContext'
|
||||
import { SettingsContext } from 'contexts/SettingsContext'
|
||||
import { AlertOptions } from 'models/shell'
|
||||
import { AudioState, VideoState, Peer } from 'models/chat'
|
||||
import { AudioState, ScreenShareState, VideoState, Peer } from 'models/chat'
|
||||
import { ErrorBoundary } from 'components/ErrorBoundary'
|
||||
|
||||
import { Drawer } from './Drawer'
|
||||
@ -46,6 +46,9 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
||||
const [tabHasFocus, setTabHasFocus] = useState(true)
|
||||
const [audioState, setAudioState] = useState<AudioState>(AudioState.STOPPED)
|
||||
const [videoState, setVideoState] = useState<VideoState>(VideoState.STOPPED)
|
||||
const [screenState, setScreenState] = useState<ScreenShareState>(
|
||||
ScreenShareState.NOT_SHARING
|
||||
)
|
||||
|
||||
const showAlert = useCallback<
|
||||
(message: string, options?: AlertOptions) => void
|
||||
@ -72,6 +75,8 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
||||
setAudioState,
|
||||
videoState,
|
||||
setVideoState,
|
||||
screenState,
|
||||
setScreenState,
|
||||
}),
|
||||
[
|
||||
isPeerListOpen,
|
||||
@ -87,6 +92,8 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
||||
setAudioState,
|
||||
videoState,
|
||||
setVideoState,
|
||||
screenState,
|
||||
setScreenState,
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -5,6 +5,10 @@ interface RoomContextProps {
|
||||
setSelfVideoStream: Dispatch<SetStateAction<MediaStream | null>>
|
||||
peerVideoStreams: Record<string, MediaStream>
|
||||
setPeerVideoStreams: Dispatch<SetStateAction<Record<string, MediaStream>>>
|
||||
selfScreenStream: MediaStream | null
|
||||
setSelfScreenStream: Dispatch<SetStateAction<MediaStream | null>>
|
||||
peerScreenStreams: Record<string, MediaStream>
|
||||
setPeerScreenStreams: Dispatch<SetStateAction<Record<string, MediaStream>>>
|
||||
}
|
||||
|
||||
export const RoomContext = createContext<RoomContextProps>({
|
||||
@ -12,4 +16,8 @@ export const RoomContext = createContext<RoomContextProps>({
|
||||
setSelfVideoStream: () => {},
|
||||
peerVideoStreams: {},
|
||||
setPeerVideoStreams: () => {},
|
||||
selfScreenStream: null,
|
||||
setSelfScreenStream: () => {},
|
||||
peerScreenStreams: {},
|
||||
setPeerScreenStreams: () => {},
|
||||
})
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { createContext, Dispatch, SetStateAction } from 'react'
|
||||
|
||||
import { AlertOptions } from 'models/shell'
|
||||
import { AudioState, VideoState, Peer } from 'models/chat'
|
||||
import { AudioState, ScreenShareState, VideoState, Peer } from 'models/chat'
|
||||
|
||||
interface ShellContextProps {
|
||||
numberOfPeers: number
|
||||
@ -18,6 +18,8 @@ interface ShellContextProps {
|
||||
setAudioState: Dispatch<SetStateAction<AudioState>>
|
||||
videoState: VideoState
|
||||
setVideoState: Dispatch<SetStateAction<VideoState>>
|
||||
screenState: ScreenShareState
|
||||
setScreenState: Dispatch<SetStateAction<ScreenShareState>>
|
||||
}
|
||||
|
||||
export const ShellContext = createContext<ShellContextProps>({
|
||||
@ -35,4 +37,6 @@ export const ShellContext = createContext<ShellContextProps>({
|
||||
setAudioState: () => {},
|
||||
videoState: VideoState.STOPPED,
|
||||
setVideoState: () => {},
|
||||
screenState: ScreenShareState.NOT_SHARING,
|
||||
setScreenState: () => {},
|
||||
})
|
||||
|
@ -15,11 +15,22 @@ export enum VideoState {
|
||||
STOPPED = 'STOPPED',
|
||||
}
|
||||
|
||||
export enum VideoStreamType {
|
||||
WEBCAM = 'WEBCAM',
|
||||
SCREEN_SHARE = 'SCREEN_SHARE',
|
||||
}
|
||||
|
||||
export enum ScreenShareState {
|
||||
SHARING = 'SHARING',
|
||||
NOT_SHARING = 'NOT_SHARING',
|
||||
}
|
||||
|
||||
export interface Peer {
|
||||
peerId: string
|
||||
userId: string
|
||||
audioState: AudioState
|
||||
videoState: VideoState
|
||||
screenShareState: ScreenShareState
|
||||
}
|
||||
|
||||
export interface ReceivedMessage extends UnsentMessage {
|
||||
|
@ -5,4 +5,5 @@ export enum PeerActions {
|
||||
PEER_NAME = 'PEER_NAME',
|
||||
AUDIO_CHANGE = 'AUDIO_CHANGE',
|
||||
VIDEO_CHANGE = 'VIDEO_CHANGE',
|
||||
SCREEN_SHARE = 'SCREEN_SHARE',
|
||||
}
|
||||
|
@ -5,11 +5,13 @@ export enum PeerHookType {
|
||||
NEW_PEER = 'NEW_PEER',
|
||||
AUDIO = 'AUDIO',
|
||||
VIDEO = 'VIDEO',
|
||||
SCREEN = 'SCREEN',
|
||||
}
|
||||
|
||||
export enum PeerStreamType {
|
||||
AUDIO = 'AUDIO',
|
||||
VIDEO = 'VIDEO',
|
||||
SCREEN = 'SCREEN',
|
||||
}
|
||||
|
||||
export class PeerRoom {
|
||||
@ -107,8 +109,8 @@ export class PeerRoom {
|
||||
return this.room.makeAction<T>(namespace)
|
||||
}
|
||||
|
||||
addStream: Room['addStream'] = stream => {
|
||||
return this.room.addStream(stream)
|
||||
addStream: Room['addStream'] = (...args) => {
|
||||
return this.room.addStream(...args)
|
||||
}
|
||||
|
||||
removeStream: Room['removeStream'] = (stream, targetPeers) => {
|
||||
|
@ -2,3 +2,11 @@ export const sleep = (milliseconds: number): Promise<void> =>
|
||||
new Promise<void>(res => {
|
||||
setTimeout(res, milliseconds)
|
||||
})
|
||||
|
||||
export const isRecord = (variable: any): variable is Record<string, any> => {
|
||||
return (
|
||||
typeof variable === 'object' &&
|
||||
!Array.isArray(variable) &&
|
||||
variable !== null
|
||||
)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user