本文介绍了参数为 null 的 addIceCandidate 导致错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

限时删除!!

我正在尝试学习 WebRTC,我已经在同一页面中连接了两个 RTCPeerConnection,现在我正在尝试将它们分成两个单独的页面并连接它们.但是,在编写代码并交换报价和​​答案后,我注意到initiator.html 上的 addIceCandidate() 将始终使用空参数抛出此

队列中的 addIceCandidate 出错:TypeError:无法在RTCPeerConnection"上执行addIceCandidate":sdpMid 和 sdpMLineIndex 的候选缺失值在 processCandidateQueue (initiator.html:69)

经过一些阅读,我了解到 null 用于表示 ICE Candidate 收集完成和示例:https://webrtc.github.io/samples/src/content/peerconnection/pc1/收集完成时,还会执行参数为 null 的addIceCandidate".但我不明白为什么我现在会看到我看到的错误.

我尝试过的:

  1. 我曾尝试编写一个检查,如果候选为空,则跳过 addIceCandidate.
  2. 将所有连接逻辑放在较少的按钮中以减少函数调用之间的延迟
  3. 为每个页面添加adapter-latest.js

结果:

  1. 发起方连接状态为失败",接收方连接状态为新建".无法流式传输到接收器页面.
  2. 抛出同样的错误
  3. 错误消失了,但连接仍然失败

启动器.html

<html lang="zh-cn"><头><title>第一个 WebRTC 项目</title><link href="common.css" rel="stylesheet"/><身体><div class="log-display"></div><div class="func-list">发起主机<div class="func"><button onclick="onPrepareMedia(this)">准备媒体</button><video class="dump"></video>

<div class="func"><button onclick="onCreatePeerConnection(this)">onCreatePeerConnection()</button><textarea class="dump"></textarea>

<div class="func"><button onclick="onCreateOffer(this)">onCreateOffer()</button><textarea class="dump"></textarea>

<div class="func"><button onclick="onSetLocalDescription(this)">onSetLocalDescription()</button><textarea class="dump"></textarea>

<div class="func"><button onclick="onSetRemoteDescription(this, answerReceived)">onSetRemoteDescription()//手动设置 answerReceived 变量</button><textarea class="dump"></textarea>

