3

im trying webrtc for the first time for a video chat app , i want up to 3 person in each chat ... my code works fine with 2 person chat

but as soon as 3rd person joins the chat everything goes wrong ... i get multiple video tags in the page and none of them are from the 3rd pear .... i'd appreciate any pointers or suggestion most tutorials cover 2 person chat

here is working url

https://chate-test-3000.herokuapp.com/

here is my code

const PEARS = [];
var video_counter = 0 ;
const STREAMES = [] ;

var myVideoArea = document.querySelector('#myvideo');

var configuration = {
    'iceServers': [{
        'url': 'stun:stun.l.google.com:19302'
    }]
};
var rtcPeerConn;

const ROOM = 'caht1';
const SIGNAL_ROOM = 'newsingal1234567898765';

io = io.connect("" ,  {transports:['websocket']});
io.emit('ready' , { chat_room : ROOM , signaling_room : SIGNAL_ROOM});


io.emit('signal' , { text :'ready for video ? ' , room : SIGNAL_ROOM , type : 'user_here'});
io.on('signlaing_message' , function(data){

   console.log('signal recived');
   console.log(data);

  if(!PEARS.includes(data.pear_id))
  {
    console.log('adding new pear --- ' , data.pear_id);
    PEARS.push(data.pear_id);
    startSignaling(data.pear_id);
  }

  if (data.type != "user_here")
  {
        var message = JSON.parse(data.message);
        if (message.sdp) {
            rtcPeerConn.setRemoteDescription(new RTCSessionDescription(message.sdp), function () {
                // if we received an offer, we need to answer
                if (rtcPeerConn.remoteDescription.type == 'offer') {
                    rtcPeerConn.createAnswer(sendLocalDesc, logError);
                }
            }, logError);
        }
        else {
            rtcPeerConn.addIceCandidate(new RTCIceCandidate(message.candidate));
        }
  }

})


function startSignaling(pear_id) {


    if(!rtcPeerConn)
    rtcPeerConn = new RTCPeerConnection(configuration);

    // send any ice candidates to the other peer
    rtcPeerConn.onicecandidate = function (evt) {
        if (evt.candidate)
            io.emit('signal',{"type":"ice candidate", "message": JSON.stringify({ 'candidate': evt.candidate }), "room":SIGNAL_ROOM});
        displaySignalMessage("completed that ice candidate...");
    };

    // let the 'negotiationneeded' event trigger offer generation
    rtcPeerConn.onnegotiationneeded = function () {
        displaySignalMessage("on negotiation called");
        rtcPeerConn.createOffer(sendLocalDesc, logError);
    }

    // once remote stream arrives, show it in the remote video element
    rtcPeerConn.ontrack = function (evt) {
        displaySignalMessage("going to add their stream...");

        video_counter++ ;
        let vid = 'video-box-'+video_counter  ;
        console.log('adding new STREAM  !!')
        console.log('###### streams  ' , evt.streams);

        if(!STREAMES.includes(evt.streams[0].id))
        {
            STREAMES.push(evt.streams[0].id);
            $('#video-wrapper').append(`<video data-id="${evt.streams[0].id}" id="${vid}" autoplay loop autobuffer muted playsinline controls></video>`);
            console.log(' video length ..... ' , $('#video-wrapper').find('#'+vid).length );
            var theirVideoArea = $('#video-wrapper').find('#'+vid)[0];
            console.log(theirVideoArea);
            theirVideoArea.srcObject = evt.streams[0] ;
            theirVideoArea.play();
        }
        
    };

    // get a local stream, show it in our video tag and add it to be sent
        navigator.getUserMedia = navigator.getUserMedia  || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
        navigator.getUserMedia({
            'audio': true,
            'video': true
        }, function (stream) {
            displaySignalMessage("going to display my stream...");

            myVideoArea.srcObject = stream
            myVideoArea.play();

            for (const track of stream.getTracks()) {
            rtcPeerConn.addTrack(track, stream);
            }

        }, logError);

}


function sendLocalDesc(desc) {
    rtcPeerConn.setLocalDescription(desc, function () {
        displaySignalMessage("sending local description");
        io.emit('signal',{"type":"SDP", "message": JSON.stringify({ 'sdp': rtcPeerConn.localDescription }), "room":SIGNAL_ROOM});
    }, logError);
}

function logError(error) {
    $('#error-area').append(`<div> ${error.name} : ${error.message}</div>`);
}

function displaySignalMessage(text  ){
    $('#signal-area').append(`<div>${text}</div>`);
}

i also use a simple nodejs server for signaling and use socket.io to connect to the server

------------------------- EDIT - PEER.JS -----------------

here is my code after switching to peerjs

const SIGNAL_ROOM = 'zxsingalroom';
var MY_PEER_ID = '' ;
const CurrentPeers = [] ;
io = io.connect("" ,  {transports:['websocket']});
io.emit('ready' , { chat_room : ROOM , signaling_room : SIGNAL_ROOM});



var peer = new Peer({
    config: {'iceServers': [
            { url: 'stun:stun.l.google.com:19302' },
        ]} /* Sample servers, please use appropriate ones */
});

peer.on('open', function(id) {
    console.log('My peer ID is: ' + id);
    MY_PEER_ID = id ;
    io.emit('peer_id_offer' , {  chat_room  : ROOM , id : id});
});

peer.on('call' , function (call) {


    navigator.mediaDevices.getUserMedia({ video : true , audio : true })
        .then((stream) => {

            call.answer(stream);
            call.on('stream' , function(remoteStream){
                if(!CurrentPeers.includes(call.peer))
                {
                    CurrentPeers.push(call.peer);
                    addRemoteVideo(remoteStream);
                }

            })


        })
        .catch( (e)=>{
            console.log('error2' , e );
        });

})


