javascriptwebrtcrtpstuncoturn

Webrtc video streaming working properly in localhost but not working in production (using google turn server)


In local server, video streaming is running proper on both ends but while network changed video of one peer is not going to display to other peer.

I am using google stun server for connection of peer with another network, but then video streaming of one user is not visible here is my code

I am not able to understand what I am doing wrong here.I am novice in webrtc

<!DOCTYPE html>
<html lang="en">
<body>
{#######  variable Initialization #######}
<script>
    let webSocket;
    let mapPeers = {};
    let username = 'user_{{ request.user.id }}';
</script>

<script>

    const iceConfiguration = {
    iceServers: [
        {
            urls: 'stun:stun.l.google.com:19302',
        }
        ]
    }

    function webSocketOnMessage(event){

        let parsedData = JSON.parse(event.data);
        console.log(parsedData);
        let peerUserName = parsedData['peer'];
        let action = parsedData['action'];
        console.log(parsedData);
        if(username === peerUserName){
            return;
        }

        let receiver_channel_name = parsedData['message']['receiver_channel_name'];

        if(action === 'new-peer'){
            if (!(peerUserName in mapPeers)){
                createOfferer(peerUserName, receiver_channel_name);
                return;
            }
            else {
                setTimeout(() => {
                    if (!(peerUserName in mapPeers)){
                        createOfferer(peerUserName, receiver_channel_name);
                        return
                    }
                }, 1000);
            }
        }

        if(action === 'new-offer'){
            let offer = parsedData['message']['sdp'];
            createAnswerer(offer, peerUserName, receiver_channel_name);
            return;
        }

        if(action === 'new-answer'){
            let answer = parsedData['message']['sdp'];
            let peer  = mapPeers[peerUserName][0];

            peer.setRemoteDescription(answer);
            return;
        }

        if(action === 'video-on'){
            manageVideoEl('on', peerUserName)
            return;
        }

        if(action === 'video-off'){
            manageVideoEl('off', peerUserName)
            return;
        }
    }
</script>


<script>

$(document).ready(function(){

    let loc = window.location;
    let wsStart = 'ws://';

    if(loc.protocol === 'https:'){
        wsStart = 'wss://';
    }

    let endPoint = wsStart + loc.host + '/ws/video/meet/'+ '{{ group_name }}/';

    webSocket = new WebSocket(endPoint);
    webSocket.addEventListener('open', (e)=>{
        sendSignal('new-peer', {'receiver_channel_name': '{{ group_name }}'});

    });
    webSocket.addEventListener('message', webSocketOnMessage);
    webSocket.addEventListener('close', (e)=>{
    });
    webSocket.addEventListener('error', (e)=>{
    });

    {% if is_group_creator %}
        sendNotificationOnMessage();
    {% endif %}
});


let localStream = new MediaStream();

    const constraints = {
        'video': true,
        'audio': true
    }

    const localVideo = document.querySelector('#local-video');

    const btnToggleAudio = document.querySelector('#btn-toggle-audio');
    const btnToggleVideo = document.querySelector('#btn-toggle-video');



    let userMedia = navigator.mediaDevices.getUserMedia(constraints)
        .then(stream => {

            localStream = stream;
            localVideo.srcObject = localStream;
            localVideo.muted = true;


            let audioTracks = stream.getAudioTracks();
            let videoTracks = stream.getVideoTracks();

            audioTracks[0].enabled = true;
            videoTracks[0].enabled = true;

            btnToggleAudio.addEventListener('click', ()=>{
                audioTracks[0].enabled = !audioTracks[0].enabled;

                if(audioTracks[0].enabled){
                    btnToggleAudio.classList.replace('mic-off', 'mic-on')
                    return;
                }
                btnToggleAudio.classList.replace('mic-on','mic-off');
            });

            btnToggleVideo.addEventListener('click', ()=>{
                videoTracks[0].enabled = !videoTracks[0].enabled;

                if(videoTracks[0].enabled){
                    btnToggleVideo.classList.replace('camera-off','camera-on');
                    sendSignal('video-on', {})
                    localVideo.srcObject = localStream;
                    return;
                }
                sendSignal('video-off', {})
                localVideo.srcObject = null;
                btnToggleVideo.classList.replace('camera-on','camera-off');
            });
        })
        .catch(error =>{
            {#console.log('Error accessing media devices', error);#}
        });

</script>


<script>

    function sendSignal(action, message){
        console.log("Sending message to other end");
        let jsonStr = JSON.stringify({
        'peer': username,
        'action': action,
        'message': message
        });
        console.log(jsonStr);
        webSocket.send(jsonStr);
    }

    function createOfferer(peerUserName, receiver_channel_name){
        console.log("creating offer");

        let peer = new RTCPeerConnection(iceConfiguration);
        addLocalTracks(peer);
        peer.addEventListener("icegatheringstatechange", ev => {
  switch(peer.iceGatheringState) {
    case "new":
      console.log("gathering is either just starting or has been reset");
      break;
    case "gathering":
      console.log("gathering has begun or is ongoing");
      break;
    case "complete":
      console.log("gathering has ended");
      break;
  }
});
        peer.addEventListener('icecandidate', (event)=>{

           if(event.candidate){
               console.log('new ice candidate', JSON.stringify(peer.localDescription));
               return;
           }
           sendSignal('new-offer', {
               'sdp':peer.localDescription,
               'receiver_channel_name': receiver_channel_name
           });
           // to notify video status of other users when new users join
           if(!localStream.getVideoTracks()[0].enabled){
               sendSignal('video-off', {})
           }

        });


        let dc = peer.createDataChannel('channel');
        dc.addEventListener('open', ()=>{
            console.log("dc connection opened");
        });
        dc.addEventListener('message', dcOnMessage);
        console.log("Creating video");
        let remoteVideo = createVideo(peerUserName);
        console.log("video created, setting track");
        setOnTrack(peer, remoteVideo);
        console.log("track setted");
        mapPeers[peerUserName] = [peer, dc];

        peer.addEventListener('iceconnectionstatechange', ()=>{
           let iceconnectionState = peer.iceConnectionState;

           if(iceconnectionState === 'failed' || iceconnectionState === 'disconnected' || iceconnectionState === 'closed'){
               delete mapPeers[peerUserName];

               if(iceconnectionState !== 'closed'){
                   peer.close();
               }

               removeVideo(remoteVideo);
           }
        });



        peer.createOffer()
            .then(o => peer.setLocalDescription(o))
            .then(() => {
                {#console.log("local description set successfully");#}
            });


    }


    function createAnswerer(offer, peerUserName, receiver_channel_name){
        let peer = new RTCPeerConnection(iceConfiguration);

        addLocalTracks(peer);
        let remoteVideo = createVideo(peerUserName);

        setOnTrack(peer, remoteVideo);
        peer.addEventListener('datachannel', e=>{
           peer.dc = e.channel;
            peer.dc.addEventListener('open', ()=>{
                {#console.log("dc connection opened");#}
            });
            peer.dc.addEventListener('message', dcOnMessage);
            mapPeers[peerUserName] = [peer, peer.dc];

        });

        peer.addEventListener('iceconnectionstatechange', ()=>{
           let iceconnectionState = peer.iceConnectionState;

           if(iceconnectionState === 'failed' || iceconnectionState === 'disconnected' || iceconnectionState === 'closed'){
               delete mapPeers[peerUserName];

               if(iceconnectionState !== 'closed'){
                   peer.close();
               }

               removeVideo(remoteVideo);
           }
        });

        peer.addEventListener('icecandidate', (event)=>{
           if(event.candidate){
               {#console.log('new 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;
    }


    function  createVideo(peerUserName){
        userId = peerUserName.split('_')[1]

        // Video element Creation
        let remoteVideo = document.createElement('video');
        remoteVideo.id = peerUserName + '-video';
        remoteVideo.autoplay = true;
        remoteVideo.playsInline = true;
        remoteVideo.classList.add('custom-video');
        remoteVideo.setAttribute('data-id', userId);
        addVideoToDOM(remoteVideo, userId)

        return remoteVideo;
    }

    function addVideoToDOM(video, userId){
        let videoContainer = document.querySelector('#video-container');
        $.getJSON(`/chat/call/participant/${userId}`, function(data){
            // Styling Elements
            video.style.backgroundImage = `url('${data.profile_image_url}')` // if video off then show this bg
            let nameTag = document.createElement('span');
            nameTag.classList.add('name-tag');
            nameTag.innerText = data.username;
            let participantActionsEl =  document.createElement('div');
            participantActionsEl.classList.add('participant-actions');
            let videoParticipantEl = document.createElement('div');
            videoParticipantEl.classList.add('video-participant');
            videoParticipantEl.appendChild(participantActionsEl);
            videoParticipantEl.appendChild(nameTag);
            videoParticipantEl.appendChild(video);
            videoParticipantEl.setAttribute('data-delete', 'true') // For removing element
            videoParticipantEl.setAttribute('data-id', userId) // For showing feature
            videoContainer.appendChild(videoParticipantEl);
            addMemberToList(data)
        })
    }

   

    function manageVideoEl(status, peerUserName) {
        const userId = peerUserName.split('_')[1]
        // After element in DOM update video element
        setTimeout(() => {
            let videoEl = document.querySelector(`video[data-id="${userId}"]`)
            if (videoEl !== null){
                // Saving source
                if (mapPeers[peerUserName]){
                    if (videoEl.srcObject != null){
                        mapPeers[peerUserName][2] = videoEl.srcObject;
                    }

                    if (status==='on'){
                        videoEl.srcObject = mapPeers[peerUserName][2] || null;
                    } else if (status==='off'){
                        videoEl.srcObject = null;
                    }
                }
            }
        }, 1000);
    }

    function setOnTrack(peer, remoteVideo){
        let remoteStream = new MediaStream();
        remoteVideo.srcObject = remoteStream;
        peer.addEventListener('track', async (event)=>{
            console.log(remoteStream);
           remoteStream.addTrack(event.track, remoteStream);
        });
    }

    function removeVideo(video){
        removeMemberFromList(video.dataset.id)
        video.closest(`[data-delete='true']`).remove()
    }

</script>



{######### script to talk with group consumer #######}
<script>
    let group_chatSocket_chat;
    let receiver_group = "{{group.id }}";
{% if is_call_starter and send_notifications %}
    function sendNotificationOnMessage()
    {

        group_chatSocket_chat = new ReconnectingWebSocket(
            'ws://' + window.location.host +
            '/ws/chat/group/' + "{{ group.name }}" + '/'+ "{{ request.user.id }}" +'/');

        group_chatSocket_chat.onopen = function(e){
            {#console.log("Connection open");#}
            let url = `${location.protocol + '//' + location.host}/chat/video/{{ join_url }}`;
            group_chatSocket_chat.send(JSON.stringify({
                            'message': `New Video call is started! Join Now Link: ${url}`,
                            'receiver_id': receiver_group,
                            'command': 'new_message',
                            'bucket_id': 0,
                        }));
        }


    }
{% endif %}


</script>

</body>

Solution

  • as you move out side of the network the webrtc ICE candidate gathering process may fail because of Routers NAT and firewalls therefore you must have a turn server in your configuration that will relay the traffic if the direct p2p connection establishment fails