154 lines
4.5 KiB
JavaScript
154 lines
4.5 KiB
JavaScript
import React from 'react';
|
|
import { broadcastData, JOIN_CALL, LEAVE_CALL, EXCHANGE, ice } from './video_util.js';
|
|
|
|
class VideoCall extends React.Component{
|
|
constructor(props){
|
|
super(props);
|
|
this.pcPeers = {};
|
|
this.userId = Math.floor(Math.random() * 10000);
|
|
this.joinCall = this.joinCall.bind(this);
|
|
this.leaveCall = this.leaveCall.bind(this);
|
|
}
|
|
componentDidMount(){
|
|
this.remoteVideoContainer =
|
|
document.getElementById("remote-video-container")
|
|
navigator.mediaDevices.getUserMedia({audio: false, video: true})
|
|
.then(stream => {
|
|
this.localStream = stream;
|
|
document.getElementById("local-video").srcObject = stream;
|
|
}).catch(error => { console.log(error)});
|
|
}
|
|
join(data){ this.createPC(data.from, true) }
|
|
joinCall(e){
|
|
console.log("Hello from joinCall");
|
|
console.log(e);
|
|
App.cable.subscriptions.create(
|
|
{ channel: "CallChannel" },
|
|
{
|
|
connected: () => {
|
|
broadcastData({ type: JOIN_CALL, from: this.userId});
|
|
},
|
|
received: data => {
|
|
console.log("RECEIVED: ", data);
|
|
if (data.from === this.userId) return;
|
|
switch(data.type){
|
|
case JOIN_CALL:
|
|
return this.join(data);
|
|
case EXCHANGE:
|
|
if (data.to !== this.userId) return;
|
|
return this.exchange(data);
|
|
case LEAVE_CALL:
|
|
return this.removeUser(data);
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
createPC(userId, offerBool){
|
|
const pc = new RTCPeerConnection(ice);
|
|
this.pcPeers[userId] = pc;
|
|
this.localStream.getTracks()
|
|
.forEach(track => pc.addTrack(track, this.localStream));
|
|
if (offerBool) {
|
|
pc.createOffer().then(offer => {
|
|
pc.setLocalDescription(offer).then(() => {
|
|
setTimeout( () => {
|
|
broadcastData({
|
|
type: EXCHANGE,
|
|
from: this.userId,
|
|
to: userId,
|
|
sdp: JSON.stringify(pc.localDescription),
|
|
});
|
|
}, 0);
|
|
});
|
|
});
|
|
}
|
|
pc.onicecandidate = (e) => {
|
|
broadcastData({
|
|
type: EXCHANGE,
|
|
from: this.userId,
|
|
to: userId,
|
|
sdp: JSON.stringify(e.candidate)
|
|
})
|
|
}
|
|
pc.ontrack = (e) => {
|
|
const remoteVid = document.createElement("video");
|
|
remoteVid.id = `remoteVideoContainer+${userId}`;
|
|
remoteVid.autoplay = "autoplay";
|
|
remoteVid.srcObject = e.streams[0];
|
|
this.remoteVideoContainer.appendChild(remoteVid);
|
|
}
|
|
pc.oniceconnectionstatechange = (e) => {
|
|
if (pc.iceConnectionState === 'disconnected'){
|
|
broadcastData({ type: LEAVE_CALL, from: userId });
|
|
}
|
|
}
|
|
return pc;
|
|
}
|
|
|
|
exchange(data){
|
|
let pc;
|
|
if(this.pcPeers[data.from]){
|
|
pc = this.pcPeers[data.from];
|
|
} else{
|
|
pc = this.createPC(data.from, false);
|
|
}
|
|
if (data.candidate){
|
|
let candidate = JSON.parse(data.candidate)
|
|
pc.addIceCandidate(new RTCIceCandidate(candidate))
|
|
}
|
|
if(data.sdp){
|
|
const sdp = JSON.parse(data.sdp);
|
|
if(sdp && !sdp.candidate){
|
|
pc.setRemoteDescription(sdp).then( () =>{
|
|
if (sdp.type === 'offer'){
|
|
pc.createAnswer().then(answer => {
|
|
pc.setLocalDescription(answer)
|
|
.then( () => {
|
|
broadcastData({
|
|
type: EXCHANGE,
|
|
from: this.userId,
|
|
to: data.from,
|
|
sdp: JSON.stringify(pc.localDescription)
|
|
});
|
|
});
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
leaveCall(e){
|
|
const pcKeys = Object.keys(this.pcPeers);
|
|
for (let i = 0; i < pcKeys.length; i++) {
|
|
this.pcPeers[pcKeys[i]].close();
|
|
}
|
|
this.pcPeers = {};
|
|
this.localVideo.srcObject.getTracks()
|
|
.forEach(function (track) { track.stop(); })
|
|
|
|
this.localVideo.srcObject = null;
|
|
App.cable.subscriptions.subscriptions = [];
|
|
this.remoteVideoContainer.innerHTML = "";
|
|
broadcastData({ type: LEAVE_CALL, from: this.userId });
|
|
}
|
|
removeUser(data){
|
|
let video = document.getElementById(`remoteVideoContainer+${data.from}`);
|
|
video && video.remove();
|
|
let peers = this.pcPeers
|
|
delete peers[data.from]
|
|
}
|
|
render(){
|
|
return(
|
|
<div className="VideoCall">
|
|
<div id="remote-video-container"></div>
|
|
<video id="local-video" autoPlay></video>
|
|
<button onClick={this.joinCall}>Join Call</button>
|
|
<button onClick={this.leaveCall}>Leave Call</button>
|
|
</div>)
|
|
}
|
|
}
|
|
export default VideoCall;
|