如何使用offer / answer从两个peerconnections交换流

时间:2015-04-08 10:03:12

标签: javascript video webrtc sdp

我正在尝试设置两个对等连接交换视频的视频聊天。这在创建数据通道后发生。所以这是事件的过程:

  1. 提供者创建数据渠道,提供者创建并提供,回答者创建答案。到现在为止还挺好。我们有一个数据通道。
  2. 提供者通过 getUserMedia 获取视频流并将其添加到对等连接,然后提供者的 onnegotiation 事件 fires,提供者会创建新的优惠,而 answerwerer 会回复。还是好的。 提供者正在流式播放。
  3. 回答者通过 getUserMedia 获取视频流并将其添加到对等连接,提供者创建新优惠,而回答者< / strong>回答答案。还好。 回答者也是流媒体。
  4. 但是,如果我切换第2步和第3步(所以 answerer 首先开始流式传输),那么事情就会开始出错。双方只有在完成第1,3和2步之后才开始流式传输。

    我很确定它与SDP提供和答案的顺序有关。

    当我让回答者在有 onnegotiationneeded 事件时创建新要约时,行为会有所不同,但仍然不稳定。

    我现在对如何添加优惠和答案毫无头绪。

    以下是代码:

    navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
    PeerConnection = window.mozRTCPeerConnection || window.webkitRTCPeerConnection || window.RTCPeerConnection;
    IceCandidate = window.mozRTCIceCandidate || window.RTCIceCandidate || window.RTCIceCandidate;
    SessionDescription =  window.mozRTCSessionDescription || window.webkitRTCSessionDescription || window.RTCSessionDescription;
    
    var videoOfferer = document.getElementById('videoOfferer');
    var videoAnswerer = document.getElementById('videoAnswerer');
    var buttonOfferer = document.getElementById('buttonOfferer');
    var buttonAnswerer = document.getElementById('buttonAnswerer');
    
    var servers = {
        iceServers: [
            {url: "stun:23.21.150.121"},
            {url: "stun:stun.1.google.com:19302"}
        ]
    };
    
    var offerer = new PeerConnection(servers), answerer = new PeerConnection(servers);
    var channelOfferer = null, channelAnswerer = null;
    
    offerer.onicecandidate = function(e) {
        if(e.candidate == null) return;
        answerer.addIceCandidate(new IceCandidate(e.candidate), function(){}, error);
    };
    
    offerer.onaddstream = function(e) {
        videoOfferer.src = URL.createObjectURL(e.stream);
        videoOfferer.play();
    };
    
    answerer.onicecandidate = function(e) {
       if(e.candidate == null) return;
        offerer.addIceCandidate(new IceCandidate(e.candidate), function(){}, error);
    };
    
    answerer.onaddstream = function(e) {
        videoAnswerer.src = URL.createObjectURL(e.stream);
        videoAnswerer.play();
    };
    
    function offerCreated(sdp) {
        console.log('offer');
        offerer.setLocalDescription(new SessionDescription(sdp), function() {
            answerer.setRemoteDescription(new SessionDescription(sdp), function() {
                answerer.createAnswer(answerCreated, error);
            }, error);
        }, error);
    }
    
    function answerCreated(sdp) {
        console.log('answer');
        answerer.setLocalDescription(new SessionDescription(sdp), function() {
        }, error);
        offerer.setRemoteDescription(new SessionDescription(sdp), function() {
        }, error);
    }
    
    function error() {}
    
    buttonOfferer.addEventListener('click', function() {
        navigator.getUserMedia({audio: true, video: true}, function(stream) {
            offerer.addStream(stream);
        }, function(){});
    });
    
    buttonAnswerer.addEventListener('click', function() {
        navigator.getUserMedia({audio: true, video: true}, function(stream) {
            answerer.addStream(stream);
        }, function(){});
    });
    
    channelOfferer = offerer.createDataChannel('channel', {reliable: true});
    
    offerer.createOffer(offerCreated, error);
    
    answerer.ondatachannel = function(e) {
        channelOfferer = e.channel;
        channelOfferer.onmessage = function(e) {
            console.log(e.data);
        };
        channelOfferer.onmessage = function(e) {
            console.log(e.data);
        };
    
          // these are added later
        offerer.onnegotiationneeded = function() {
            offerer.createOffer(offerCreated, error);
        };
    
        answerer.onnegotiationneeded = function() {
            offerer.createOffer(offerCreated, error);
        };
    };
    

1 个答案:

答案 0 :(得分:3)

我认为问题出现在第3步中,当应答者添加视频时,您可以启动提议者。在一个真正的远程呼叫中,提议者怎么会知道这样做呢?

据我所知,当回答者需要重新协商时,角色会被有效地逆转,因为出于重新谈判的目的:重新谈判者充当提议者,而非重新谈判者充当提议者。回答者。

换句话说:对pc.onnegotiationneeded的回复始终是:

  1. createOffer()
  2. 然后setLocalDescription(description)
  3. 然后将pc.localDescription发送到另一方
  4. 无论哪一方。

    我不是SDP的权威,所以我不确定这是正确的做法,但是the examples in the spec至少向我建议上面的步骤是正确的,我让它工作了这样。

    我已经在this Firefox jsfiddle中对此进行了测试,但似乎有效。使用小提琴的说明:

    1. 没有服务器(因为它是小提琴),所以按下Offer按钮并复制优惠。
    2. 将优惠粘贴到另一个标签或另一台机器上同一个小提琴中的同一位置。
    3. 按ENTER,然后复制你得到的答案并将其粘贴回第一个小提琴。
    4. 您现在已连接两个数据通道:一个用于聊天,另一个用于信号。
    5. 现在按两端addTrack,视频应显示在另一端。
    6. 按另一个方向的addTrack,您应该双向播放视频。
    7. 这会产生眩光吗?我敢肯定,可能有更好的方法来解决这个问题,但这似乎对我有用。