WebRTC 在本地连接上连接,但在 Internet 上失败
WebRTC connects on local connection, but fails over internet
我有一些测试代码 运行ning,我用它来尝试学习 WebRTC 的基础知识。此测试代码可在 LAN 上运行,但不能在 Internet 上运行,即使我使用 TURN 服务器(一侧显示状态 "checking",另一侧显示状态 "failed")。我可以看到 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>
如果你没有看到像 relay 或 server-reflexive 这样的候选对象,那么你可以先尝试使用 wireshark 等捕获 tcpdump,看看是否有任何传出数据包到你的 STUN/TURN 服务器,如果通过过滤为 STUN 没有传出数据包,那么您的配置可能无法正常工作。如果您看到 STUN/TURN 流量,那么无论错误是什么,您都可能会从服务器收到一些响应,这些响应可能有身份验证错误或其他一些错误,但根据响应类型,您可以确定 STUN/TRUN 的问题。如果您看到来自 STUN/TURN 的成功响应,那么您可以查看您是否在 SDP 或您的信令 offer/answer 消息中正确地包含了候选人以向另一端通告候选人。
SDP 将只包含到那个时间点收集的候选人,所以除非你在 pc.onicecandidate
回调中等待 null
候选人,否则你不会得到所有候选人方式(您似乎在 createLocalOffer
中等待,但不在 handleOffer
中,我认为问题出在这里)。
也就是说,我不推荐这种方法,因为 ICE 代理可能需要长达 20 秒的时间来耗尽所有路径(例如,在具有 VPN 的系统上经常发生)。相反,我强烈建议将候选人明确地派往另一方。即 Trickle ICE。
我有一些测试代码 运行ning,我用它来尝试学习 WebRTC 的基础知识。此测试代码可在 LAN 上运行,但不能在 Internet 上运行,即使我使用 TURN 服务器(一侧显示状态 "checking",另一侧显示状态 "failed")。我可以看到 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>
如果你没有看到像 relay 或 server-reflexive 这样的候选对象,那么你可以先尝试使用 wireshark 等捕获 tcpdump,看看是否有任何传出数据包到你的 STUN/TURN 服务器,如果通过过滤为 STUN 没有传出数据包,那么您的配置可能无法正常工作。如果您看到 STUN/TURN 流量,那么无论错误是什么,您都可能会从服务器收到一些响应,这些响应可能有身份验证错误或其他一些错误,但根据响应类型,您可以确定 STUN/TRUN 的问题。如果您看到来自 STUN/TURN 的成功响应,那么您可以查看您是否在 SDP 或您的信令 offer/answer 消息中正确地包含了候选人以向另一端通告候选人。
SDP 将只包含到那个时间点收集的候选人,所以除非你在 pc.onicecandidate
回调中等待 null
候选人,否则你不会得到所有候选人方式(您似乎在 createLocalOffer
中等待,但不在 handleOffer
中,我认为问题出在这里)。
也就是说,我不推荐这种方法,因为 ICE 代理可能需要长达 20 秒的时间来耗尽所有路径(例如,在具有 VPN 的系统上经常发生)。相反,我强烈建议将候选人明确地派往另一方。即 Trickle ICE。