我正在尝试学习WebRTC,我已经在同一页面中实现了两个RTCPeerConnection的连接,现在我试图将它们分成两个单独的页面并进行连接。 但是,在编写代码并交换要约和答案之后,我注意到initiator.html上的addIceCandidate()始终会使用null参数抛出该错误
Error at addIceCandidate from queue: TypeError: Failed to execute 'addIceCandidate' on 'RTCPeerConnection': Candidate missing values for both sdpMid and sdpMLineIndex
at processCandidateQueue (initiator.html:69)
经过一番阅读,我了解到null用来表示ICE候选者的收集完成和此处的示例:https://webrtc.github.io/samples/src/content/peerconnection/pc1/ 收集完成时,还使用参数null执行“ addIceCandidate”。 但是我不明白为什么我现在看到我看到的错误。
我尝试过的:
结果:
initiator.html
<!doctype html>
<html lang="en">
<head>
<title>First WebRTC Project</title>
<link href="common.css" rel="stylesheet" />
</head>
<body>
<div class="log-display"></div>
<div class="func-list">
Initiating host
<div class="func">
<button onclick="onPrepareMedia(this)">Prepare media</button>
<video class="dump"></video>
</div>
<div class="func">
<button onclick="onCreatePeerConnection(this)">onCreatePeerConnection()</button>
<textarea class="dump"></textarea>
</div>
<div class="func">
<button onclick="onCreateOffer(this)">onCreateOffer()</button>
<textarea class="dump"></textarea>
</div>
<div class="func">
<button onclick="onSetLocalDescription(this)">onSetLocalDescription()</button>
<textarea class="dump"></textarea>
</div>
<div class="func">
<button onclick="onSetRemoteDescription(this, answerReceived)">onSetRemoteDescription() // set answerReceived variable manually</button>
<textarea class="dump"></textarea>
</div>
</div>
<script src="common.js"></script>
<script>
localStorage.removeItem("FirstWebRTC_offer");
localStorage.removeItem("FirstWebRTC_answer");
var constraints = { video: true, audio: true };
var stream = null;
var peerConn = null;
var offer = null, offerReceived = null;
var answer = null, answerReceived = null;
const offerOptions = {
offerToReceiveAudio: 1,
offerToReceiveVideo: 1
};
candidateQueue = [];
var onIceCandidate = async function(e) {
window.log("onIceCandidate", e);
if(peerConn.remoteDescription) {
var rslt = e.candidate && await peerConn.addIceCandidate(e.candidate).catch(e => onError("addIceCandidate", e));
} else {
candidateQueue.push(e.candidate);
}
window.log(JSON.stringify(rslt));
};
var onIceConnectionStateChange = function(e) {
window.log("onIceConnectionStateChange", e);
};
var onNegotiationNeeded = function(e) {
console.log("-----", e);
}
var processCandidateQueue = async function() {
for(var i in candidateQueue) {
var candidate = candidateQueue[i];
await peerConn.addIceCandidate(candidate).catch(e => onError("addIceCandidate from queue", e));
}
}
async function onPrepareMedia(e) {
stream = await navigator.mediaDevices.getUserMedia(constraints);
e.parentElement.children[1].value = dumpProperty(stream)
video = e.parentElement.children[1];
video.srcObject = stream;
video.play();
}
function onCreatePeerConnection(e) {
peerConn = new RTCPeerConnection({});
// Setup ICE event handlers
peerConn.onicecandidate = onIceCandidate;
peerConn.oniceconnectionstatechange = onIceConnectionStateChange;
peerConn.onnegotiationneeded = onNegotiationNeeded
// Add tracks to be transmitted
stream.getTracks().forEach(track => peerConn.addTrack(track, stream));
e.parentElement.children[1].value = dumpProperty(peerConn)
}
async function onCreateOffer(e) {
offer = await peerConn.createOffer(offerOptions)
localStorage.setItem("FirstWebRTC_offer", JSON.stringify(offer))
e.parentElement.children[1].value = dumpProperty(offer)
}
async function onSetLocalDescription(e) {
var rslt = await peerConn.setLocalDescription(offer)
e.parentElement.children[1].value = dumpProperty(rslt)
}
async function onSetRemoteDescription(e) {
answerReceived = JSON.parse(localStorage.getItem("FirstWebRTC_answer"));
rslt = await peerConn.setRemoteDescription(answerReceived)
e.parentElement.children[1].value = dumpProperty(rslt)
processCandidateQueue();
}
</script>
</body>
</html>
receiver.html
<!doctype html>
<html lang="en">
<head>
<title>First WebRTC Project</title>
<link href="common.css" rel="stylesheet" />
</head>
<body>
<div class="log-display"></div>
<div class="func-list">
Receiving host
<div class="func">
<button >Received video</button>
<video class="dump"></video>
</div>
<div class="func">
<button onclick="onCreatePeerConnection(this)">onCreatePeerConnection()</button>
<textarea class="dump"></textarea>
</div>
<div class="func">
<button onclick="onSetRemoteDescription(this)">onSetRemoteDescription()</button>
<textarea class="dump"></textarea>
</div>
<div class="func">
<button onclick="onCreateAnswer(this)">onCreateAnswer()</button>
<textarea class="dump"></textarea>
</div>
<div class="func">
<button onclick="onSetLocalDescription(this)">onSetLocalDescription()</button>
<textarea class="dump"></textarea>
</div>
</div>
<script src="common.js"></script>
<script>
localStorage.removeItem("FirstWebRTC_offer");
localStorage.removeItem("FirstWebRTC_answer");
var constraints = { video: true, audio: true };
var stream = null;
var peerConn = null;
var offer = null, offerReceived = null;
var answer = null, answerReceived = null;
const offerOptions = {
offerToReceiveAudio: 1,
offerToReceiveVideo: 1
};
var onTrack = function(e) {
console.log(e);
video = document.querySelector("video")
if (video.srcObject !== e.streams[0]) {
video.srcObject = e.streams[0];
video.play();
console.log('received and playing remote stream');
}
}
var onIceCandidate = async function(e) {
window.log("onIceCandidate", e);
var rslt = e.candidate && await peerConn.addIceCandidate(e.candidate).catch(e => onError("addIceCandidate", e));
window.log(JSON.stringify(rslt));
};
var onIceConnectionStateChange = function(e) {
window.log("onIceConnectionStateChange", e);
};
function onCreatePeerConnection(e) {
peerConn = new RTCPeerConnection({});
// Setup ICE event handlers
peerConn.onicecandidate = onIceCandidate;
peerConn.oniceconnectionstatechange = onIceConnectionStateChange;
peerConn.ontrack = onTrack;
e.parentElement.children[1].value = dumpProperty(peerConn);
}
async function onSetRemoteDescription(e) {
offerReceived = JSON.parse(localStorage.getItem("FirstWebRTC_offer"));
rslt = await peerConn.setRemoteDescription(offerReceived);
e.parentElement.children[1].value = dumpProperty(rslt);
}
async function onCreateAnswer(e) {
answer = await peerConn.createAnswer(offerReceived);
localStorage.setItem("FirstWebRTC_answer", JSON.stringify(answer));
e.parentElement.children[1].value = dumpProperty(answer);
}
async function onSetLocalDescription(e) {
var rslt = await peerConn.setLocalDescription(answer);
e.parentElement.children[1].value = dumpProperty(rslt);
}
</script>
</body>
</html>
common.js
function dumpProperty(obj, noJSON) {
var output = JSON.stringify(obj);
if(output == "{}" || noJSON) {
output = ""
for (var property in obj) {
output += property + ': ' + obj[property]+';\n';
}
}
return output;
}
function onError(name, e) {
console.warn("Error at " + name + ": ", e);
}
window.log = function(str, obj) {
var logDisplay = document.getElementsByClassName('log-display')[0];
if(logDisplay) {
var newLog = document.createElement("div");
newLog.innerText = str + " : " + dumpProperty(obj);
logDisplay.appendChild(newLog);
}
console.log(str, obj);
}
common.css
.connection-flow-diagram {
display: flex;
text-align: center;
}
.func-list {
display: flex;
flex-direction: column;
flex-wrap: wrap;
justify-content: space-around;
width: 50%;
margin-left: auto;
margin-right: auto;
text-align: center;
}
.func {
padding: 1rem;
display: flex;
flex-direction: column;
border: 1px dashed black;
}
.func button {
}
.func .dump {
height: 180px;
}
.log-display {
position: fixed;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
pointer-events: none;
color: rgba(0,0,0,0.4);
}
答案 0 :(得分:1)
为什么在示例代码运行正常的情况下为addIceCandidate提供null会导致错误?
这是因为您的浏览器不符合规范。
addIceCandidate(null)
在latest spec中有效,与addIceCandidate()
和addIceCandidate({})
不可区分。它们都从远端发出候选信号。
WebRTC samples之所以起作用,是因为它们使用了adapter.js,从而填补了旧版浏览器的正确规范行为。
答案 1 :(得分:0)
经过一番阅读,我弄清楚了为什么我的代码不起作用。它包含与该问题的标题无关的致命缺陷。
首先,回答标题问题。 问:“为什么为addIceCandidate()提供null会导致错误?” 答:这是因为我阅读了有关WebRTC的过时文章,过去一段时间addIceCandidate()可以采用null值,这将是很高兴的。但是,截至2019年4月25日,情况已不再如此。相反,使用当前实现:
如果事件的侯选属性为null,则表明ICE收集已完成。
MDN - Event: RTCPeerConnection.onicecandidate
因此,要正确处理这种情况,我需要测试空候选者
onIceCandidateHandler(e)
if e.candidate is not null
signalingMedium.sendToRemote(e.candidate)
else
do nothing
这就是为什么当我添加adapter-latest.js时,错误消失了的原因。它替换了addIceCandidate()来防止空候选
第二,我提到虽然添加adapter-latest.js时错误消失了。 这是因为我做错了信号。
这是来自MDN的icecandidate事件的描述
只要本地ICE代理需要通过信令服务器将消息传递给另一个对等方,就会发生这种情况。
...
只需实施此方法即可使用您选择的任何消息传递技术将ICE候选人发送给远程对等方。
在我自己的代码中,我正在向本地对等连接添加候选对象(这是错误的)。
var onIceCandidate = async function(e) {
window.log("onIceCandidate", e);
if(peerConn.remoteDescription) {
var rslt = e.candidate && await peerConn.addIceCandidate(e.candidate).catch(e => onError("addIceCandidate", e));
} else {
candidateQueue.push(e.candidate);
}
window.log(JSON.stringify(rslt));
};
由于我实际上是在连接自己,所以连接总是失败。
一旦解决此问题,我将为jsFiddle提供更正的代码。