参数为 null 的 addIceCandidate 导致错误 [英] addIceCandidate with argument null result in error

查看:54
本文介绍了参数为 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);}

解决方案

为什么在示例代码工作正常时提供 null 的 addIceCandidate 会导致错误?

这是因为您的浏览器不符合规范.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);
}

解决方案

Why does supplying addIceCandidate with null result in error while the example code works fine?

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 导致错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
前端开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