1

I want to stream video from two PCs and see both the videos on browser. With the following code I am getting both the videos on one PC but not on the other one that means one pc is streaming video to the remote one but the other one is not streaming its video to the first peer. I am using single peerconnection object in this program. Can anyone let me what's going wrong here.

    var constraints = { audio: true, video: true };    
    var configuration = { iceServers:[{
                            urls: "stun:stun.services.mozilla.com",
                            username: "louis@mozilla.com",
                            credential: "webrtcdemo"
                        }, {
                            urls:["stun:xx.xx.xx.xx:8085"]
                        }]
    };
    var pc;

    function handlePeerAvailableCheckMsg(cmd) {
        console.log('peer-availability ' + cmd);
        start();
        if (cmd === "peer-available-yes") {        
            addCameraMic();
        }    
    }

    function addCameraMic() {
        var localStream;
var promisifiedOldGUM = function(constraints) {            
            var getUserMedia = (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia);

            if(!getUserMedia) {
                return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
            }            
            return new Promise(function(resolve, reject) {
                getUserMedia.call(navigator, constraints, resolve, reject);
            });
        }


        if(navigator.mediaDevices === undefined) {
            navigator.mediaDevices = {};
        }

        if(navigator.mediaDevices.getUserMedia === undefined) {
            navigator.mediaDevices.getUserMedia = promisifiedOldGUM;
        }

        //get local media (camera, microphone) and render on screen.
            navigator.mediaDevices.getUserMedia(constraints)
            .then(function(stream) {        
                var lvideo = document.getElementById('lvideo');
                localStream = stream;
                lvideo.src = window.URL.createObjectURL(localStream);        
                lvideo.onloadedmetadata = function(e) {
                    lvideo.play();
                };
                //Adding a track to a connection triggers renegotiation by firing an negotiationneeded event.
                //localStream.getTracks().forEach(track => pc.addTrack(track,localStream));
                pc.addStream(stream);
            }).catch(function(err) {
                console.log("getMediaUser Reject - " + err.name + ": " + err.message);
            });
    }

    function start() {        
        var RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection;
        pc = new RTCPeerConnection(configuration);

        // send any ice candidates to the other peer, the local ICE agent needs to deliver a message to the other peer through the signaling server.
        // this will be done once the local description is set successfully, icecandidate events will be generated by local ice agent.
        pc.onicecandidate = function (evt) {
            if (!evt.candidate) return;
            console.log('onicecandidate: '+ evt.candidate);
            wbClient.send(JSON.stringify({"cmd":"new-ice-candidate","candidate": evt.candidate }));
        };

        //Start negotiation by setting local description and sending video offer to the remote peer.
        pc.onnegotiationneeded = function(evt){            
            pc.createOffer()
            .then(function(offer){ //offer is nothing but SDP
                    console.log('Local description is '+ offer + ' and its status when offer created' + pc.signalingState);                
                    return pc.setLocalDescription(offer);
            })
            .then(function(){
                    wbClient.send(JSON.stringify({"userName":userName,"roomId":roomId,"cmd":"video-offer","sdp":pc.localDescription}));                
            })
            .catch(function(err) {                
                    console.log("[INFO:whiteboard.js:start] - Offer could not be sent to the remote peer - " + err.name + ": " + err.message);
            });
        };

        pc.onaddstream = function (evt) {
            console.log('[INFO:whiteboard.js:start] - Receiving video stream from remote peer');
            var rvideo = document.getElementById('rvideo');
            rvideo.src = window.URL.createObjectURL(evt.stream);
            rvideo.onloadedmetadata = function(e) {
                console.log('remote video metadata loaded..');
                rvideo.play();
            };
        };        
    }

    //will be invoked when a message from remote peer arrives at this client
    function handleVideoOfferMsg(obj) {    

        var RTCSessionDescription = window.webkitRTCSessionDescription || window.RTCSessionDescription;
        //Session description based on received SDP from remote peer.
        var desc = new RTCSessionDescription(obj.sdp);
        var descJson = desc.toJSON();
        console.log('Received session description (new offer) : ' + JSON.stringify(descJson));    
        //Set remote peer's capabilities based on received SDP
        console.log('While processing incoming offer - LDT : ' + pc.localDescription + ' RDT ' + pc.remoteDescription);
        addCameraMic();
        pc.setRemoteDescription(desc)        
            .then(function(){
                return pc.createAnswer();
            })
            .then (function(answer){
                //var ansJson = answer.toJSON();
                return pc.setLocalDescription(answer);
            })        
            .then (function(){                              
                console.log('sending answer to the remote peer ' + pc.localDescription);
                wbClient.send(JSON.stringify({"userName":obj.userName,"roomId":roomId,"cmd":"video-answer","sdp":pc.localDescription}));                
            })        
            .catch(function(err) {            
                console.log("Error while sending answer " + err.name + ": " + err.message);      
            });
    }

    function handleVideoAnswerMsg(desc) {
        if (pc.localDescription === null || pc.localDescription.type !== "offer") return;
        var RTCSessionDescription = window.webkitRTCSessionDescription || window.RTCSessionDescription;
        var des = new RTCSessionDescription(desc);
        var descJson = des.toJSON();
        console.log('Received answer session description (new answer) : ' + JSON.stringify(descJson));
        pc.setRemoteDescription(des); //set remote description to incoming description object of remote peer
    }

    function handleNewICECandidateMsg(candidate) {    
        var RTCIceCandidate = window.webkitRTCIceCandidate || window.RTCIceCandidate;
        if (pc.localDescription !== null && pc.remoteDescription !== null && pc.signalingState === "stable")
            pc.addIceCandidate(new RTCIceCandidate(candidate))
        .catch(function(err) {
            console.log('handleNewICECandidateMsg ' + err.name + ": " + err.message);      
        });
    }
