javascript - WebRTC 在本地连接上连接,但在 Internet 上失败

标签 javascript webrtc

我正在运行一些测试代码,用于尝试学习 WebRTC 的基础知识。此测试代码可在 LAN 上运行,但不能在 Internet 上运行,即使我使用 TURN 服务器(一侧显示状态“正在检查”,另一侧显示状态“失败”)。我可以看到 SDP 中有冰候选项,所以我不需要显式发送它们(对吧?)。

这会向控制台写入大量调试信息,因此我可以知道我的信令服务器正在工作。我被卡住了 - 我需要在我的代码中做些什么不同的事情才能让它在互联网上工作?

顺便说一句,我在我的测试计算机之间运行了其他 WebRTC 演示脚本,它们确实有效(比如 opentokrtc.ocom)

<html>
<head>
<title>test</title>
<script type="text/javascript">
var curInvite = null;
//create an invitation to connect and post to signalling server
function CreateInvite(){
    //function to run upon receiving a response
    var postRespFunc = function(txt){
        console.log("Posted offer and received " + txt);
        var invite = txt;
        curInvite = invite;
        document.getElementById("inviteId").innerHTML = invite;
        //then poll for answer...
        var pollFunc = function(){
            GetRequest("answered?"+invite,function(txt){
                if(txt){
                    //assume it's the answer
                    handleAnswer(txt);
                }else{
                    //poll more
                    setTimeout(pollFunc,1000);
                }
            });
        };
        //start polling for answer
        setTimeout(pollFunc,1000);
    };
    //function to run after creating the WebRTC offer
    var postFunc = function(offer){
        PostRequest('offer','offer='+encodeURIComponent(offer), postRespFunc);
    }
    //create the offer
    createLocalOffer(postFunc);
}
function AnswerInvite(){
    var invite = document.getElementById("invitation").value;
    //can we create our local description BEFORE we get the remote desc?
    //reduce to one ajax call?
    GetRequest("accept?"+invite,function(txt){
        var answerPostedCallback = function(txt){
            console.log("answerPostedCallback",txt);
        }
        var answerCallback = function(answer){
            PostRequest("answer?"+invite,'answer='+encodeURIComponent(answer), answerPostedCallback);
        }
        handleOffer(txt, answerCallback);
        //then we're waiting for a data channel to be open...
    });
}

function PostRequest(postUrl, reqStr, callback){
    var req=new XMLHttpRequest();
    req.onload = function(){
        var strResp = req.responseText;
        if(callback) callback(strResp);
    }
    //var namevalue=encodeURIComponent(document.getElementById("test").value);
    //var parameters="name="+namevalue;
    req.open("POST", postUrl, true);
    req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    req.send(reqStr);
}
function GetRequest(getUrl, callback){
    var req=new XMLHttpRequest();
    req.onload = function(){
        var strResp = req.responseText;
        if(callback) callback(strResp);
    }
    //var namevalue=encodeURIComponent(document.getElementById("test").value);
    //var parameters="name="+namevalue;
    req.open("GET", getUrl, true);
    req.send();
}

/************ WebRTC stuff ****************/
var RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || 
                       window.webkitRTCPeerConnection || window.msRTCPeerConnection;
var RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription ||
                       window.webkitRTCSessionDescription || window.msRTCSessionDescription;

navigator.getUserMedia = navigator.getUserMedia || navigator.mozGetUserMedia ||
                       navigator.webkitGetUserMedia || navigator.msGetUserMedia;
//SEE http://olegh.ftp.sh/public-stun.txt                       
var cfg = {"iceServers":[
{url:'stun:stun.12connect.com:3478'},
{url:'stun:stun.12voip.com:3478'}
]};

cfg.iceServers = [{
url: 'turn:numb.viagenie.ca',
    credential: 'muazkh',
    username: 'webrtc@live.com'
}]

var con = { 'optional': [{'DtlsSrtpKeyAgreement': true}] };

var peerConnection = new RTCPeerConnection(cfg,con);
var dataChannel = null;

