I have tried to connect my video (No webcam) to sync with the other video remotely. But I don't know where I'm wrong. I did it taking into account that they were going to connect to the same room. One performs the stream (LocalVideo) and the other sees what the other does (Remote). Something very similar to this: https://webrtc.github.io/samples/src/content/capture/video-pc/. But I still haven't given an answer for the problem, PLEASE if someone who knows about node.js and WebRTC can rescue me!
main.js:
let divSelectRoom = document.getElementById("selectRoom")
let divConsultingRoom = document.getElementById("consultingRoom")
let divConsultingRoom2 = document.getElementById("consultingRoom2")
let inputRoomNumber = document.getElementById("roomNumber")
let btnGoRoom = document.getElementById("goRoom")
let localVideo = document.getElementById("localVideo")
let remoteVideo = document.getElementById("remoteVideo")
let locallabel = document.getElementById("locallabel")
let visitlabel = document.getElementById("visitlabel")
let roomNumber, localStream, remoteStream, rtcPeerConnection, isCaller
let stream;
const iceServers = {
'iceServer': [
{ 'urls': 'stun:stun.services.mozilla.com' },
{ 'urls': 'stun:stun.l.google.com:19302' }
]
}
const streamConstraints = {
audio: 1,
video: 1
}
let startTime;
const socket = io();
btnGoRoom.onclick = () => {
if (inputRoomNumber.value == '') {
alert("please type a room name")
} else {
roomNumber = inputRoomNumber.value
socket.emit('create or join', roomNumber)
divSelectRoom.style = "display: none"
}
}
function maybeCreateStream() {
if (stream) {
return;
}
if (localVideo.captureStream) {
stream = localVideo.captureStream();
console.log('Captured stream from localVideo with captureStream',
stream);
} else if (localVideo.mozCaptureStream) {
stream = localVideo.mozCaptureStream();
console.log('Captured stream from localVideo with mozCaptureStream()',
stream);
} else {
console.log('captureStream() not supported');
}
localStream = stream
}
localVideo.play();
socket.on('created', room => {
divConsultingRoom.style = "display: block"
// Video tag capture must be set up after video tracks are enumerated.
localVideo.oncanplay = maybeCreateStream;
if (localVideo.readyState >= 3) { // HAVE_FUTURE_DATA
// Video is already ready to play, call maybeCreateStream in case oncanplay
// fired before we registered the event handler.
maybeCreateStream();
}
isCaller = true
})
socket.on('joined', room => {
divConsultingRoom2.style = "display: block"
remoteVideo.srcObject = localStream;
isCaller = false
socket.emit('ready', roomNumber)
})
socket.on('ready', () => {
console.log('enter ready')
const videoTracks = stream.getVideoTracks();
const audioTracks = stream.getAudioTracks();
if (videoTracks.length > 0) {
console.log(`Using video device: ${videoTracks[0].label}`);
}
if (audioTracks.length > 0) {
console.log(`Using audio device: ${audioTracks[0].label}`);
}
if (isCaller) {
rtcPeerConnection = new RTCPeerConnection(iceServers)
rtcPeerConnection.onicecandidate = onIceCandidate
rtcPeerConnection.ontrack = onAddStream
rtcPeerConnection.addTrack(localStream.getTracks()[0], localStream)
rtcPeerConnection.addTrack(localStream.getTracks()[1], localStream)
rtcPeerConnection.createOffer()
.then(sessionDescription => {
console.log('sending offer', sessionDescription)
rtcPeerConnection.setLocalDescription(sessionDescription)
socket.emit('offer', {
type: 'offer',
sdp: sessionDescription,
room: roomNumber
})
}).catch(err => {
console.log(err)
})
}
})
socket.on('offer', (event) => {
console.log('enter offer', isCaller)
if (!isCaller) {
rtcPeerConnection = new RTCPeerConnection(iceServers)
rtcPeerConnection.onicecandidate = onIceCandidate
rtcPeerConnection.ontrack = onAddStream
//rtcPeerConnection.addTrack(localStream.getTracks()[0], localStream)
//rtcPeerConnection.addTrack(localStream.getTracks()[1], localStream)
console.log('received offer', event)
rtcPeerConnection.setRemoteDescription(new RTCSessionDescription(event))
rtcPeerConnection.createAnswer().then(sessionDescription => {
console.log('sending answer', sessionDescription)
socket.emit('answer', {
type: 'answer',
sdp: sessionDescription,
room: roomNumber
})
}).catch(err => {
console.log(err)
})
}
})
socket.on('answer', event => {
console.log('received answer', event)
rtcPeerConnection.setRemoteDescription(new RTCSessionDescription(event))
})
socket.on('candidate', event => {
const candidate = new RTCIceCandidate({
sdpMLineIndex: event.label,
candidate: event.candidate
})
console.log('received candidate', candidate)
rtcPeerConnection.addIceCandidate(candidate)
})
function onAddStream(event) {
console.log('Stream It')
remoteVideo.srcObject = event.streams[0];
remoteStream = event.streams[0]
console.log('visit received remote stream', event.streams[0]);
}
function onIceCandidate(event) {
if (event.candidate) {
console.log('sending ice candidate', event.candidate)
socket.emit('candidate', {
type: 'candidate',
label: event.candidate.sdpMLineIndex,
id: event.candidate.sdpMid,
candidate: event.candidate.candidate,
room: roomNumber
})
}
}
app.js:
const { Socket } = require('dgram')
const express = require('express')
const app = express()
let http = require('http').Server(app)
const port = process.env.PORT || 3000
let io = require('socket.io')(http)
app.use(express.static('public'))
http.listen(port, () => {
console.log('listening on', port)
})
io.on('connection', socket => {
console.log('a user connected')
socket.on('create or join', room => {
console.log('create or join to room', room)
const myRoom = io.sockets.adapter.rooms[room] || {length: 0}
const numClients = myRoom.length
console.log(room, 'has',numClients,'clients')
if(numClients == 0){
socket.join(room)
socket.emit('created', room)
}else if (numClients == 1){
socket.join(room)
socket.emit('joined', room)
}else{
socket.emit('full', room)
}
})
socket.on('ready', room => {
socket.broadcast.to(room).emit('ready')
})
socket.on('candidate', event => {
socket.broadcast.to(event.room).emit('candidate', event)
})
socket.on('offer', event => {
socket.broadcast.to(event.room).emit('offer', event.sdp)
})
socket.on('answer', event => {
socket.broadcast.to(event.room).emit('answer', event.sdp)
})
})
index.html:
<!DOCTYPE html>
<head>
<title>WebRTC Training</title>
</head>
<body>
<h1>WebRTC TRAINING</h1>
<div id="selectRoom">
<label>Type room name</label>
<input id="roomNumber" type="text"/>
<button id="goRoom">Go</button>
</div>
<div id="consultingRoom" style="display: none;">
<video id="localVideo" playsinline controls loop muted>
<source src="videos/dudu.webm" type="video/webm"/>
<source src="videos/dudu.mp4" type="video/mp4"/>
<p>This browser does not support the video element.</p>
</video>
<label id="locallabel">local</label>
</div>
<div id="consultingRoom2" style="display: none;">
<video id="remoteVideo" playsinline autoplay></video>
<label id="visitlabel">visit</label>
</div>
<script src="/socket.io/socket.io.js"></script>
<script src="main.js"></script>
</body>
This is a picture so you can understand it better.
Related
I have a very simple code for video calling using WebRTC. The entire system working different for different browsers.
Capture Browser
Player Browser
Working
Chrome
Firefox
✔
Chrome
Chrome
X
Firefox
Chrome
X
Firefox
Firefox
✔
The capture code is
JS:
(function () {
var localVideo, localConnection;
const signaling = new WebSocket('wss://crs4kx11s1/websockets');
signaling.onmessage = function (message) {
var data = JSON.parse(message.data);
if (data.sdp) {
var answerSDP = data.sdp;
if (answerSDP.type == "answer") {
localConnection.setRemoteDescription(answerSDP);
}
}
if (data.candidate && data.candidateType == "answerClient") {
localConnection.addIceCandidate(data.candidate);
}
}
localConnection = new RTCPeerConnection({
iceServers: [{
urls: 'turn:127.0.0.1:8043?transport=tcp',
credential: 'jupiter',
username: 'simpleshare'
}]
});
document.addEventListener("DOMContentLoaded", function (event) {
$("#share").click(function (event) {
navigator.mediaDevices.getUserMedia({ video: true })
.then(function (stream) {
stream.getTracks().forEach(
function (track) {
localConnection.addTrack(
track,
stream
);
}
);
localVideo = document.getElementById('local');
localVideo.srcObject = stream;
localConnection.onnegotiationneeded = function () {
localConnection.createOffer()
.then(offer => {
localConnection.setLocalDescription(offer)
.then(() => {
signaling.send(JSON.stringify({ sdp: offer }));
})
});
}
localConnection.onicecandidate = function (e) {
if (e.candidate) {
signaling.send(JSON.stringify({
candidateType: 'offerClient',
candidate: e.candidate.toJSON()
}));
}
console.log('offerClient is on icecandidate');
};
});
});
});
})();
HTML
<div>
<button id="share">Share</button>
<video id="local" autoplay></video>
</div>
Now the player code
JS
(function () {
var localVideo, localConnection;
const signaling = new WebSocket('wss://crs4kx11s1/websockets');
signaling.onmessage = function (message) {
const data = JSON.parse(message.data);
// const content = data.content;
try {
if (data.sdp) {
let offerSDP = data.sdp;
if (offerSDP.type == "offer") {
console.log("Accepting the offer.")
localConnection.setRemoteDescription(offerSDP);
localConnection.createAnswer().then(function (answer) {
console.log("Answer created!")
localConnection.setLocalDescription(answer);
signaling.send(JSON.stringify({ sdp: answer }));
});
}
}
if (data.candidate && data.candidateType == "offerClient") {
console.log("ICE candidate added!");
localConnection.addIceCandidate(data.candidate);
}
} catch (err) {
console.error(err);
}
};
document.addEventListener("DOMContentLoaded", function (event) {
startConnection();
localVideo = document.getElementById('self-view');
});
function startConnection() {
console.info("Starting connection");
localConnection = new RTCPeerConnection({iceServers: [{
urls: 'turn:127.0.0.1:8043?transport=tcp',
credential: 'jupiter',
username: 'simpleshare'
}]
});
//startCapture();
localConnection.onicecandidate = function (e) {
console.info("onicecandidate", e);
if (e.candidate) {
signaling.send(JSON.stringify({
candidateType: 'answerClient',
candidate: e.candidate.toJSON()
}));
}
console.log('answerClient is on icecandidate');
};
localConnection.onconnectionstatechange = function (e) {
console.log("Current state", localConnection.connectionState);
}
localConnection.ontrack = function (e) {
localVideo.srcObject = e.streams[0];
}
}
})();
HTML
<div id="chat-room">
<div id="videos">
<video id="self-view" autoplay></video>
</div>
</div>
Apart from these there is a WebSocket server which relays the SDP offers and candidates.
Please note that I have used our own TURN server for.
Got it worked. It was because of new autoplay policy in chrome. Just added localVideo.play(); and it worked.
I am trying to create a video conference app using the tutorial https://youtu.be/DvlyzDZDEq4 . I want to add one feature to change the quality of video( not the resolution of the video ) user can see like we have in YouTube videos auto, high picture quality etc. I am very new to WebRTC and Peer library so any help is appreciated. Thank you.
script.js
const socket = io('/')
const videoGrid = document.getElementById('video-grid')
const myPeer = new Peer(undefined, {
host: '/',
port: '3001'
})
const myVideo = document.createElement('video')
myVideo.muted = true
const peers = {}
navigator.mediaDevices.getUserMedia({
video: true,
audio: true
}).then(stream => {
addVideoStream(myVideo, stream)
myPeer.on('call', call => {
call.answer(stream)
const video = document.createElement('video')
call.on('stream', userVideoStream => {
addVideoStream(video, userVideoStream)
})
})
socket.on('user-connected', userId => {
connectToNewUser(userId, stream)
})
})
socket.on('user-disconnected', userId => {
if (peers[userId]) peers[userId].close()
})
myPeer.on('open', id => {
socket.emit('join-room', ROOM_ID, id)
})
function connectToNewUser(userId, stream) {
const call = myPeer.call(userId, stream)
const video = document.createElement('video')
call.on('stream', userVideoStream => {
addVideoStream(video, userVideoStream)
})
call.on('close', () => {
video.remove()
})
peers[userId] = call
}
function addVideoStream(video, stream) {
video.srcObject = stream
video.addEventListener('loadedmetadata', () => {
video.play()
})
videoGrid.append(video)
}
room.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script>
const ROOM_ID = "<%= roomId %>"
</script>
<script defer src="https://unpkg.com/peerjs#1.2.0/dist/peerjs.min.js"></script>
<script src="/socket.io/socket.io.js" defer></script>
<script src="script.js" defer></script>
</head>
<body>
<div id="video-grid"></div>
</body>
</html>
server.js
const express = require('express')
const app = express()
const server = require('http').Server(app)
const io = require('socket.io')(server)
const { v4: uuidV4 } = require('uuid')
app.set('view engine', 'ejs')
app.use(express.static('public'))
app.get('/', (req, res) => {
res.redirect(`/${uuidV4()}`)
})
app.get('/:room', (req, res) => {
res.render('room', { roomId: req.params.room })
})
io.on('connection', socket => {
socket.on('join-room', (roomId, userId) => {
socket.join(roomId)
socket.to(roomId).broadcast.emit('user-connected', userId)
socket.on('disconnect', () => {
socket.to(roomId).broadcast.emit('user-disconnected', userId)
})
})
})
server.listen(3000)
Updated Script.js
const socket = io('/zoom')
const videoGrid = document.getElementById('video-grid')
const myPeer = new Peer(undefined, {
host: '/',
port: '3002'
})
let pc;
const myVideo = document.createElement('video')
myVideo.muted = true
const peers = {}
let myVideoStream;
navigator.mediaDevices.getUserMedia({
video: true,
audio: true
}).then(stream => {
myVideoStream = stream;
addVideoStream(myVideo, stream,user)
myPeer.on('call', call => {
call.answer(stream)
const video = document.createElement('video')
call.on('stream', userVideoStream => {
addVideoStream(video, userVideoStream,user)
})
})
socket.on("user-connected", (userId, username) =>
{ console.log("checking "+ username);
setTimeout(function ()
{ connectToNewUser(userId, stream);
console.log("zoooom "+ username);
}, 1000);
});
let text = $("input");
// when press enter send message
$('html').keydown(function (e) {
if (e.which == 13 && text.val().length !== 0) {
socket.emit('message', text.val());
text.val('')
}
});
socket.on("createMessage", (message, username) => {
//$("ul").append(`<li class="message"><b>${username}</b><br/>${message}</li>`);
scrollToBottom()
})
})
socket.on('user-disconnected', (userId, username) => {
$("ul").append(`<span class="messageHeader"><small>${username} Left Meeting</small></span>`);
if (peers[userId]) peers[userId].close()
})
const offerOptions = {
offerToReceiveAudio: 0,
offerToReceiveVideo: 1
};
myPeer.on('open', id => {
const servers = null;
pc1 = new RTCPeerConnection(servers);
pc1.createOffer({
offerToReceiveAudio: 0,
offerToReceiveVideo: 1
})
.then((desc)=>{const videobitrate = 20000;
return pc1.setLocalDescription(desc);
/* let offer = pc1.localDescription;
console.log("offer "+pc1.localDescription);
// Set bandwidth for video
offer.sdp = offer.sdp.replace(/(m=video.*\r\n)/g, `$1b=AS:125\r\n`);
pc1.setLocalDescription(offer);*/}).then(()=>{
let offer = pc1.localDescription;
console.log("offer "+pc1.localDescription);
// Set bandwidth for video
offer.sdp = offer.sdp.replace(/(m=video.*\r\n)/g, `$1b=AS:20000\r\n`);
return pc1.setLocalDescription(offer);
}).then(()=>{console.log(pc1.localDescription.sdp)})
.catch((err)=>{ console.log('Failed to set session description: ' + err);});
console.log("user mypeer"+user);
socket.emit('join-room', ROOM_ID,id,user);
})
function connectToNewUser(userId, stream) {
const call = myPeer.call(userId, stream)
const servers = null;
pc2 = new RTCPeerConnection(servers);
pc2.createOffer({
offerToReceiveAudio: 0,
offerToReceiveVideo: 1
})
.then((desc)=>{const videobitrate = 20000;
return pc2.setLocalDescription(desc);
/* let offer = pc1.localDescription;
console.log("offer "+pc1.localDescription);
// Set bandwidth for video
offer.sdp = offer.sdp.replace(/(m=video.*\r\n)/g, `$1b=AS:125\r\n`);
pc1.setLocalDescription(offer);*/}).then(()=>{
let offer = pc2.localDescription;
console.log("offer "+pc2.localDescription);
// Set bandwidth for video
offer.sdp = offer.sdp.replace(/(m=video.*\r\n)/g, `$1b=AS:20000\r\n`);
return pc2.setLocalDescription(offer);
}).then(()=>{console.log(pc2.localDescription.sdp)})
.catch((err)=>{ console.log('Failed to set session description: ' + err);});
const video = document.createElement('video')
call.on('stream', userVideoStream => {
addVideoStream(video, userVideoStream,user)
})
call.on('close', () => {
video.remove()
})
peers[userId] = call
}
function addVideoStream(video, stream,username) {
video.srcObject = stream
console.log("adding video")
video.addEventListener('loadedmetadata', () => {
video.play()
})
videoGrid.append(video)
video.insertAdjacentHTML('beforebegin',`<b style="color:white">${username}</b>`);
}
const scrollToBottom = () => {
var d = $('.main__chat_window');
d.scrollTop(d.prop("scrollHeight"));
}
const muteUnmute = () => {
const enabled = myVideoStream.getAudioTracks()[0].enabled;
if (enabled) {
myVideoStream.getAudioTracks()[0].enabled = false;
setUnmuteButton();
} else {
setMuteButton();
myVideoStream.getAudioTracks()[0].enabled = true;
}
}
const playStop = () => {
console.log('object')
let enabled = myVideoStream.getVideoTracks()[0].enabled;
if (enabled) {
myVideoStream.getVideoTracks()[0].enabled = false;
setPlayVideo()
} else {
setStopVideo()
myVideoStream.getVideoTracks()[0].enabled = true;
}
}
const setMuteButton = () => {
const html = `
<i class="fas fa-microphone"></i>
<span>Mute</span>
`
document.querySelector('.main__mute_button').innerHTML = html;
}
const setUnmuteButton = () => {
const html = `
<i class="unmute fas fa-microphone-slash"></i>
<span>Unmute</span>
`
document.querySelector('.main__mute_button').innerHTML = html;
}
const setStopVideo = () => {
const html = `
<i class="fas fa-video"></i>
<span>Stop Video</span>
`
document.querySelector('.main__video_button').innerHTML = html;
}
const setPlayVideo = () => {
const html = `
<i class="stop fas fa-video-slash"></i>
<span>Play Video</span>
`
document.querySelector('.main__video_button').innerHTML = html;
}
I am trying to implement the zoom clone with WebRTC using peerjs, the problem is myPeer.on("call", (call) => { its never calling, this same code is executing on the locahost for the others who made the tutorial on zoom clone, I am not sure where is the problem with my code,
Its working on heroku though its not working on localhost, so where is the problem with my localhost setup, why i am not getting the other users who joined the room on localhost, I am not understanding.
script.js
const socket = io("/");
const videoGrid = document.getElementById("video-grid");
const myPeer = new Peer(undefined, {
path: "/peerjs",
host: "/",
port: "3030",
});
let myVideoStream;
const myVideo = document.createElement("video");
myVideo.muted = true;
const peers = {};
navigator.mediaDevices
.getUserMedia({
video: true,
audio: true,
})
.then((stream) => {
myVideoStream = stream;
addVideoStream(myVideo, stream);
// Never Called this myPeer.on call
myPeer.on("call", (call) => {
call.answer(stream);
const video = document.createElement("video");
call.on("stream", (userVideoStream) => {
addVideoStream(video, userVideoStream);
});
});
socket.on("user-connected", (userId) => {
console.log("User Connected", userId);
connectToNewUser(userId, stream);
});
// input value
let text = $("input");
// when press enter send message
$("html").keydown(function (e) {
if (e.which == 13 && text.val().length !== 0) {
socket.emit("message", text.val());
text.val("");
}
});
socket.on("createMessage", (message) => {
$("ul").append(`<li class="message"><b>user</b><br/>${message}</li>`);
scrollToBottom();
});
});
socket.on("user-disconnected", (userId) => {
if (peers[userId]) peers[userId].close();
});
myPeer.on("open", (id) => {
socket.emit("join-room", ROOM_ID, id);
});
function connectToNewUser(userId, stream) {
const call = myPeer.call(userId, stream);
const video = document.createElement("video");
call.on("stream", (userVideoStream) => {
addVideoStream(video, userVideoStream);
});
call.on("close", () => {
video.remove();
});
peers[userId] = call;
}
function addVideoStream(video, stream) {
video.srcObject = stream;
video.addEventListener("loadedmetadata", () => {
video.play();
});
videoGrid.append(video);
}
const scrollToBottom = () => {
var d = $(".main__chat_window");
d.scrollTop(d.prop("scrollHeight"));
};
const muteUnmute = () => {
const enabled = myVideoStream.getAudioTracks()[0].enabled;
if (enabled) {
myVideoStream.getAudioTracks()[0].enabled = false;
setUnmuteButton();
} else {
setMuteButton();
myVideoStream.getAudioTracks()[0].enabled = true;
}
};
const playStop = () => {
console.log("object");
let enabled = myVideoStream.getVideoTracks()[0].enabled;
if (enabled) {
myVideoStream.getVideoTracks()[0].enabled = false;
setPlayVideo();
} else {
setStopVideo();
myVideoStream.getVideoTracks()[0].enabled = true;
}
};
const setMuteButton = () => {
const html = `
<i class="fas fa-microphone"></i>
<span>Mute</span>
`;
document.querySelector(".main__mute_button").innerHTML = html;
};
const setUnmuteButton = () => {
const html = `
<i class="unmute fas fa-microphone-slash"></i>
<span>Unmute</span>
`;
document.querySelector(".main__mute_button").innerHTML = html;
};
const setStopVideo = () => {
const html = `
<i class="fas fa-video"></i>
<span>Stop Video</span>
`;
document.querySelector(".main__video_button").innerHTML = html;
};
const setPlayVideo = () => {
const html = `
<i class="stop fas fa-video-slash"></i>
<span>Play Video</span>
`;
document.querySelector(".main__video_button").innerHTML = html;
};
server.js
const express = require("express");
const app = express();
// const cors = require('cors')
// app.use(cors())
const server = require("http").Server(app);
const io = require("socket.io")(server);
const { ExpressPeerServer } = require("peer");
const peerServer = ExpressPeerServer(server, {
debug: true,
});
const { v4: uuidV4 } = require("uuid");
app.use("/peerjs", peerServer);
app.set("view engine", "ejs");
app.use(express.static("public"));
app.get("/", (req, res) => {
res.redirect(`/${uuidV4()}`);
});
app.get("/:room", (req, res) => {
res.render("room", { roomId: req.params.room });
});
io.on("connection", (socket) => {
socket.on("join-room", (roomId, userId) => {
socket.join(roomId);
socket.to(roomId).broadcast.emit("user-connected", userId);
// messages
socket.on("message", (message) => {
//send message to the same room
io.to(roomId).emit("createMessage", message);
});
socket.on("disconnect", () => {
socket.to(roomId).broadcast.emit("user-disconnected", userId);
});
});
});
server.listen(process.env.PORT || 3030);
the function connectToNewUser get executed before the myPeer.on('call')
try modifying the function as
socket.on("user-connected", (userId) => {
console.log("User Connected", userId);
//connectToNewUser(userId, stream);
setTimeout(connectToNewUser,1000,userId,stream)
});
I had the same issue and it seemed to be a race condition with socket.emit("join-room", ROOM_ID, id);.
The server will emit socket.to(roomId).broadcast.emit("user-connected", userId); which will trigger the function that calls the peer in connectToNewUser before the user has finished the navigator promise.
I solved my issue by making sure the navigator promise has finished before firing socket.emit("join-room", ROOM_ID, id);.
Im not sure what is wrong. The caller and callee both receive the same stream that is getting set in local video but it wont set the remote video.
I have seen it worked before when I clicked the call button twice but I changed the code and dont know how to recreate it.
Any help is appreciated. Thank you.
video.js
//Starts by calling the user
const callUser = (socketId, mySocket) => {
navigator.getUserMedia({ video: true, audio: true }, stream => {
console.log("My Stream: ", stream)
const localVideo = document.getElementById("local-video");
localVideo.srcObject = stream;
localStream = stream
createAndSendOffer(socketId, mySocket);
})
}
//Create Offer
const createAndSendOffer = async (socketId, mySocket) => {
peerConnection = new RTCPeerConnection()
peerConnection.ontrack = function(event) {
console.log("Remote Stream", event.streams[0])
const remoteVideo = document.getElementById("remoteVideo")
if(remoteVideo.srcObject !== event.stream[0]) {
remoteVideo.srcObject = event.streams[0];
}
}
localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream))
await peerConnection.createOffer( offer => {
peerConnection.setLocalDescription(new RTCSessionDescription(offer),
function() {
mySocket.emit("callUser", {offer,to: socketId});
})
})
}
//Callee Is getting called
const answerCall = (data, mySocket, inCall) => {
navigator.getUserMedia({ video: true, audio: true }, stream => {
console.log("My Stream: ", stream)
const localVideo = document.getElementById("local-video");
localVideo.srcObject = stream;
localStream = stream
createAndSendAnswer(data, mySocket, inCall);
})
}
//Create Answer
const createAndSendAnswer = async (data, mySocket, inCall) => {
peerConnection = new RTCPeerConnection()
peerConnection.ontrack = function(event) {
console.log("Remote Stream", event.streams[0])
const remoteVideo = document.getElementById("remoteVideo")
if(remoteVideo.srcObject !== event.stream[0]) {
console.log(event.streams[0])
remoteVideo.srcObject = event.streams[0];
}
inCall(true)
}
localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream))
await peerConnection.setRemoteDescription(new RTCSessionDescription(data.offer))
await peerConnection.createAnswer(answer => {
peerConnection.setLocalDescription(new RTCSessionDescription(answer),
function() {
mySocket.emit("makeAnswer", {answer, to: data.socket});
})
})
}
const startSockets = (socket, inCall) => {
socket = socket
socket.on("gettingCalled", data => {
console.log("You are getting called with socketId", data.socket)
answerCall(data, socket, inCall);
})
socket.on("answerMade", async data => {
await peerConnection.setRemoteDescription(new RTCSessionDescription(data.answer))
})
}
class Video extends Component {
constructor(props){
super(props);
this.state={
friends: [],
mySocket: [],
inCall: false
}
}
componentDidMount() {
if(this.props.user) {
startSockets(this.props.user.socket, this.inCallChange)
}
}
inCallChange = boolean => this.setState({inCall: boolean})
callUser = socketId => callUser(socketId, this.state.mySocket, this.state.inCall)
render() {
if(!this.props.user) {
return (<div>No User</div>)
} else {
return (
<div>`enter code here`
<div className="video-chat-container">
<div className="video-container">
<video
autoPlay
className="remote-video"
id="remoteVideo"
></video>
<video
autoPlay
muted
className="local-video"
id="local-video"
></video>
</div>
</div>
</div>
);
}
}
}
export default withRouter(connect(mapStateToProps)(Video))
Update
Im not sure why this is the case but I have to send just the offer and receive the answer again. For now, I resend an offer createAndSendOffer(), starting the process again. I dont think this is the best practice and if you find a better solution that would be great :)
first check your pc.ontrack() firing or not. pc.ontrack() event is fired after pc.addTrack() and remotedescription is set.and also make sure tracks are added before creating answer/offer
I'm new to webrtc and react. I'm developing a peer to peer video chat app. On the calling side, both remote video and local video shows up. But on the callee side, only local video shows up. I've been trying to find out where I'm doing wrong but not able to figure it out. One thing I noticed when I console.log peerconnection variable inside handle ice candidate function is, the 'connectionState' is still 'connecting' on the callee side.('connected' on the caller side).
EDIT: I have modified code for readability and using async await. Now I am getting "Failed to execute 'addIceCandidate' on 'RTCPeerConnection': Error processing ICE candidate" error.
EDIT 2: Modified code according to answer
Here is my ORIGINAL code
//refs for my video tag
const localVideoRef = useRef("");
const remoteVideoRef = useRef("");
//video elements
<video ref={remoteVideoRef} playsInline autoPlay className="remoteVideo"></video>
<video ref={localVideoRef} playsInline autoPlay muted className="localVideo"></video>
//button to start call
<button onClick={handleCall}>Call</button>
const handleCall = async () => {
createPeerConnection();
navigator.mediaDevices
.getUserMedia({
audio: true,
video: true
})
.then(function(localStream) {
let videoObj = localVideoRef.current;
videoObj.srcObject = localStream;
localStream
.getTracks()
.forEach(track => myPeerConnection.addTrack(track, localStream));
})
.catch("getUserMedia() error: ");
};
let myPeerConnection = null;
function createPeerConnection() {
myPeerConnection = new RTCPeerConnection({
iceServers: [
{
urls: "stun:stun2.1.google.com:19302"
}
]
});
myPeerConnection.onnegotiationneeded = handleNegotiationNeededEvent;
myPeerConnection.onicecandidate = handleICECandidateEvent;
myPeerConnection.ontrack = handleTrackEvent;
myPeerConnection.onsignalingstatechange = handleSignalingStateChangeEvent;
}
var isNegotiating = false;
const handleSignalingStateChangeEvent = () => {
isNegotiating = myPeerConnection.signalingState != "stable";
};
function handleNegotiationNeededEvent() {
if (isNegotiating) {
return;
}
isNegotiating = true;
myPeerConnection
.createOffer()
.then(function(offer) {
return myPeerConnection.setLocalDescription(offer);
})
.then(function() {
socket.emit("video-offer", {
from: authContext.user.name,
to: connectedTo,
sdp: myPeerConnection.localDescription
});
});
}
//checking if socket is initialized
if (socket) {
socket.on("gotOffer", data => {
handleVideoOfferMsg(data);
});
socket.on("gotCandidate", data => {
handleNewICECandidateMsg(data);
});
socket.on("gotAnswer", data => {
console.log("inside got answer");
handleGotAnswer(data);
});
}
function handleVideoOfferMsg(msg) {
createPeerConnection();
var desc = new RTCSessionDescription(msg.sdp);
myPeerConnection
.setRemoteDescription(desc)
.then(function() {
return navigator.mediaDevices.getUserMedia({
audio: true,
video: true
});
})
.then(function(stream) {
let localStream = stream;
let videoObj = localVideoRef.current;
videoObj.srcObject = stream;
localStream
.getTracks()
.forEach(track => myPeerConnection.addTrack(track, localStream));
})
.then(function() {
return myPeerConnection.createAnswer();
})
.then(function(answer) {
return myPeerConnection.setLocalDescription(answer);
})
.then(function() {
socket.emit("video-answer", {
from: authContext.user.name,
to: connectedTo,
sdp: myPeerConnection.localDescription
});
})
.catch("error");
}
async function handleGotAnswer(msg) {
if (!myPeerConnection) return;
// if (isNegotiating) return;
//I don't know why it's not working (no remote video on the caller side too) when I add above line. So, I am checking signaling state in the below line
if (myPeerConnection.signalingState == "stable") return;
await myPeerConnection.setRemoteDescription(
new RTCSessionDescription(msg.sdp)
);
}
function handleICECandidateEvent(event) {
if (!myPeerConnection) return;
if (isNegotiating) return;
if (event.candidate) {
socket.emit("candidate", {
to: connectedTo,
from: authContext.user.name,
candidate: event.candidate
});
}
}
function handleNewICECandidateMsg(msg) {
if (myPeerConnection.signalingState == "stable") return;
var candidate = new RTCIceCandidate(msg.candidate);
myPeerConnection.addIceCandidate(candidate).catch("error");
}
function handleTrackEvent(event) {
let videoObj = remoteVideoRef.current;
videoObj.srcObject = event.streams[0];
}
Here is my NEW code:
let pc1 = new RTCPeerConnection({
iceServers: [
{
urls: "stun:stun2.1.google.com:19302"
}
]
});
let pc2 = new RTCPeerConnection({
iceServers: [
{
urls: "stun:stun2.1.google.com:19302"
}
]
});
const handleCall = async () => {
let stream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: true
});
let videoObj = localVideoRef.current;
videoObj.srcObject = stream;
let localStream = stream;
stream
.getTracks()
.forEach(async track => await pc1.addTrack(track, localStream));
pc1.onnegotiationneeded = async function() {
let offer = await pc1.createOffer();
await pc1.setLocalDescription(offer);
socket.emit("video-offer", {
from: authContext.user.name,
to: connectedTo,
sdp: pc1.localDescription
});
pc1.onicecandidate = function(event) {
if (event.candidate) {
socket.emit("candidate", {
pc: "pc1",
to: connectedTo,
from: authContext.user.name,
candidate: event.candidate
});
}
};
};
pc1.ontrack = function(event) {
let videoObj = remoteVideoRef.current;
videoObj.srcObject = event.streams[0];
};
};
//listening to socket emits from server related to video chat
if (socket) {
socket.on("gotOffer", data => {
//step 1 of callee
handleVideoOfferMsg(data);
});
socket.on("gotCandidate", data => {
handleNewICECandidateMsg(data);
});
socket.on("gotAnswer", data => {
handleGotAnswer(data);
});
}
async function handleVideoOfferMsg(msg) {
var desc = new RTCSessionDescription(msg.sdp);
await pc2.setRemoteDescription(desc);
let stream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: true
});
let videoObj = localVideoRef.current;
videoObj.srcObject = stream;
let localStream = stream;
stream
.getTracks()
.forEach(async track => await pc2.addTrack(track, localStream));
let answer = await pc2.createAnswer();
await pc2.setLocalDescription(answer);
socket.emit("video-answer", {
from: authContext.user.name,
to: connectedTo,
sdp: pc2.localDescription
});
pc2.ontrack = function(event) {
let videoObj = remoteVideoRef.current;
videoObj.srcObject = event.streams[0];
};
pc2.onicecandidate = function(event) {
if (event.candidate) {
socket.emit("candidate", {
pc: "pc2",
to: connectedTo,
from: authContext.user.name,
candidate: event.candidate
});
}
};
}
async function handleGotAnswer(msg) {
if (pc1.signalingState == "stable") {
console.log("negotiating");
return;
}
await pc1.setRemoteDescription(new RTCSessionDescription(msg.sdp));
//INSERTED THIS
if (candidatesArray.length) {
candidatesArray.forEach(async msg => {
var candidate = new RTCIceCandidate(msg.candidate);
await pc1.addIceCandidate(candidate);
});
}
}
let candidatesArray = [];
async function handleNewICECandidateMsg(msg) {
if (msg.pc == "pc1") {
var candidate = new RTCIceCandidate(msg.candidate);
await pc2.addIceCandidate(candidate);
}
if (msg.pc == "pc2") {
try {
if (pc1.connectionState != "stable" && !pc1.remoteDescription) {
candidatesArray.push(msg);
return;
}
var candidate = new RTCIceCandidate(msg.candidate);
await pc1.addIceCandidate(candidate);
} catch (error) {
//this is where error is triggered.
console.log("error adding ice candidate: " + error);
}
}
}
I have not put my server side code, because I find no issues in it.
From what I understand, the error is because remotedescription is not being set when addicecandidate is called. May be because I am skipping setting remote description when signalingState is stable. But if I remove that line of code, I am getting another error - "Failed to set remote answer sdp: Called in wrong state: kStable"
Where am I going wrong?
Immediately after calling pc.setLocalDescription(), the PeerConnection will start emitting onicecandidate events, thanks to Trickle ICE. However, this means that maybe the first candidates are generated too fast and they get sent to the remote peer even before sending SDP Offer/Answer!
Maybe that's what happens in your case, and the first candidates are arriving too early from the other side. For this reason it's a good idea to check the PeerConnection signaling state: if it is stable and the remote description has been already set, then you can call pc.addIceCandidate(). If not, you store the candidate in a queue.
Later, when the remote description finally arrives, after setting it you manually add all the candidates that are waiting in the queue.
Here you can see code with this idea. Candidates are first queued, and later when the PeerConnection signaling state becomes stable, queued items are added.