我正在运行一些测试代码,用于尝试学习 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/