Satya Narayana
  • 454
  • 6
  • 20

1 Answers1

1

I see two problems:

Camera is racing

It looks like the camera is not added in time on an incoming call:

    addCameraMic();
    pc.setRemoteDescription(desc)        
        .then(function(){
            return pc.createAnswer();
        })

addCameraMic is an asynchronous function, but returns no promise, so it is likely racing with pc.setRemoteDescription and losing, which means pc.createAnswer runs before a stream has been added, causing it to answer without video.

Fix addCameraMic to return a promise, and try this instead:

pc.setRemoteDescription(desc)
    .then(addCameraMic)
    .then(function() {
        return pc.createAnswer();
    })

This should ensure that the stream is added before the answer.

Note: Always call pc.setRemoteDescription first, to make pc ready to accept ICE candidates, which may arrive immediately after the offer.

Ice candidates arrive earlier than you think

In addition to this, your handleNewICECandidateMsg looks wrong:

function handleNewICECandidateMsg(candidate) {    
    if (pc.localDescription !== null && pc.remoteDescription !== null && pc.signalingState === "stable")
        pc.addIceCandidate(new RTCIceCandidate(candidate))

Trickle ICE overlaps the offer/answer exchange, and doesn't wait for stable state.

ICE candidates start firing from a peer as soon as the peer's setLocalDescription's success callback has completed, which means your signaling channel - provided it preserves order - looks like this: offer, candidate, candidate, candidate, one way, and answer, candidate, candidate, candidate the other. Basically, if you see a candidate, add it:

function handleNewICECandidateMsg(candidate) {    
    pc.addIceCandidate(new RTCIceCandidate(candidate))

Hope that helps.

jib
  • 40,579
  • 17
  • 100
  • 158
  • I also highly recommend [adapter.js](https://github.com/webrtc/adapter), the official WebRTC polyfill, to deal with browser changes. It would simplify this code quite a bit. – jib Aug 06 '16 at 03:21
  • I tried above suggestions but could not get result. I have one more doubt. I am using a single PeerConnection object for connection between Peer A and Peer B. Peer A and Peer B will be listening on their respective ports. Now, Can I use this single connection for transmitting video streams from Peer A to Peer B and vice-versa or do I need to create another PeerConnection object for video stream transmission from Peer B to Peer A. What I want to now is, do we need two PeerConnection objects for to and fro video transmission in a P2P video connection using WebRTC or single one is suffice. – Satya Narayana Aug 08 '16 at 04:04
  • When I am using single PeerConnection object as described in above code, i am calling onaddstream before creating offer or answer. still both the videos are being displayed on one peer only but in other peer i am not received remote stream. unable to understand what's going wrong here. – Satya Narayana Aug 08 '16 at 05:14
  • My problem is solved and my above doubt is also cleared. After creating a peer connection object and setting its handlers like onaddstream, onnegotiationneeded, onicecandidate send offer only when you are sure that other peer is online and is ready. to achieve this i am using signaling server which maintains a variable which will be set by the first peer. then the second peer is sure that another peer is already online hence it can send offer message. this way i am using signaling server to coordinate offer/answer exchange. i am calling addStream method before sending offer/answer. – Satya Narayana Aug 08 '16 at 05:56