addIceCandidate с нулевым аргументом приводит к ошибке - PullRequest
2 голосов
/ 25 апреля 2019

Я пытаюсь изучить WebRTC, мне удалось соединить два RTCPeerConnection на одной странице, и сейчас я пытаюсь разделить их на две отдельные страницы и соединить их.Однако после написания и обмена предложениями и ответами я заметил, что addIceCandidate () в initiator.html всегда будет выдавать это значение с нулевым аргументом

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)

После некоторого чтения я выучил нульиспользуется для указания окончания сбора кандидатов ICE, и пример здесь: https://webrtc.github.io/samples/src/content/peerconnection/pc1/ Также выполняет «addIceCandidate» с нулевым аргументом при завершении сбора.Но я не понимаю, почему я вижу ошибку, которую вижу в данный момент.

То, что я пытался:

  1. Я пытался выписать проверку таким образом, чтобы, если кандидат был нулевым, пропустите addIceCandidate.
  2. Поместите всю логику подключения в меньшее количество кнопок, чтобы уменьшить задержку между вызовами функций
  3. Добавить adaptor-latest.js на каждую страницу

Результат:

  1. Состояние подключения инициатора - «сбой», состояние подключения приемника - «новый».Не удалось выполнить потоковую передачу на страницу получателя.
  2. Произошла та же ошибка
  3. Ошибка исчезла, но соединение по-прежнему не удается

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>

receive.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);
}

Ответы [ 2 ]

1 голос
/ 28 апреля 2019

Почему предоставление addIceCandidate со значением NULL приводит к ошибке, в то время как пример кода работает нормально?

Это потому, что ваш браузер не соответствует спецификации. addIceCandidate(null) действителен в последней спецификации и неотличим от addIceCandidate() и addIceCandidate({}). Все они сигнализируют об окончании кандидатов с удаленного конца.

Примеры WebRTC работают, потому что они используют adapter.js , который обеспечивает правильное поведение спецификации в старых браузерах.

0 голосов
/ 26 апреля 2019

После некоторого чтения я понял, почему мой код не работает. Он содержит роковой недостаток, не связанный с названием этого вопроса.

Сначала ответьте на вопрос названия. В "Почему предоставление addIceCandidate () со значением NULL приведет к ошибке?" A: Это потому, что я прочитал устаревшую статью о WebRTC, где некоторое время назад addIceCandidate () смог принять нулевое значение, и он будет счастлив. Однако по состоянию на 25 апреля 2019 года это уже не так. Вместо этого с текущей реализацией:

Если свойство кандидата события равно нулю, сбор ICE завершен.

MDN - Событие: подключение RTCPeer .onicecandidate

Поэтому, чтобы правильно обработать этот случай, мне нужно проверить на нулевого кандидата

onIceCandidateHandler(e)
    if e.candidate is not null
        signalingMedium.sendToRemote(e.candidate)
    else
        do nothing

Именно поэтому, когда я добавил adapter-latest.js, ошибка исчезла; он заменяет addIceCandidate () для защиты от нулевого кандидата

Во-вторых, я упоминал, что ошибка исчезла, когда был добавлен адаптер-latest.js. Это потому, что я делал неправильные сигналы.

Вот описание ледового события от MDN

Это происходит всякий раз, когда локальному агенту 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 с исправленным кодом, как только устраню проблему.

...