2022-11-06 13:36:15 -06:00
|
|
|
import { useContext, useEffect, useCallback, useState } from 'react'
|
|
|
|
|
2022-11-06 20:49:48 -06:00
|
|
|
import { RoomContext } from 'contexts/RoomContext'
|
2022-11-06 13:36:15 -06:00
|
|
|
import { ShellContext } from 'contexts/ShellContext'
|
|
|
|
import { PeerActions } from 'models/network'
|
|
|
|
import { VideoState, Peer } from 'models/chat'
|
|
|
|
import { PeerRoom, PeerHookType, PeerStreamType } from 'services/PeerRoom'
|
|
|
|
|
|
|
|
import { usePeerRoomAction } from './usePeerRoomAction'
|
|
|
|
|
|
|
|
interface UseRoomVideoConfig {
|
|
|
|
peerRoom: PeerRoom
|
|
|
|
}
|
|
|
|
|
|
|
|
export function useRoomVideo({ peerRoom }: UseRoomVideoConfig) {
|
|
|
|
const shellContext = useContext(ShellContext)
|
2022-11-06 20:49:48 -06:00
|
|
|
const roomContext = useContext(RoomContext)
|
2022-11-06 13:36:15 -06:00
|
|
|
const [isCameraEnabled, setIsCameraEnabled] = useState(false)
|
|
|
|
const [videoDevices, setVideoDevices] = useState<MediaDeviceInfo[]>([])
|
|
|
|
const [selectedVideoDeviceId, setSelectedVideoDeviceId] = useState<
|
|
|
|
string | null
|
|
|
|
>(null)
|
|
|
|
|
2022-11-06 20:49:48 -06:00
|
|
|
const { peerList, setPeerList, setVideoState } = shellContext
|
|
|
|
|
2022-11-06 13:36:15 -06:00
|
|
|
const {
|
|
|
|
peerVideoStreams,
|
|
|
|
selfVideoStream,
|
|
|
|
setPeerVideoStreams,
|
|
|
|
setSelfVideoStream,
|
2022-11-06 20:49:48 -06:00
|
|
|
} = roomContext
|
2022-11-06 13:36:15 -06:00
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
;(async () => {
|
|
|
|
if (!selfVideoStream) return
|
|
|
|
|
|
|
|
const devices = await window.navigator.mediaDevices.enumerateDevices()
|
|
|
|
const rawVideoDevices = devices.filter(
|
|
|
|
({ kind }) => kind === 'videoinput'
|
|
|
|
)
|
|
|
|
|
|
|
|
// Sometimes duplicate devices are provided by enumerateDevices, so
|
|
|
|
// dedupe them by ID.
|
|
|
|
const newVideoDevices = [
|
|
|
|
...rawVideoDevices
|
|
|
|
.reduce((acc, videoDevice) => {
|
|
|
|
return acc.set(videoDevice.deviceId, videoDevice)
|
|
|
|
}, new Map<string, MediaDeviceInfo>())
|
|
|
|
.values(),
|
|
|
|
]
|
|
|
|
|
|
|
|
setVideoDevices(newVideoDevices)
|
|
|
|
|
|
|
|
if (newVideoDevices.length > 0 && !selfVideoStream) {
|
|
|
|
const [firstVideoDevice] = newVideoDevices
|
|
|
|
const newSelfStream = await navigator.mediaDevices.getUserMedia({
|
|
|
|
audio: false,
|
|
|
|
video: {
|
|
|
|
deviceId: firstVideoDevice.deviceId,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
peerRoom.addStream(newSelfStream)
|
|
|
|
setSelfVideoStream(newSelfStream)
|
|
|
|
|
|
|
|
setSelfVideoStream(newSelfStream)
|
|
|
|
}
|
|
|
|
})()
|
|
|
|
}, [peerRoom, selfVideoStream, setSelfVideoStream])
|
|
|
|
|
|
|
|
const [sendVideoChange, receiveVideoChange] = usePeerRoomAction<VideoState>(
|
|
|
|
peerRoom,
|
|
|
|
PeerActions.VIDEO_CHANGE
|
|
|
|
)
|
|
|
|
|
|
|
|
receiveVideoChange((videoState, peerId) => {
|
|
|
|
const newPeerList = peerList.map(peer => {
|
|
|
|
const newPeer: Peer = { ...peer }
|
|
|
|
|
|
|
|
if (peer.peerId === peerId) {
|
|
|
|
newPeer.videoState = videoState
|
|
|
|
|
|
|
|
if (videoState === VideoState.STOPPED) {
|
|
|
|
deletePeerVideo(peerId)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return newPeer
|
|
|
|
})
|
|
|
|
|
|
|
|
setPeerList(newPeerList)
|
|
|
|
})
|
|
|
|
|
|
|
|
peerRoom.onPeerStream(PeerStreamType.VIDEO, (stream, peerId) => {
|
|
|
|
const videoTracks = stream.getVideoTracks()
|
|
|
|
|
|
|
|
if (videoTracks.length === 0) return
|
|
|
|
|
|
|
|
setPeerVideoStreams({
|
|
|
|
...peerVideoStreams,
|
|
|
|
[peerId]: stream,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
const cleanupVideo = useCallback(() => {
|
|
|
|
if (!selfVideoStream) return
|
|
|
|
|
|
|
|
for (const videoTrack of selfVideoStream.getTracks()) {
|
|
|
|
videoTrack.stop()
|
|
|
|
selfVideoStream.removeTrack(videoTrack)
|
|
|
|
}
|
|
|
|
}, [selfVideoStream])
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
;(async () => {
|
|
|
|
if (isCameraEnabled) {
|
|
|
|
if (!selfVideoStream) {
|
|
|
|
const newSelfStream = await navigator.mediaDevices.getUserMedia({
|
|
|
|
audio: false,
|
|
|
|
video: selectedVideoDeviceId
|
|
|
|
? { deviceId: selectedVideoDeviceId }
|
|
|
|
: true,
|
|
|
|
})
|
|
|
|
|
|
|
|
peerRoom.addStream(newSelfStream)
|
|
|
|
sendVideoChange(VideoState.PLAYING)
|
|
|
|
setVideoState(VideoState.PLAYING)
|
|
|
|
setSelfVideoStream(newSelfStream)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (selfVideoStream) {
|
|
|
|
cleanupVideo()
|
|
|
|
|
|
|
|
peerRoom.removeStream(selfVideoStream, peerRoom.getPeers())
|
|
|
|
sendVideoChange(VideoState.STOPPED)
|
|
|
|
setVideoState(VideoState.STOPPED)
|
|
|
|
setSelfVideoStream(null)
|
|
|
|
setSelfVideoStream(null)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})()
|
|
|
|
}, [
|
|
|
|
isCameraEnabled,
|
|
|
|
peerRoom,
|
|
|
|
selfVideoStream,
|
|
|
|
selectedVideoDeviceId,
|
|
|
|
sendVideoChange,
|
|
|
|
cleanupVideo,
|
|
|
|
setSelfVideoStream,
|
|
|
|
setVideoState,
|
|
|
|
])
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
return () => {
|
|
|
|
cleanupVideo()
|
|
|
|
}
|
|
|
|
}, [cleanupVideo])
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
return () => {
|
|
|
|
if (selfVideoStream) {
|
|
|
|
setSelfVideoStream(null)
|
|
|
|
setVideoState(VideoState.STOPPED)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, [selfVideoStream, setSelfVideoStream, setVideoState])
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
return () => {
|
|
|
|
setPeerVideoStreams({})
|
|
|
|
}
|
|
|
|
}, [setPeerVideoStreams])
|
|
|
|
|
|
|
|
const handleVideoDeviceSelect = async (videoDevice: MediaDeviceInfo) => {
|
|
|
|
const { deviceId } = videoDevice
|
|
|
|
setSelectedVideoDeviceId(deviceId)
|
|
|
|
|
|
|
|
if (!selfVideoStream) return
|
|
|
|
|
|
|
|
for (const videoTrack of selfVideoStream.getTracks()) {
|
|
|
|
videoTrack.stop()
|
|
|
|
selfVideoStream.removeTrack(videoTrack)
|
|
|
|
}
|
|
|
|
|
|
|
|
peerRoom.removeStream(selfVideoStream, peerRoom.getPeers())
|
|
|
|
|
|
|
|
const newSelfStream = await navigator.mediaDevices.getUserMedia({
|
|
|
|
audio: false,
|
|
|
|
video: {
|
|
|
|
deviceId,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
peerRoom.addStream(newSelfStream)
|
|
|
|
setSelfVideoStream(newSelfStream)
|
|
|
|
}
|
|
|
|
|
|
|
|
const deletePeerVideo = (peerId: string) => {
|
|
|
|
const newPeerVideos = { ...peerVideoStreams }
|
|
|
|
delete newPeerVideos[peerId]
|
|
|
|
setPeerVideoStreams(newPeerVideos)
|
|
|
|
}
|
|
|
|
|
|
|
|
const handleVideoForNewPeer = (peerId: string) => {
|
|
|
|
if (selfVideoStream) {
|
|
|
|
peerRoom.addStream(selfVideoStream, peerId)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const handleVideoForLeavingPeer = (peerId: string) => {
|
|
|
|
if (selfVideoStream) {
|
|
|
|
peerRoom.removeStream(selfVideoStream, peerId)
|
|
|
|
deletePeerVideo(peerId)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
peerRoom.onPeerJoin(PeerHookType.VIDEO, (peerId: string) => {
|
|
|
|
handleVideoForNewPeer(peerId)
|
|
|
|
})
|
|
|
|
|
|
|
|
peerRoom.onPeerLeave(PeerHookType.VIDEO, (peerId: string) => {
|
|
|
|
handleVideoForLeavingPeer(peerId)
|
|
|
|
})
|
|
|
|
|
|
|
|
return {
|
|
|
|
videoDevices,
|
|
|
|
isCameraEnabled,
|
|
|
|
setIsCameraEnabled,
|
|
|
|
handleVideoDeviceSelect,
|
|
|
|
}
|
|
|
|
}
|