io.on('peer_id_recived' , function(data){

    console.log(`peer id recived : `);
    console.log(data);


    for (let [key, value] of Object.entries(data.peer_ids)) {
        if(value.peer_id != MY_PEER_ID)
        {
            callPeer(value.peer_id);
        }
    }

});


function callPeer( id )
{
        console.log('calling peers 1 .... ');
        navigator.mediaDevices.getUserMedia({ video : true , audio : true })
        .then(  (stream) => {
            console.log('calling peers  2 .... ' + id);
            addOurVideo(stream);

            let  call = peer.call(id , stream);
            console.log( typeof call);
            call.on('stream' , function(remoteStream){
                console.log('calling peers  3 .... ');
                if(!CurrentPeers.includes(call.peer))
                {
                    CurrentPeers.push(call.peer);
                    addRemoteVideo(remoteStream);
                }

            })
        })
        .catch( (e)=>{
            console.log('error1' , e );
        });
}

function addRemoteVideo(stream){

    console.log(' adding remote stream!!!');
    let total_perrs = CurrentPeers.length ;
    let vid = `video-box-${total_perrs}`;

   $('#video-wrapper').append(`<video  id="${vid}" autoplay loop autobuffer muted playsinline controls></video>`);
    var theirVideoArea = $('#video-wrapper').find('#'+vid)[0];
   theirVideoArea.srcObject = stream ;
   theirVideoArea.play();

}
function addOurVideo(stream){

    console.log(' adding our stream');
    var ourVideArea = $('#video-wrapper').find('#our-video')[0];
    ourVideArea.srcObject = stream ;
    ourVideArea.play();

}
hretic
  • 999
  • 9
  • 36
  • 78
  • are you using peerJS? if so how do you manage calls and responding when a new user enters the chat room? do you send a videoStream object on user connection? when you receive the videoStream object from the other peer after they connect do you append it to a video Grid or you just append your own video again? – Sid Barrack Jul 23 '21 at 12:37
  • @ahmedcheikhsidahmed i wasnt using peer js , after you've mentioned it i tried and wrote a peerjs verion ... i've added that version to bottom of my question please take a look – hretic Jul 26 '21 at 11:11
  • are you getting any errors? try to `console.log(CurrentPeers)` to see if the connection is established if its the case then you know its a frontend problem maybe related to the way you append the video to your videogrid – Sid Barrack Jul 26 '21 at 11:41
  • @ahmedcheikhsidahmed thanx after switching to peer it seems to be working , but when the connections went bit up i've experienced some video freeze and i got this `WebRTC: ICE failed, add a TURN server and see about:webrtc for more details` ... it started to show after couple of seconds again .... but i assume there are some things like this that i should solve in order to have a good product , since you seem to have experience with webrtc is there any suggestion or tip u can share with me ? is using commercial server necessary for having a good app ? – hretic Jul 26 '21 at 12:16
  • cool! glad its fixed I'm gonna post an answer recapping what I suggested in the comments and a few suggestion for your last error for others that might run into the same thing – Sid Barrack Jul 26 '21 at 12:21
  • I added an answer could you mark it as accepted if your problem is fixed for others that might run into the same issue, thanks! – Sid Barrack Jul 26 '21 at 12:41

1 Answers1

1

You should use some sort of P2P or Media Server to handle multiple simultaneous connections from different clients PeerJS is a great option. for the WebRTC: ICE failed, add a TURN server and see about:webrtc for more details error its exactly what it says STUN servers are used to create the connection but if the P2P connection cannot be established, the fallback is that all the communication goes through a TURN server, so they need high resources and bandwidth. TURN servers are generally not free but one open source option that might fix your problem is using a COTURN server https://github.com/coturn/coturn you should put the following example config in your PeerJS options

"iceServers": [
         {
          "urls": "stun:vc.example.com:3478"
         },
         {
          "urls": "turn:vc.example.com:3478",
          "username": "coturnUser",
          "credential": "coturnUserPassword"
         }
      ],

you could specify "iceTransportPolicy": "relay" before urls to only use relay server(without P2P)

Sid Barrack
  • 1,235
  • 7
  • 17
  • thanx actually i prefer p2p , so if i add a turn server ... it would work as a fallback option ? meaning that it would try p2p first and if failed it will use the TURN server ? i should host the COTURN server myself right ? ... is p2p connection reliable in general ? – hretic Jul 26 '21 at 13:20
  • yes the connection will be established using the STUN server first and communication using P2P and if there is a problem with P2P connection the COTURN server will be used as fallback. And yes you do need to configure and host the COTURN server yourself – Sid Barrack Jul 26 '21 at 13:28
  • you could use a commercial TURN relay server there are many https://xirsys.com/ for example. Can you please mark the answer as correct since the original question is answered – Sid Barrack Jul 26 '21 at 13:36
  • i mean i only have to change the `iceServers` if i decide to use commercial servers right ? – hretic Jul 26 '21 at 13:39
  • yes you just add the address and credentials given by your provider and set `"iceTransportPolicy": "relay"` for TURN – Sid Barrack Jul 26 '21 at 13:42
  • ok last question , if p2p is faster (which it should since it's a direct connection) , why is using those commercial servers better ? unless they handle some network problems better .... any idea ? – hretic Jul 26 '21 at 13:47
  • I think its just a question of availability you don't want your WebRTC system to function only when there is a P2P connection you want a plan B in case P2P connection fails thats why TURN is used as a fallback – Sid Barrack Jul 26 '21 at 15:56
  • its only when the STUN server isn't able to establish a Peer-to-Peer connection between 2 IP addresses that the TURN is used – Sid Barrack Jul 26 '21 at 15:59