function initDataChannel(){
    dataChannel.onerror = function (error) {
            console.log("Data Channel Error:", error);
        };

        dataChannel.onmessage = function (event) {
            console.log("Got Data Channel Message:", event.data);
            var data = JSON.parse(event.data);
            document.getElementById("chat").innerHTML+= "RECD: " + data + "<br />";
        };

        dataChannel.onopen = function () {
            console.log('data channel open');
            alert("data channel open, ready to connect!");
        };

        dataChannel.onclose = function () {
            console.log("The Data Channel is Closed");
            peerConnection.close();
            alert("Disconnected!");
        };
}

//used when peerConnection is an answerer
peerConnection.ondatachannel = function (e) {
    dataChannel = e.channel || e; // Chrome sends event, FF sends raw channel
    initDataChannel();
    console.log("Received datachannel", arguments);
}
//to initiate a connection
function createLocalOffer(callback) {
        //create datachannel
        try {
        dataChannel = peerConnection.createDataChannel('test', {reliable:true});
        initDataChannel();
        console.log("Created datachannel (peerConnection)");
    } catch (e) { console.warn("No data channel (peerConnection)", e); }
        //set event handler
        peerConnection.onicecandidate = function (e) {
                console.log("ICE candidate (peerConnection)", e);
                if (e.candidate == null) {
                        console.log("ice candidate",peerConnection.localDescription);
                        callback(JSON.stringify(peerConnection.localDescription));
                }
        };
    peerConnection.createOffer(function (desc) {
        peerConnection.setLocalDescription(desc);
        console.log("created local offer", desc);
    }, function () {console.warn("Couldn't create offer");});
}

peerConnection.onconnection = function(e){
    console.log("peerConnection connected",e);
};

function onsignalingstatechange(state) {
    console.info('signaling state change:', state);
}

function oniceconnectionstatechange(state) {
    console.info('ice connection state change:', state);
    console.info('iceConnectionState: ', peerConnection.iceConnectionState);
}

function onicegatheringstatechange(state) {
    console.info('ice gathering state change:', state);
}

peerConnection.onsignalingstatechange = onsignalingstatechange;
peerConnection.oniceconnectionstatechange = oniceconnectionstatechange;
peerConnection.onicegatheringstatechange = onicegatheringstatechange;

//local handles answer from remote
function handleAnswer(answerJson) {
        var obj = JSON.parse(answerJson);
        var answerDesc = new RTCSessionDescription(obj);
    peerConnection.setRemoteDescription(answerDesc);
}

/* functions for remote side */

//handle offer from the initiator
function handleOffer(offerJson, callback) {
        var obj = JSON.parse(offerJson);
        var offerDesc = new RTCSessionDescription(obj);
    peerConnection.setRemoteDescription(offerDesc);
    //set event handler
        peerConnection.onicecandidate = function (e) {
                console.log("ICE candidate (peerConnection)", e);
                if (e.candidate == null) {
                        console.log("ice candidate",peerConnection.localDescription);
                }
        };
    peerConnection.createAnswer(function (answerDesc) {
        console.log("Created local answer: ", answerDesc);
        peerConnection.setLocalDescription(answerDesc);
        callback(JSON.stringify(answerDesc));
    }, function () { console.warn("No create answer"); });
}

function sendMessage() {
            var msg = document.getElementById("msg").value;
            document.getElementById("msg").value = null;
            document.getElementById("chat").innerHTML+= "SENT: " + msg + "<br />";
            var obj = {message: msg};
            dataChannel.send(JSON.stringify(msg));
    return false;
};

</script>
</script>
</head>
<body>
<p>test</p>
<p>
<div id="createWrapper">
<h4>create an invitiation</h4>
<button type="button" onclick="CreateInvite();">create invitation</button>
<h3 id="inviteId"></h3>
</div>
<div id="acceptWrapper">
<h4>or accept an inviation</h4>
<input id="invitation" type="text" name="invitation" />
<button type="button" onclick="AnswerInvite()">answer invitation</button>
</div>
<p>Once the data channel is open type your messages below</p>
<input type="text" id="msg" /><button type="button" onclick="sendMessage()">send</button>
<div id="chat"></div>
</body>
</html>

[编辑:这是工作代码,以防它对其他人有用。您仍然需要自己的信令服务器和工作的 STUN/TURN 服务器,但这对我理解基础知识很有帮助]

