двусторонняя потоковая передача видео с использованием webrtc

Я хочу транслировать видео с двух компьютеров и просматривать оба видео в браузере. С помощью следующего кода я получаю оба видео на одном компьютере, но не на другом, что означает, что один компьютер передает потоковое видео на удаленный компьютер, а другой не передает свое видео первому узлу. В этой программе я использую один объект однорангового соединения. Может ли кто-нибудь сказать мне, что здесь происходит не так.

    var constraints = { audio: true, video: true };    
    var configuration = { iceServers:[{
                            urls: "stun:stun.services.mozilla.com",
                            username: "[email protected]",
                            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);      
        });
    }

person Satya Narayana    schedule 05.08.2016    source источник


Ответы (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))

Надеюсь, это поможет.

person jib    schedule 06.08.2016
comment
Я также настоятельно рекомендую adapter.js, официальный полифил WebRTC, для обработки изменений браузера. Это немного упростило бы этот код. - person jib; 06.08.2016
comment
Я попробовал приведенные выше предложения, но не смог получить результат. У меня есть еще одно сомнение. Я использую один объект PeerConnection для соединения между Peer A и Peer B. Peer A и Peer B будут прослушивать свои соответствующие порты. Теперь, могу ли я использовать это единственное соединение для передачи видеопотоков от узла A к узлу B и наоборот, или мне нужно создать еще один объект PeerConnection для передачи видеопотока от узла B к узлу A. Теперь я хочу сделать следующее: нам нужно два объекта PeerConnection для передачи видео туда и обратно в P2P-видеосоединении с использованием WebRTC или достаточно одного. - person Satya Narayana; 08.08.2016
comment
Когда я использую один объект PeerConnection, как описано в приведенном выше коде, я вызываю onaddstream перед созданием предложения или ответа. по-прежнему оба видео отображаются только на одном узле, но на другом узле я не получаю удаленный поток. не могу понять, что здесь не так. - person Satya Narayana; 08.08.2016
comment
Моя проблема решена, и мои вышеуказанные сомнения также развеяны. После создания объекта однорангового соединения и настройки его обработчиков, таких как onaddstream, onnegotiationneeded, onicecandidate, отправьте предложение только тогда, когда вы уверены, что другой одноранговый узел находится в сети и готов. для этого я использую сигнальный сервер, который поддерживает переменную, которая будет установлена ​​первым узлом. тогда второй узел уверен, что другой узел уже находится в сети, поэтому он может отправить сообщение с предложением. Таким образом, я использую сигнальный сервер для координации обмена предложениями и ответами. я вызываю метод addStream перед отправкой предложения/ответа. - person Satya Narayana; 08.08.2016