I basicly Implemented a simple webrtc application where a client streams a video and then another client can connect and access the remote stream from each other.
The first implementation I did was with AgoraRTM signaling and it worked well in all browsers. After that I wanted to try to move to websockets as the signaling part.
When I finished moving to websockets, I noticed that it kept working at Firefox, but at Chrome and Edge (probably chromium browsers) the remoteStream doesn't show up (it shows up the first and second time, but stops working if I disconnect and connect a new client)
Here is my code:
main.js (frontend logic, webrtc)
let token = null;
let uid = String(Math.floor(Math.random() * 10000));
let queryString = window.location.search;
const urlSearch = new URLSearchParams(queryString);
const room = urlSearch.get("room");
if (!room) {
window.location = "lobby.html";
}
let client;
let channel;
let socket;
const constraints = {
video: {
width: { min: 640, ideal: 1920, max: 1920 },
height: { min: 480, ideal: 1080, max: 1920 },
aspectRatio: 1.777777778,
},
audio: false,
};
const servers = {
iceServers: [
{
urls: [
"stun:stun.l.google.com:19302",
"stun:stun1.l.google.com:19302",
"stun:stun2.l.google.com:19302",
"stun:stun3.l.google.com:19302",
"stun:stun4.l.google.com:19302",
],
},
],
};
const localVideoRef = document.getElementById("localVideo");
const remoteVideoRef = document.getElementById("remoteVideo");
let localStream;
let remoteStream;
let peerConnection;
const configureSignaling = async () => {
socket = await io.connect("http://localhost:4000");
socket.emit("join", { room, uid });
socket.on("MemberJoined", handleMemberJoined);
socket.on("MessageFromPeer", handleMessageFromPeer);
socket.on("MemberLeft", handleMemberLeft)
};
const handleMemberLeft = async () => {
remoteVideoRef.style.display = "none";
};
const handleMessageFromPeer = (m, uid) => {
const message = JSON.parse(m.text);
if(message.type !== "candidate") {
console.log('handleMessageFromPeer: ', message, uid)
}
if (message.type === "offer") {
createAnswer(uid, message.offer);
}
if (message.type === "answer") {
addAnswer(message.answer);
}
if (message.type === "candidate") {
if (peerConnection && peerConnection.currentRemoteDescription) {
peerConnection.addIceCandidate(message.candidate);
}
}
};
const createLocalStream = async () => {
localStream = await navigator.mediaDevices.getUserMedia(constraints);
localVideoRef.srcObject = localStream;
localVideoRef.play()
remoteVideoRef.play()
};
const init = async () => {
await configureSignaling();
await createLocalStream();
};
const handleMemberJoined = async (uid) => {
createOffer(uid);
};
let createOffer = async (uid) => {
await createPeerConnection(uid);
let offer = await peerConnection.createOffer();
console.log({ offer })
await peerConnection.setLocalDescription(offer);
console.log('localStream: ', offer)
socket.emit(
"sendMessageToPeer",
{ text: JSON.stringify({ type: "offer", offer: offer }) },
uid
);
};
let createPeerConnection = async (uid) => {
peerConnection = new RTCPeerConnection(servers);
remoteStream = new MediaStream();
remoteVideoRef.srcObject = remoteStream;
remoteVideoRef.style.display = "block";
remoteVideoRef.classList.add("remoteFrame");
if (!localStream) {
await createLocalStream();
}
localStream.getTracks().forEach((track) => {
peerConnection.addTrack(track, localStream);
});
peerConnection.ontrack = (event) => {
event.streams[0].getTracks().forEach((track) => {
remoteStream.addTrack(track);
});
};
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
socket.emit(
"sendMessageToPeer",
{
text: JSON.stringify({
type: "candidate",
candidate: event.candidate,
}),
},
uid
);
}
};
};
let createAnswer = async (uid, offer) => {
await createPeerConnection(uid);
await peerConnection.setRemoteDescription(offer);
console.log('remoteStream: ', offer)
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
console.log('localStream: ', answer)
socket.emit(
"sendMessageToPeer",
{ text: JSON.stringify({ type: "answer", answer: answer }) },
uid
);
};
let addAnswer = async (answer) => {
if (!peerConnection.currentRemoteDescription) {
peerConnection.setRemoteDescription(answer);
}
console.log(peerConnection)
};
let onLogout = async () => {
peerConnection.close()
remoteVideoRef.classList.remove("remoteFrame");
await socket.emit('onLeaveRoom', room)
};
let onToggleCamera = async () => {
const videoTrack = localStream
.getTracks()
.find((track) => track.kind === "video");
if (videoTrack.enabled) {
videoTrack.enabled = false;
document.getElementById("camera-btn").style.backgroundColor =
"rgb(255, 80, 80)";
} else {
videoTrack.enabled = true;
document.getElementById("camera-btn").style.backgroundColor =
"rgb(179, 102, 249, .9)";
}
};
let onToggleMic = async () => {
const audioTrack = localStream
.getTracks()
.find((track) => track.kind === "audio");
if (audioTrack.enabled) {
audioTrack.enabled = false;
document.getElementById("mic-btn").style.backgroundColor =
"rgb(255, 80, 80)";
} else {
audioTrack.enabled = true;
document.getElementById("mic-btn").style.backgroundColor =
"rgb(179, 102, 249, .9)";
}
};
window.addEventListener("beforeunload", onLogout);
init();
index.js (server logic, websockets)
const express = require("express");
const app = express();
const PORT = 4000;
const http = require("http").Server(app);
const cors = require("cors");
const users = []
app.use(cors());
const socketIO = require("socket.io")(http, {
cors: {
origin: "http://127.0.0.1:5501",
},
});
//Add this before the app.get() block
socketIO.on("connection", (socket) => {
socket.on('join', async ({room, uid}) => {
users.push(uid)
await socket.join(room);
socket.broadcast.to(room).emit('MemberJoined', uid)
})
socket.on("onLeaveRoom", async (room) => {
socket.broadcast.to(room).emit('MemberLeft')
})
socket.on("disconnect", async (room) => {
socket.broadcast.to(room).emit('MemberLeft')
})
socket.on('sendMessageToPeer', (data, uid) => {
socket.broadcast.emit('MessageFromPeer', data, uid )
});
});
app.get("/api", (req, res) => {
res.json({
message: "Hello world",
});
});
http.listen(PORT, () => {
console.log(`Server listening on ${PORT}`);
});
I tried a couple of things like:
Checking if the offer and the answer was sended correctly, just 1 time and with the correct SDP
Checked if there was a problem disconnecting from a socket that leads to have more clients in a room
Tried also to add localVideoRef.play() to check if there is an issue with the autoplay for chrome, since there was a similar thread at stackoverflow (Ended up not working)
Related
Basicly I am working in building a webRTC application using socket.io as signaling, I had previously agoraRTM and the app worked fine.
The app is quite simple, basicly I have the client streaming video and when I open a new tab I want to show a small screen with the local stream and a bigger one with the remote stream.
The application works the first time I enter, so, I open a first tab (I see my local Stream), I open the second one and I see both (remote and local stream).
The problems start happening when I close one of the tabs and try to duplicate the other tab again (basicaly leaving the room and joining again)
I tried a couple of things, like disconnecting the socket, leaving the room. I am not sure why it is happening. (I am learning websockets, it might be something that I am missing)
Here is my code:
client (main.js)
let token = null;
let uid = String(Math.floor(Math.random() * 10000));
let queryString = window.location.search;
const urlSearch = new URLSearchParams(queryString);
const room = urlSearch.get("room");
if (!room) {
window.location = "lobby.html";
}
let client;
let channel;
let socket;
const constraints = {
video: {
width: { min: 640, ideal: 1920, max: 1920 },
height: { min: 480, ideal: 1080, max: 1920 },
aspectRatio: 1.777777778,
},
audio: false,
};
const servers = {
iceServers: [
{
urls: [
"stun:stun.l.google.com:19302",
"stun:stun1.l.google.com:19302",
"stun:stun2.l.google.com:19302",
"stun:stun3.l.google.com:19302",
"stun:stun4.l.google.com:19302",
],
},
],
};
const localVideoRef = document.getElementById("localVideo");
const remoteVideoRef = document.getElementById("remoteVideo");
let localStream;
let remoteStream;
let peerConnection;
const configureSignaling = async () => {
socket = await io.connect("http://localhost:4000");
socket.emit("join", { room, uid });
socket.on("MemberJoined", handleMemberJoined);
socket.on("MessageFromPeer", handleMessageFromPeer);
socket.on("MemberLeft", handleMemberLeft)
};
const handleMemberLeft = async () => {
remoteVideoRef.style.display = "none";
};
const handleMessageFromPeer = (m, uid) => {
const message = JSON.parse(m.text);
if(message.type !== "candidate") {
console.log('handleMessageFromPeer: ', message, uid)
}
if (message.type === "offer") {
createAnswer(uid, message.offer);
}
if (message.type === "answer") {
addAnswer(message.answer);
}
if (message.type === "candidate") {
if (peerConnection && peerConnection.currentRemoteDescription) {
peerConnection.addIceCandidate(message.candidate);
}
}
};
const createLocalStream = async () => {
localStream = await navigator.mediaDevices.getUserMedia(constraints);
localVideoRef.srcObject = localStream;
};
const init = async () => {
await configureSignaling();
await createLocalStream();
};
const handleMemberJoined = async (uid) => {
createOffer(uid);
};
let createOffer = async (uid) => {
await createPeerConnection(uid);
let offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
socket.emit(
"sendMessageToPeer",
{ text: JSON.stringify({ type: "offer", offer: offer }) },
uid
);
};
let createPeerConnection = async (uid) => {
peerConnection = new RTCPeerConnection(servers);
remoteStream = new MediaStream();
remoteVideoRef.srcObject = remoteStream;
remoteVideoRef.style.display = "block";
remoteVideoRef.classList.add("remoteFrame");
if (!localStream) {
await createLocalStream();
}
localStream.getTracks().forEach((track) => {
peerConnection.addTrack(track, localStream);
});
peerConnection.ontrack = (event) => {
event.streams[0].getTracks().forEach((track) => {
remoteStream.addTrack(track);
});
};
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
socket.emit(
"sendMessageToPeer",
{
text: JSON.stringify({
type: "candidate",
candidate: event.candidate,
}),
},
uid
);
}
};
};
let createAnswer = async (uid, offer) => {
await createPeerConnection(uid);
await peerConnection.setRemoteDescription(offer);
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
socket.emit(
"sendMessageToPeer",
{ text: JSON.stringify({ type: "answer", answer: answer }) },
uid
);
};
let addAnswer = async (answer) => {
console.log(!peerConnection.currentRemoteDescription, answer)
if (!peerConnection.currentRemoteDescription) {
peerConnection.setRemoteDescription(answer);
}
};
let onLogout = async () => {
remoteVideoRef.classList.remove("remoteFrame");
await socket.emit('onLeaveRoom', room)
};
let onToggleCamera = async () => {
const videoTrack = localStream
.getTracks()
.find((track) => track.kind === "video");
if (videoTrack.enabled) {
videoTrack.enabled = false;
document.getElementById("camera-btn").style.backgroundColor =
"rgb(255, 80, 80)";
} else {
videoTrack.enabled = true;
document.getElementById("camera-btn").style.backgroundColor =
"rgb(179, 102, 249, .9)";
}
};
let onToggleMic = async () => {
const audioTrack = localStream
.getTracks()
.find((track) => track.kind === "audio");
if (audioTrack.enabled) {
audioTrack.enabled = false;
document.getElementById("mic-btn").style.backgroundColor =
"rgb(255, 80, 80)";
} else {
audioTrack.enabled = true;
document.getElementById("mic-btn").style.backgroundColor =
"rgb(179, 102, 249, .9)";
}
};
window.addEventListener("beforeunload", onLogout);
init();
server (index.js)
const express = require("express");
const app = express();
const PORT = 4000;
const http = require("http").Server(app);
const cors = require("cors");
app.use(cors());
const socketIO = require("socket.io")(http, {
cors: {
origin: "http://127.0.0.1:5501",
},
});
//Add this before the app.get() block
socketIO.on("connection", (socket) => {
console.log(`⚡: ${socket.id} user just connected!`);
console.log('CONNECTED')
socket.on('join', async ({room, uid}) => {
console.log({ room })
await socket.join(room);
socket.broadcast.emit('MemberJoined', uid)
})
socket.on("onLeaveRoom", async (room) => {
await socket.leave(room)
socket.broadcast.emit('MemberLeft')
})
socket.on("disconnect", async (room) => {
await socket.leave('123')
socket.broadcast.emit('MemberLeft')
})
socket.on('sendMessageToPeer', (data, uid) => socket.broadcast.emit('MessageFromPeer', data, uid));
});
app.get("/api", (req, res) => {
res.json({
message: "Hello world",
});
});
http.listen(PORT, () => {
console.log(`Server listening on ${PORT}`);
});
I tried a couple of things, like disconnecting the socket.io when the user leaves, leaving the room . I feel like I am missing something websockets related in this case. Would like to know if that is the case :)
I'm trying to receive my partner's stream but it's not working, I don't know if I'm misimplementing the example given by WebRTC website and I can't understand it well because I'm new to this.
That I have to do?
const connection = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] });
async function start() {
const localStream = await navigator.mediaDevices.getUserMedia({video: true, audio: true});
localStream.getTracks().forEach(track => {
connection.addTrack(track, localStream);
});
}
const remoteVideo = document.querySelector('.remote-video');
connection.addEventListener('track', async (event) => {
const remoteStream = event.streams[0];
remoteVideo.srcObject = remoteStream;
connection.addTrack(event.track);
});
Please Try This:-
const start = async () => {try {
const constraints = { video: { cursor: "always" }, audio: false };
const localStream = await navigator.mediaDevices.getDisplayMedia(
constraints
);
screenStream = localStream;
let videoTrack = screenStream.getVideoTracks()[0];
return localStream;
} catch (error) {}}
const remoteVideo = document.querySelector('.remote-video')
function handleRemoteStreamAddedScreen(event) {
alert("Remote stream added.")
if ("srcObject" in remoteVideo) {
remoteVideo.srcObject = event.streams[0]
} else {
remoteVideo.src = window.URL.createObjectURL(event.stream)}
remoteScreenStream = event.stream}
I hope it will be work for you.
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);.
I´m using Gabriel Tanners video broadcasting Tutorial on localhost it works great! Over Internet connection most of the times the viewer simply doesn't show anything. I spent hours testing and changing the lifecycle. Nothing works.
Here´s the Broadcaster code:
const peerConnections = {};
const config = {
iceServers: [
{
urls: ["stun:stun.l.google.com:19302"]
}
]
};
const socket = io.connect(window.location.origin);
socket.on("answer", (id, description) => {
peerConnections[id].setRemoteDescription(description);
});
socket.on("watcher", id => {
const peerConnection = new RTCPeerConnection(config);
peerConnections[id] = peerConnection;
let stream = videoElement.srcObject;
stream.getTracks().forEach(track => peerConnection.addTrack(track, stream));
peerConnection.onicecandidate = event => {
if (event.candidate) {
socket.emit("candidate", id, event.candidate);
}
};
peerConnection
.createOffer()
.then(sdp => peerConnection.setLocalDescription(sdp))
.then(() => {
socket.emit("offer", id, peerConnection.localDescription);
});
});
socket.on("candidate", (id, candidate) => {
peerConnections[id].addIceCandidate(new RTCIceCandidate(candidate));
});
socket.on("disconnectPeer", id => {
peerConnections[id].close();
delete peerConnections[id];
});
window.onunload = window.onbeforeunload = () => {
socket.close();
};
// Get camera and microphone
const videoElement = document.querySelector("video");
const audioSelect = document.querySelector("select#audioSource");
const videoSelect = document.querySelector("select#videoSource");
audioSelect.onchange = getStream;
videoSelect.onchange = getStream;
getStream()
.then(getDevices)
.then(gotDevices);
function getDevices() {
return navigator.mediaDevices.enumerateDevices();
}
function gotDevices(deviceInfos) {
window.deviceInfos = deviceInfos;
for (const deviceInfo of deviceInfos) {
const option = document.createElement("option");
option.value = deviceInfo.deviceId;
if (deviceInfo.kind === "audioinput") {
option.text = deviceInfo.label || `Microphone ${audioSelect.length + 1}`;
audioSelect.appendChild(option);
} else if (deviceInfo.kind === "videoinput") {
option.text = deviceInfo.label || `Camera ${videoSelect.length + 1}`;
videoSelect.appendChild(option);
}
}
}
function getStream() {
if (window.stream) {
window.stream.getTracks().forEach(track => {
track.stop();
});
}
const audioSource = audioSelect.value;
const videoSource = videoSelect.value;
const constraints = {
audio: { deviceId: audioSource ? { exact: audioSource } : undefined },
video: { deviceId: videoSource ? { exact: videoSource } : undefined }
};
return navigator.mediaDevices
.getUserMedia(constraints)
.then(gotStream)
.catch(handleError);
}
function gotStream(stream) {
window.stream = stream;
audioSelect.selectedIndex = [...audioSelect.options].findIndex(
option => option.text === stream.getAudioTracks()[0].label
);
videoSelect.selectedIndex = [...videoSelect.options].findIndex(
option => option.text === stream.getVideoTracks()[0].label
);
videoElement.srcObject = stream;
socket.emit("broadcaster");
}
function handleError(error) {
console.error("Error: ", error);
}
The viewer:
let peerConnection;
const config = {
iceServers: [
{
urls: ["stun:stun.l.google.com:19302"]
}
]
};
const socket = io.connect(window.location.origin);
const video = document.querySelector("video");
socket.on("offer", (id, description) => {
peerConnection = new RTCPeerConnection(config);
peerConnection
.setRemoteDescription(description)
.then(() => peerConnection.createAnswer())
.then(sdp => peerConnection.setLocalDescription(sdp))
.then(() => {
socket.emit("answer", id, peerConnection.localDescription);
});
peerConnection.ontrack = event => {
video.srcObject = event.streams[0];
};
peerConnection.onicecandidate = event => {
if (event.candidate) {
socket.emit("candidate", id, event.candidate);
}
};
});
socket.on("candidate", (id, candidate) => {
peerConnection
.addIceCandidate(new RTCIceCandidate(candidate))
.catch(e => console.error(e));
});
socket.on("connect", () => {
socket.emit("watcher");
});
socket.on("broadcaster", () => {
socket.emit("watcher");
});
socket.on("disconnectPeer", () => {
peerConnection.close();
});
window.onunload = window.onbeforeunload = () => {
socket.close();
};
I´m very thankful for your help
Since the two devices are not in the same network, you will need a TURN server for the establishment of the connection. A TURN server is used to relay as a relay if the peer-to-peer connection fails, which seems to be happening in your case.
Since there are no public TURN servers out there, you will probably need to create your own. For that, I can recommend the following options:
Coturn - Has a lot of possible configuration options but is thereby harder to set up.
Pion TURN - Easier to set up but doesn't feature as many possibilities
If you want to learn more about the internal of WebRTC and how they work together, checkout out webrtcforthecurious.
With two peers connected via WebRTC, we can see the peer connection is complete, yet only one client is receiving the video stream (the "offering" peer). The "answering" peer is never receiving "track" events (peerConnection.ontrack).
Code for the "answering" peer that displays the problem:
const peerConnection = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun1.l.google.com:19302' }]});
const localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
localStream.getTracks().forEach((track) => { peerConnection.addTrack(track, localStream) });
const { data } = await axios.get("http://{HIDDEN}/offer");
if (data) {
// console.log('received offer', data.offer)
await peerConnection.setRemoteDescription(data.offer);
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
connectionInfo.current.answer = peerConnection.localDescription;
data.iceCandidates.forEach(async (candidate: any) => {
await peerConnection.addIceCandidate(candidate);
});
// Set peer connection event handlers
peerConnection.onicecandidate = (event) => {
if (event.candidate) connectionInfo.current.iceCandidates.push(event.candidate);
};
peerConnection.onicegatheringstatechange = async () => {
if (peerConnection.iceGatheringState === "complete" && connectionInfo.current.answer) {
await axios.post("http://{HIDDEN}/answer", connectionInfo.current);
}
};
peerConnection.ontrack = async (event) => {
remoteStream.addTrack(event.track);
};
if (peerConnection.iceGatheringState === "complete") {
await axios.post("http://{HIDDEN}/answer", connectionInfo.current);
}
}
I found this post which mentions your problem and suggests that the order in which event listeners are created relative to the peerConnection.setRemoteDescription() call is the problem: https://github.com/w3c/webrtc-pc/issues/198
Moving the event handlers immediately after the new RTCPeerConnection initialization (before the description setting) should fix the issue. For example:
const peerConnection = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun1.l.google.com:19302' }]});
// Set peer connection event handlers - MOVED UP HERE
peerConnection.onicecandidate = (event) => {
if (event.candidate) connectionInfo.current.iceCandidates.push(event.candidate);
};
peerConnection.onicegatheringstatechange = async () => {
if (peerConnection.iceGatheringState === "complete" && connectionInfo.current.answer) {
await axios.post("http://{HIDDEN}/answer", connectionInfo.current);
}
};
peerConnection.ontrack = async (event) => {
remoteStream.addTrack(event.track);
};
const localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
localStream.getTracks().forEach((track) => { peerConnection.addTrack(track, localStream) });
const { data } = await axios.get("http://{HIDDEN}/offer");
if (data) {
// console.log('received offer', data.offer)
await peerConnection.setRemoteDescription(data.offer);
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
connectionInfo.current.answer = peerConnection.localDescription;
data.iceCandidates.forEach(async (candidate: any) => {
await peerConnection.addIceCandidate(candidate);
});
if (peerConnection.iceGatheringState === "complete") {
await axios.post("http://{HIDDEN}/answer", connectionInfo.current);
}
}