<html>
<head>
<title>test</title>
<script type="text/javascript">
var curInvite = null;
//create an invitation to connect and post to signalling server
function CreateInvite(){
  //function to run upon receiving a response
  var postRespFunc = function(txt){
    console.log("Posted offer and received " + txt);
    var invite = txt;
    curInvite = invite;
    document.getElementById("inviteId").innerHTML = invite;
    //then poll for answer...
    var pollFunc = function(){
      GetRequest("answered?"+invite,function(txt){
        if(txt){
          //assume it's the answer
          handleAnswer(txt);
        }else{
          //poll more
          setTimeout(pollFunc,1000);
        }
      });
    };
    //start polling for answer
    setTimeout(pollFunc,100);
  };
  //function to run after creating the WebRTC offer
  var postFunc = function(offer){
    PostRequest('offer','offer='+encodeURIComponent(offer), postRespFunc);
  }
  //create the offer
  createLocalOffer(postFunc);
}
function AnswerInvite(){
  var invite = document.getElementById("invitation").value;
  //can we create our local description BEFORE we get the remote desc?
  //reduce to one ajax call?
  GetRequest("accept?"+invite,function(txt){
    var answerPostedCallback = function(txt){
      console.log("answerPostedCallback",txt);
    }
    var answerCallback = function(answer){
      PostRequest("answer?"+invite,'answer='+encodeURIComponent(answer), answerPostedCallback);
    }
    handleOffer(txt, answerCallback);
    //then we're waiting for a data channel to be open...
  });
}

function PostRequest(postUrl, reqStr, callback){
  var req=new XMLHttpRequest();
  req.onload = function(){
    var strResp = req.responseText;
    if(callback) callback(strResp);
  }
  //var namevalue=encodeURIComponent(document.getElementById("test").value);
  //var parameters="name="+namevalue;
  req.open("POST", postUrl, true);
  req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  req.send(reqStr);
}
function GetRequest(getUrl, callback){
  var req=new XMLHttpRequest();
  req.onload = function(){
    var strResp = req.responseText;
    if(callback) callback(strResp);
  }
  //var namevalue=encodeURIComponent(document.getElementById("test").value);
  //var parameters="name="+namevalue;
  req.open("GET", getUrl, true);
  req.send();
}

/************ WebRTC stuff ****************/
var RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || 
                       window.webkitRTCPeerConnection || window.msRTCPeerConnection;
var RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription ||
                       window.webkitRTCSessionDescription || window.msRTCSessionDescription;

navigator.getUserMedia = navigator.getUserMedia || navigator.mozGetUserMedia ||
                       navigator.webkitGetUserMedia || navigator.msGetUserMedia;
//SEE http://olegh.ftp.sh/public-stun.txt                       
var cfg = {"iceServers":[
    {url: 'turn:numb.viagenie.ca',
        credential: 'muazkh',
        username: 'webrtc@live.com'
    }
]};

var con = { 'optional': [{'DtlsSrtpKeyAgreement': true}] };

var peerConnection = null;

function createPeer(){
  peerConnection = new RTCPeerConnection(cfg,con);

  //used when peerConnection is an answerer
  peerConnection.ondatachannel = function (e) {
      dataChannel = e.channel || e; // Chrome sends event, FF sends raw channel
      initDataChannel();
      console.log("Received datachannel", arguments);
  }

  peerConnection.onsignalingstatechange = onsignalingstatechange;
  peerConnection.oniceconnectionstatechange = oniceconnectionstatechange;
  peerConnection.onicegatheringstatechange = onicegatheringstatechange;

  peerConnection.onconnection = function(e){
    console.log("peerConnection connected",e);
  };
}

var dataChannel = null;

function initDataChannel(){
  dataChannel.onerror = function (error) {
      console.log("Data Channel Error:", error);
    };

    dataChannel.onmessage = function (event) {
      console.log("Got Data Channel Message:", event.data);
      var data = JSON.parse(event.data);
      document.getElementById("chat").innerHTML+= "RECD: " + data + "<br />";
    };

    dataChannel.onopen = function () {
      console.log('data channel open');
      alert("data channel open, ready to connect!");
    };

    dataChannel.onclose = function () {
      console.log("The Data Channel is Closed");
      peerConnection.close();
      alert("Disconnected!");
    };
}

