使用webrtc进行双向视频流传输

时间:2016-08-05 11:28:14

标签: video-streaming webrtc two-way

我想从两台电脑上传输视频并在浏览器上看到这两个视频。使用以下代码,我将两个视频都放在一台PC上但不放在另一台PC上,这意味着一台PC正在将视频流式传输到远程的视频,而另一台则不会将视频流式传输到第一台PC。我在这个程序中使用单个peerconnection对象。任何人都可以告诉我这里出了什么问题。

    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);      
        });
    }

1 个答案:

答案 0 :(得分:1)

我看到两个问题:

相机正在比赛

在来电时看起来没有及时添加摄像头:

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

addCameraMic是一个异步函数,但不返回任何承诺,因此可能会与pc.setRemoteDescription竞争失败,这意味着pc.createAnswer在添加流之前运行,导致它回答没有视频。

修复addCameraMic以返回承诺,并尝试相反:

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

这应确保在回答之前添加流。

注意:请务必先致电pc.setRemoteDescription,让pc准备接受ICE候选人,这可能会在优惠之后立即到达。

冰候选人比你想象的要早到达

除此之外,您的handleNewICECandidateMsg看起来错了:

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

Trickle ICE重叠了提议/答案交换,并且不等待stable状态。

ICE候选人在对等方的setLocalDescription成功回调完成后立即从对等方开始触发,这意味着您的信令通道 - 只要保留顺序 - 如下所示:offer, candidate, candidate, candidate,单向和另一个answer, candidate, candidate, candidate。基本上,如果您看到候选人,请添加它:

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

希望有所帮助。