Failed to execute 'setRemoteDescription' on 'RTCPeerConnection' on webrtc
So I am following the tutorial by TauhidCodes on youtube here: https://www.youtube.com/watch?v=MBOlZMLaQ8g
It is a little different because I am using reverse urls for the different room names.
I am implementing a video call system that allows more than 3 users and above to join by entering the appropriate url like https://127.0.0.1:8000/video/test/. I open 2 tabs in the same window and press start call for both of them and they both say the error.
Error
test/:1 Uncaught (in promise) InvalidStateError: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection': Failed to set remote answer sdp: Called in wrong state: stable
<!DOCTYPE html>
<html>
<head>
<style>
button {
border: 0;
background-color: orange;
padding: 10px;
color: white;
border-radius: 7px;
}
video {
border-radius: 15px;
}
.videoContainer {
display: flex;
margin: 20px;
width: 640px;
}
.videoContainer h2 {
color: white;
position: relative;
bottom: -380px;
left: -350px;
width: max-content;
}
#meet {
display: flex;
}
#recordButton.recording {
background-color: red;
}
#downloadButton {
background-color: #4caf50;
}
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
</style>
<title>A Free Bird Video Call</title>
<script src="https://meet.jit.si/external_api.js"></script>
</head>
{% load static %}
<body>
<div id="meet">
<button id="videoOff" onclick="toggleVideoOff()" disabled>Video Off</button>
<button id="audioMute" onclick="toggleAudioMute()" disabled>Mute Audio</button>
<div id="remote-videos">
<div id="videoContainer">
<video id="localVideo" autoplay playsinline></video>
<h2>{{ request.user.full_name }}</h2>
</div>
</div>
<!-- <div class="videoContainer">
<video id="remoteVideo" autoplay playsinline></video>
<h2></h2>
</div> -->
</div>
<div>
<button onclick="startCall()">Start Call</button>
<button id="recordButton" onclick="toggleRecording()" disabled>
Start Recording
</button>
<button id="downloadButton" onclick="downloadRecording()" disabled>
Download Recording
</button>
<button id="shareScreenButton" onclick="shareScreen()">Share Screen</button>
</div>
<div id="chat">
<h3>Chat</h3>
<div id="messages">
<ul id="messageList"></ul>
<input type="text" id="messageInput" placeholder="Type your message here...">
<button id="sendMessageButton">Send</button>
</div>
</div>
<input type="hidden" id="user_id" value="{{ request.user.id }}">
<script src="{% static 'js/video_call.js' %}" type="text/javascript" data-room-name="{{ room_name }}"></script>
</body>
</html>
var mapPeers = {}
var userId = document.getElementById('user_id').value;
console.log('User ID:', userId);
var ws;
function webSocketOnMessage(e) {
var parsedData = JSON.parse(e.data);
var peerUsername = parsedData['peer'];
var action = parsedData['action'];
var receiver_channel_name = parsedData['message']['receiver_channel_name'];
if (action == 'new-peer') {
createOfferer(peerUsername, receiver_channel_name);
return;
}
if (action == 'new-offer') {
var offer = parsedData['message']['sdp'];
createAnswerer(offer, peerUsername, receiver_channel_name);
return;
}
if (action == 'new-answer') {
var answer = parsedData['message']['sdp'];
var peer = mapPeers[peerUsername][0];
peer.setRemoteDescription(answer);
return;
}
}
function startCall() {
var loc = window.location;
var wsStart = 'ws://';
if (loc.protocol === 'https:') {
wsStart = 'wss://';
}
var roomName = document.querySelector('script[src*="video_call.js"]').dataset.roomName;
var endpoint = wsStart + loc.host + '/ws/webrtc/' + roomName + '/';
ws = new WebSocket(endpoint);
ws.addEventListener('open', (e) => {
console.log('Connected to WebSocket server.');
sendSignal('new-peer', {});
});
ws.addEventListener('message', webSocketOnMessage);
ws.addEventListener('error', (e) => {
console.log('WebSocket error:', e);
});
ws.addEventListener('close', (e) => {
console.log('WebSocket connection closed:', e);
});
}
var localStream = new MediaStream();
const constraints = {
'video': true,
'audio': true
}
const localVideo = document.getElementById('localVideo');
var videoOffButton = document.getElementById('videoOff');
var audioMuteButton = document.getElementById('audioMute');
var userMedia = navigator.mediaDevices.getUserMedia(constraints)
.then((mediaStream) => {
localStream = mediaStream;
localVideo.srcObject = localStream;
localVideo.muted = true;
var audioTracks = localStream.getAudioTracks();
var videoTracks = localStream.getVideoTracks();
audioTracks[0].enabled = true;
videoTracks[0].enabled = true;
videoOffButton.addEventListener('click', () => {
videoTracks[0].enabled = !videoTracks[0].enabled;
if (videoTracks[0].enabled) {
videoOffButton.textContent = 'Video Off';
return;
}
videoOffButton.textContent = 'Video On';
});
audioMuteButton.addEventListener('click', () => {
audioTracks[0].enabled = !audioTracks[0].enabled;
if (audioTracks[0].enabled) {
audioMuteButton.textContent = 'Mute Audio';
return;
}
audioMuteButton.textContent = 'Unmute Audio';
});
})
.catch((err) => {
console.error("Error accessing media devices:", err);
});
function sendSignal(action, message) {
var jsonStr = JSON.stringify({
'peer': userId,
'action': action,
'message': message,
})
ws.send(jsonStr);
}
function createOfferer(peerUsername, receiver_channel_name) {
var peer = new RTCPeerConnection(null);
addLocalTracks(peer);
var dc = peer.createDataChannel('channel');
dc.addEventListener('open', () => {
console.log('Data channel is open');
});
dc.addEventListener('message', dcOnMessage);
var remoteVideo = createVideo(peerUsername);
setOnTrack(peer, remoteVideo);
mapPeers[peerUsername] = [peer, dc];
peer.addEventListener('iceconnectionstatechange', () => {
var iceConnectionState = peer.iceConnectionState;
if (iceConnectionState === 'disconnected' || iceConnectionState === 'failed' || iceConnectionState === 'closed') {
delete mapPeers[peerUsername];
if (iceConnectionState != 'closed') {
peer.close();
}
removeVideo(remoteVideo);
}
});
peer.addEventListener('icecandidate', (event) => {
if (event.candidate) {
console.log("Sending ICE candidate", JSON.stringify(peer.localDescription));
return;
}
sendSignal('new-offer', {
'sdp': peer.localDescription,
'receiver_channel_name': receiver_channel_name,
});
});
peer.createOffer()
.then((offer) => {
peer.setLocalDescription(offer);
})
.then(() => {
console.log('local description set successfully');
});
}
function createAnswerer(offer, peerUsername, receiver_channel_name) {
var peer = new RTCPeerConnection({
'iceServers': [
{'urls': 'stun:stun.l.google.com:19302'},
{'urls': 'stun:stun1.l.google.com:19302'},
{'urls': 'turn:turn.brii.me:5349?transport=udp', 'credential': 'brii', 'username': 'brii'},
{'urls': 'turn:turn.anyfirewall.com:443?transport=tcp', 'credential': 'webrtc', 'username': 'webrtc'},
]
});
addLocalTracks(peer);
var remoteVideo = createVideo(peerUsername);
setOnTrack(peer, remoteVideo);
peer.addEventListener('datachannel', (event) => {
peer.dc = event.channel;
peer.dc.addEventListener('open', () => {
console.log('Data channel is open');
});
peer.dc.addEventListener('message', dcOnMessage);
mapPeers[peerUsername] = [peer, peer.dc];
});
peer.addEventListener('iceconnectionstatechange', () => {
var iceConnectionState = peer.iceConnectionState;
if (iceConnectionState === 'disconnected' || iceConnectionState === 'failed' || iceConnectionState === 'closed') {
delete mapPeers[peerUsername];
if (iceConnectionState != 'closed') {
peer.close();
}
removeVideo(remoteVideo);
}
});
peer.addEventListener('icecandidate', (event) => {
if (event.candidate) {
console.log("Sending ICE candidate", JSON.stringify(peer.localDescription));
return;
}
sendSignal('new-answer', {
'sdp': peer.localDescription,
'receiver_channel_name': receiver_channel_name,
});
});
peer.setRemoteDescription(offer)
.then(() => {
console.log('remote description set successfully for %s', peerUsername);
return peer.createAnswer();
})
.then(a => {
console.log('Answer created');
peer.setLocalDescription(a);
})
}
function addLocalTracks(peer) {
localStream.getTracks().forEach((track) => {
peer.addTrack(track, localStream);
});
return;
}
var messageList = document.querySelector('#messageList');
function dcOnMessage(e) {
var message = e.data;
var li = document.createElement('li');
li.appendChild(document.createTextNode(message));
messageList.appendChild(li);
}
function createVideo(peerUsername) {
var videoContainer = document.querySelector('#remote-videos');
var remoteVideo = document.createElement('video');
remoteVideo.id = `remote-video-${peerUsername}`;
remoteVideo.autoplay = true;
remoteVideo.playsinline = true;
var videoWrapper = document.createElement('div');
videoContainer.appendChild(videoWrapper);
videoWrapper.appendChild(remoteVideo);
return remoteVideo;
}
function setOnTrack(peer, remoteVideo) {
var remoteStream = new MediaStream();
remoteVideo.srcObject = remoteStream;
peer.addEventListener('track', async (event) => {
remoteStream.addTrack(event.track, remoteStream);
});
}
function removeVideo(video) {
var videoWrapper = video.parentNode;
videoWrapper.parentNode.removeChild(videoWrapper);
}
I tried to look at the code but there wasn't much different. I don't think it has any mistakes... I am expecting it to show two video streams, one local and one remote using two tabs with the video call room open. Before, it showed that there were 3 video streams when I pressed start call but only on one tab. The other tab had the same error I mentioned. I am trying to make this work because they are a non profit and don't want to spend unneccessary money.