<script src="common.js"></script><脚本>localStorage.removeItem("FirstWebRTC_offer");localStorage.removeItem("FirstWebRTC_answer");var 约束 = { video: true, audio: true };var 流 = 空;var peerConn = null;var offer = null, offerReceived = null;var answer = null, answerReceived = null;const offerOptions = {offerToReceiveAudio: 1,offerToReceiveVideo: 1};候选队列 = [];var onIceCandidate = 异步函数(e){window.log("onIceCandidate", e);如果(peerConn.remoteDescription){var rslt = e.candidate &&await peerConn.addIceCandidate(e.candidate).catch(e => onError("addIceCandidate", e));} 别的 {候选队列.推(e.candidate);}window.log(JSON.stringify(rslt));};var onIceConnectionStateChange = function(e) {window.log("onIceConnectionStateChange", e);};var onNegotiationNeeded = function(e) {console.log("-----", e);}var processCandidateQueue = 异步函数() {for(var i 在候选队列中){var 候选 = 候选队列 [i];await peerConn.addIceCandidate(candidate).catch(e => onError("addIceCandidate from queue", e));}}异步函数 onPrepareMedia(e) {流 = 等待 navigator.mediaDevices.getUserMedia(constraints);e.parentElement.children[1].value = dumpProperty(stream)视频 = e.parentElement.children[1];video.srcObject = 流;视频播放();}函数 onCreatePeerConnection(e) {peerConn = new RTCPeerConnection({});//设置 ICE 事件处理程序peerConn.onicecandidate = onIceCandidate;peerConn.oniceconnectionstatechange = onIceConnectionStateChange;peerConn.onnegotiationneeded = onNegotiationNeeded//添加要传输的轨道stream.getTracks().forEach(track => peerConn.addTrack(track, stream));e.parentElement.children[1].value = dumpProperty(peerConn)}异步函数 onCreateOffer(e) {offer = await peerConn.createOffer(offerOptions)localStorage.setItem("FirstWebRTC_offer", JSON.stringify(offer))e.parentElement.children[1].value = dumpProperty(offer)}异步函数 onSetLocalDescription(e) {var rslt = 等待 peerConn.setLocalDescription(offer)e.parentElement.children[1].value = dumpProperty(rslt)}异步函数 onSetRemoteDescription(e) {answerReceived = JSON.parse(localStorage.getItem("FirstWebRTC_answer"));rslt = 等待 peerConn.setRemoteDescription(answerReceived)e.parentElement.children[1].value = dumpProperty(rslt)进程候选队列();}</html>

接收器.html

<html lang="zh-cn"><头><title>第一个 WebRTC 项目</title><link href="common.css" rel="stylesheet"/><身体><div class="log-display"></div><div class="func-list">接收主机<div class="func"><按钮>接收到的视频</按钮><video class="dump"></video>

<div class="func"><button onclick="onCreatePeerConnection(this)">onCreatePeerConnection()</button><textarea class="dump"></textarea>

<div class="func"><button onclick="onSetRemoteDescription(this)">onSetRemoteDescription()</button><textarea class="dump"></textarea>

<div class="func"><button onclick="onCreateAnswer(this)">onCreateAnswer()</button><textarea class="dump"></textarea>

<div class="func"><button onclick="onSetLocalDescription(this)">onSetLocalDescription()</button><textarea class="dump"></textarea>

<script src="common.js"></script><脚本>localStorage.removeItem("FirstWebRTC_offer");localStorage.removeItem("FirstWebRTC_answer");var 约束 = { video: true, audio: true };var 流 = 空;var peerConn = null;var offer = null, offerReceived = null;var answer = null, answerReceived = null;const offerOptions = {offerToReceiveAudio: 1,offerToReceiveVideo: 1};var onTrack = 函数(e){控制台日志(e);video = document.querySelector("video")if (video.srcObject !== e.streams[0]) {video.srcObject = e.streams[0];视频播放();console.log('接收并播放远程流');}}var onIceCandidate = 异步函数(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);};函数 onCreatePeerConnection(e) {peerConn = new RTCPeerConnection({});//设置 ICE 事件处理程序peerConn.onicecandidate = onIceCandidate;peerConn.oniceconnectionstatechange = onIceConnectionStateChange;peerConn.ontrack = onTrack;e.parentElement.children[1].value = dumpProperty(peerConn);}异步函数 onSetRemoteDescription(e) {offerReceived = JSON.parse(localStorage.getItem("FirstWebRTC_offer"));rslt = 等待 peerConn.setRemoteDescription(offerReceived);e.parentElement.children[1].value = dumpProperty(rslt);}异步函数 onCreateAnswer(e) {answer = await peerConn.createAnswer(offerReceived);localStorage.setItem("FirstWebRTC_answer", JSON.stringify(answer));e.parentElement.children[1].value = dumpProperty(answer);}异步函数 onSetLocalDescription(e) {var rslt = await peerConn.setLocalDescription(answer);e.parentElement.children[1].value = dumpProperty(rslt);}</html>

common.js

function dumpProperty(obj, noJSON) {var 输出 = JSON.stringify(obj);if(output == "{}" || noJSON) {输出 = ""for(obj 中的 var 属性){输出 += 属性 + ': ' + obj[property]+';\n';}}返回输出;}函数 onError(name, e) {console.warn("错误在" + name + ": ", e);}window.log = function(str, obj) {var logDisplay = document.getElementsByClassName('log-display')[0];如果(日志显示){var newLog = document.createElement("div");newLog.innerText = str + ":" + dumpProperty(obj);logDisplay.appendChild(newLog);}console.log(str, obj);}

common.css

.connection-flow-diagram {显示:弹性;文本对齐:居中;}.func-列表{显示:弹性;弹性方向:列;flex-wrap: 包裹;justify-content: 空间环绕;宽度:50%;左边距:自动;右边距:自动;文本对齐:居中;}.func {填充:1rem;显示:弹性;弹性方向:列;边框:1px 黑色虚线;}.func 按钮 {}.func .dump {高度:180px;}.log-显示{位置:固定;左:0;顶部:0;宽度:100vw;高度:100vh;指针事件:无;颜色:RGBA(0,0,0,0.4);}
解决方案

这是因为您的浏览器不符合规范.addIceCandidate(null)最新规范中有效,与 addIceCandidate()addIceCandidate({}) 没有区别.它们都从远端发出候选结束的信号.

WebRTC 示例 有效,因为它们使用 adapter.js,它在旧浏览器上填充了正确的规范行为.

I am trying to learn WebRTC, I had achieved connecting two RTCPeerConnection in same page and I am now attempting to separate them into two separate pages and connects them.However, after code are written and exchanged offer and answer, I noticed addIceCandidate() on initiator.html will always throw this with null argument

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)

After some reading, I learnt null is used to indicate ICE Candidate gathering finishes and example here: https://webrtc.github.io/samples/src/content/peerconnection/pc1/Also executes "addIceCandidate" with argument null when gathering finishes.But I do not understand why I am seeing the error I see at this moment.

What I had tried:

  1. I had tried to write a check such that if candidate is null, skip addIceCandidate.
  2. Place all connection logic in less buttons to reduce delay between function calls
  3. Add adapter-latest.js to each page

Result:

  1. Initiator connection state is "fail", receiver connection state is "new". Failed to stream to receiver page.
  2. Same error was thrown
  3. Error is gone, but connection still fails

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);
}
解决方案

It's because your browser is not up to spec.addIceCandidate(null) is valid in the latest spec, and indistinguishable from addIceCandidate() and addIceCandidate({}). They all signal end-of-candidates from the remote end.

The WebRTC samples work because they use adapter.js, which polyfills the correct spec behavior on older browsers.

这篇关于参数为 null 的 addIceCandidate 导致错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

1403页,肝出来的..

09-08 06:02