//to initiate a connection
function createLocalOffer(callback) {
  createPeer();
    //create datachannel
    try {
        dataChannel = peerConnection.createDataChannel('test', {reliable:true});
        initDataChannel();
        console.log("Created datachannel (peerConnection)");
    } catch (e) { console.warn("No data channel (peerConnection)", e); }
    //set event handler
    peerConnection.onicecandidate = function (e) {
        console.log("ICE candidate (peerConnection)", e);
        if (e.candidate == null) {
            console.log("ice candidate",peerConnection.localDescription);
            callback(JSON.stringify(peerConnection.localDescription));
        }
    };
    peerConnection.createOffer(function (desc) {
        peerConnection.setLocalDescription(desc);
        console.log("created local offer", desc);
    }, function () {console.warn("Couldn't create offer");});
}


function onsignalingstatechange(state) {
    console.info('signaling state change:', state);
}

function oniceconnectionstatechange(state) {
    console.info('ice connection state change:', state);
    console.info('iceConnectionState: ', peerConnection.iceConnectionState);
}

function onicegatheringstatechange(state) {
    console.info('ice gathering state change:', state);
}

//local handles answer from remote
function handleAnswer(answerJson) {
    var obj = JSON.parse(answerJson);
    var answerDesc = new RTCSessionDescription(obj);
    peerConnection.setRemoteDescription(answerDesc);
}

/* functions for remote side */

//handle offer from the initiator
function handleOffer(offerJson, callback) {
    createPeer();
    var obj = JSON.parse(offerJson);
    var offerDesc = new RTCSessionDescription(obj);
    //set event handler
    peerConnection.onicecandidate = function (e) {
        console.log("ICE candidate (peerConnection)", e);
        if (e.candidate == null) {
          console.log("ice candidate",peerConnection.localDescription);
          callback(JSON.stringify(peerConnection.localDescription));
        }
    };
    peerConnection.setRemoteDescription(offerDesc);
    peerConnection.createAnswer(function (answerDesc) {
        console.log("Created local answer: ", answerDesc);
        peerConnection.setLocalDescription(answerDesc);
    }, function () { console.warn("No create answer"); });
}

function sendMessage() {
      var msg = document.getElementById("msg").value;
      document.getElementById("msg").value = null;
      document.getElementById("chat").innerHTML+= "SENT: " + msg + "<br />";
      var obj = {message: msg};
      dataChannel.send(JSON.stringify(msg));
    return false;
};

</script>
</script>
</head>
<body>
<p>test</p>
<p>
<div id="createWrapper">
<h4>create an invitiation</h4>
<button type="button" onclick="CreateInvite();">create invitation</button>
<h3 id="inviteId"></h3>
</div>
<div id="acceptWrapper">
<h4>or accept an inviation</h4>
<input id="invitation" type="text" name="invitation" />
<button type="button" onclick="AnswerInvite()">answer invitation</button>
</div>
<p>Once the data channel is open type your messages below</p>
<input type="text" id="msg" /><button type="button" onclick="sendMessage()">send</button>
<div id="chat"></div>
</body>
</html>

最佳答案

SDP 将仅包含截至该时间点已收集的候选者,因此除非您在 pc.onicecandidate 回调中等待 null 候选者,你不会以这种方式获得所有候选人(你似乎在等待你的 createLocalOffer,但不是在你的 handleOffer 中,我认为这是问题所在)。

也就是说,我不推荐这种方法,因为 ICE 代理可能需要长达 20 秒的时间来耗尽所有路径(例如,在具有 VPN 的系统上经常发生)。相反,我强烈建议将候选人明确地派往另一方。即 Trickle ICE。

关于javascript - WebRTC 在本地连接上连接,但在 Internet 上失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34217462/

相关文章:

javascript - 我可以通过 Web RTC 将视频从一个客户端流式传输到另一个客户端吗?

javascript - 运行 Javascript Asyn 以便页面不会阻塞

javascript - 有没有更干净的方法来停止初始加载时的 React 渲染?

port - 为 webRTC 使用特定端口

webrtc - 我们可以使用公共(public) STUN 服务器来创建我们的商业应用程序吗?

javascript - 是否可以将 WebRTC SDP 报价转换为答案?

javascript - new (getClass()) 是什么意思?

javascript - AngularJS:使用按钮链接到使用按钮上的 ng-click 指令构建的页面

javascript - 使用 JSON POST 请求

websocket - webRTC 文本聊天 - 项目、PeerConnection 和握手设置