Video streaming 使用webrtc的双向视频流

Video streaming 使用webrtc的双向视频流,video-streaming,webrtc,two-way,Video Streaming,Webrtc,Two Way,我想从两台电脑上传输视频,并在浏览器上查看这两个视频。使用下面的代码,我在一台电脑上获得两个视频,但在另一台电脑上没有,这意味着一台电脑正在将视频流传输到远程电脑,而另一台电脑没有将其视频流传输到第一个对等机。我在这个程序中使用单个peerconnection对象。谁能告诉我这里出了什么问题吗 var constraints = { audio: true, video: true }; var configuration = { iceServers:[{

我想从两台电脑上传输视频,并在浏览器上查看这两个视频。使用下面的代码,我在一台电脑上获得两个视频,但在另一台电脑上没有,这意味着一台电脑正在将视频流传输到远程电脑,而另一台电脑没有将其视频流传输到第一个对等机。我在这个程序中使用单个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);      
        });
    }
我看到两个问题:

照相机正在加速 在接到来电时,似乎没有及时添加摄像头:

    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候选人,他们可能会在报价后立即到达

Ice候选人比你想象的来得早 除此之外,您的
handleNewICECandidateMsg
看起来是错误的:

function handleNewICECandidateMsg(candidate) {    
    if (pc.localDescription !== null && pc.remoteDescription !== null && pc.signalingState === "stable")
        pc.addIceCandidate(new RTCIceCandidate(candidate))
涓流冰与报价/应答交换重叠,不等待<代码>稳定状态

当对等方的
setLocalDescription
成功回调完成后,ICE候选方就开始从对等方启动,这意味着您的信令通道(只要它保持顺序)如下所示:
offer,candidate,candidate,candidate
,一种方式,和
应答,candidate,candidate,candidate
另一种方式。基本上,如果您看到候选人,请添加:

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

希望对您有所帮助。

我还强烈推荐官方WebRTC polyfill处理浏览器更改。这将大大简化此代码。我尝试了上述建议,但没有得到结果。我还有一个疑问。我正在使用单个PeerConnection对象来连接对等点a和对等点B。对等点a和对等点B将在各自的端口上侦听。现在,我可以使用此单一连接将视频流从对等点A传输到对等点B,反之亦然,还是需要创建另一个对等连接对象以将视频流从对等点B传输到对等点A,我们是否需要两个PeerConnection对象在使用WebRTC的P2P视频连接中来回传输视频,或者一个就足够了。当我使用上述代码中所述的单个PeerConnection对象时,我会在创建报价或应答之前调用onaddstream。尽管如此,两个视频都只在一个对等点上显示,但在另一个对等点上,我没有收到远程流。无法理解这里出了什么问题。我的问题解决了,我的上述疑问也消除了。创建对等连接对象并设置其处理程序(如onaddstream、OnGotiationRequired)后,仅当您确定其他对等方已联机并准备就绪时,才可以使用onicecandidate发送要约。为了实现这一点,我使用信令服务器来维护一个变量,该变量将由第一个对等方设置。然后第二个对等方确定另一个对等方已经在线,因此它可以发送要约消息。通过这种方式,我使用信令服务器来协调提供/应答交换。在发送报价/应答之前,我正在调用addStream方法。