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.

Вернуться на верх