Удаленный видеопоток не работает с WebRTC

EDIT: я написал подробный учебник, объясняющий, как построить простое видеочат-приложение, включая сервер сигнализации:

учебник: создайте свой собственный видеочат-приложение с HTML и JavaScript

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


Я пытаюсь заставить потоки работать через WebRTC и Websocket (nodejs-server). Насколько я вижу, рукопожатие через SDP работает, и Peerconnection установленный. Проблема - удаленный-видео не играет. КГД-атрибут объекта и автозапуск установлен, но он просто не будет играть. Может быть, я делаю что-то не так с Ice-кандидатами (они используются для медиа-потоковой передачи, верно?). Есть ли способ проверить, правильно ли настроен PeerConnection?

EDIT: возможно, я должен объяснить, как работает код

  1. при загрузке сайта устанавливается соединение с websocket-сервером, PeerConnection с помощью googles STUN-server создается и видео и аудио-потоки собираются и добавляются в PeerConnection

  2. когда один пользователь нажимает на кнопку "Создать предложение" -сообщение, содержащее его сеанс-описание (SDP) отправляется на сервер (client func sendOffer ()), который передает его другому пользователю

  3. другой пользователь получает сообщение и сохраняет SDP, который он получил

  4. если пользователь нажимает " принять предложение", SDP добавляется в RemoteDescription (func createAnswer ()), который затем отправляет ответ-сообщение (содержащее SDP ответившего пользователя) пользователю предложения

  5. на стороне предложения-пользователя выполняется func offerAccepted (), который добавляет SDP другого пользователя к его RemoteDesription.

Я не уверен, в какой именно момент вызываются обработчики icecandidate, но я думаю, что они должны работать, потому что я получаю оба журнала обе стороны.

вот мой код (это только для тестирования, поэтому даже если есть функция под названием broadcast, это означает, что только 2 пользователя могут быть на одном веб-сайте одновременно):

разметка индекса.HTML-код:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <style>
            #acceptOffer  {
                display: none;
            }
        </style>
    </head>
    <body>
        <h2>Chat</h2>
        <div>
            <textarea class="output" name="" id="" cols="30" rows="10"></textarea>
        </div>
        <button id="createOffer">create Offer</button>
        <button id="acceptOffer">accept Offer</button>

        <h2>My Stream</h2>
        <video id="myStream" autoplay src=""></video>
        <h2>Remote Stream</h2>
        <video id="remoteStream" autoplay src=""></video>

        <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
        <script src="websocketClient.js"></script>
</body>
</html>

вот сервер-код:

"use strict";

var webSocketsServerPort = 61122;

var webSocketServer = require('websocket').server,
http = require('http'),
clients = [];


var server = http.createServer(function(request, response) {
    // Not important for us. We're writing WebSocket server, not HTTP server
});
server.listen(webSocketsServerPort, function() {
    console.log((new Date()) + " Server is listening on port " + webSocketsServerPort);
});

var wsServer = new webSocketServer({
    httpServer: server
});

wsServer.on('request', function(request) {
    console.log((new Date()) + ' Connection from origin ' + request.origin + '.');

    var connection = request.accept(null, request.origin),
    index = clients.push(connection) - 1,
    userName=false;
    console.log((new Date()) + ' Connection accepted from '+connection.remoteAddress);

    // user sent some message
    connection.on('message', function(message) {
        var json = JSON.parse(message.utf8Data);

        console.log(json.type);
        switch (json.type) {
            case 'broadcast':
                broadcast(json);
            break;

            case 'emit':
                emit({type:'offer', data:json.data.data});
            break;

            case 'client':
                respondToClient(json, clients[index]);
            break;

            default:
                respondToClient({type:'error', data:'Sorry, i dont understand that.'}, clients[index]);
            break;

        }

    });

    connection.on('close', function(connection) {
        clients.splice(index,1);
        console.log((new Date()) + " Peer " + connection.remoteAddress + " disconnected.");
        broadcast({type:'text', data: userName+' has left the channel.'});
    });

    var respondToClient = function(data, client){
        client.sendUTF(JSON.stringify( data ));
    };

    var broadcast = function(data){
        for(var i = 0; i < clients.length; i++ ) {
            if(i != index ) {
                clients[i].sendUTF(JSON.stringify( data ));
            }
        }
    };
    var emit = function(){
        // TBD
    };
});

а вот клиент-код:

$(function () {
    "use strict";

    /**
    * Websocket Stuff
    **/

    window.WebSocket = window.WebSocket || window.MozWebSocket;

    // open connection
    var connection = new WebSocket('ws://url-to-node-server:61122'),
    myName = false,
    mySDP = false,
    otherSDP = false;

    connection.onopen = function () {
        console.log("connection to WebSocketServer successfull");
    };

    connection.onerror = function (error) {
        console.log("WebSocket connection error");
    };

    connection.onmessage = function (message) {
        try {
            var json = JSON.parse(message.data),
            output = document.getElementsByClassName('output')[0];

            switch(json.callback) {
                case 'offer':
                    otherSDP = json.data;
                    document.getElementById('acceptOffer').style.display = 'block';
                break;

                case 'setIceCandidate':
                console.log('ICE CANDITATE ADDED');
                    peerConnection.addIceCandidate(json.data);
                break;

                case 'text':
                    var text = output.value;
                    output.value = json.data+'n'+output.value;
                break;

                case 'answer':
                    otherSDP = json.data;
                    offerAccepted();
                break;

            }

        } catch (e) {
            console.log('This doesn't look like a valid JSON or something else went wrong.');
            return;
        }
    };
    /**
    * P2P Stuff
    **/
    navigator.getMedia = ( navigator.getUserMedia ||
       navigator.webkitGetUserMedia ||
       navigator.mozGetUserMedia ||
       navigator.msGetUserMedia);

    // create Connection
    var peerConnection = new webkitRTCPeerConnection(
        { "iceServers": [{ "url": "stun:stun.l.google.com:19302" }] }
    );


    var remoteVideo = document.getElementById('remoteStream'),
        myVideo = document.getElementById('myStream'),

        // get local video-Stream and add to Peerconnection
        stream = navigator.webkitGetUserMedia({ audio: false, video: true }, function (stream) {
            myVideo.src = webkitURL.createObjectURL(stream);
            console.log(stream);
            peerConnection.addStream(stream);
    });

    // executes if other side adds stream
    peerConnection.onaddstream = function(e){
        console.log("stream added");
        if (!e)
        {
            return;
        }
        remoteVideo.setAttribute("src",URL.createObjectURL(e.stream));
        console.log(e.stream);
    };

    // executes if my icecandidate is received, then send it to other side
    peerConnection.onicecandidate  = function(candidate){
        console.log('ICE CANDITATE RECEIVED');
        var json = JSON.stringify( { type: 'broadcast', callback:'setIceCandidate', data:candidate});
        connection.send(json);
    };

    // send offer via Websocket
    var sendOffer = function(){
        peerConnection.createOffer(function (sessionDescription) {
            peerConnection.setLocalDescription(sessionDescription);
            // POST-Offer-SDP-For-Other-Peer(sessionDescription.sdp, sessionDescription.type);
            var json = JSON.stringify( { type: 'broadcast', callback:'offer',data:{sdp:sessionDescription.sdp,type:'offer'}});
            connection.send(json);

        }, null, { 'mandatory': { 'OfferToReceiveAudio': true, 'OfferToReceiveVideo': true } });
    };

    // executes if offer is received and has been accepted
    var createAnswer = function(){

        peerConnection.setRemoteDescription(new RTCSessionDescription(otherSDP));

        peerConnection.createAnswer(function (sessionDescription) {
            peerConnection.setLocalDescription(sessionDescription);
            // POST-answer-SDP-back-to-Offerer(sessionDescription.sdp, sessionDescription.type);
            var json = JSON.stringify( { type: 'broadcast', callback:'answer',data:{sdp:sessionDescription.sdp,type:'answer'}});
            connection.send(json);
        }, null, { 'mandatory': { 'OfferToReceiveAudio': true, 'OfferToReceiveVideo': true } });

    };

    // executes if other side accepted my offer
    var offerAccepted = function(){
        peerConnection.setRemoteDescription(new RTCSessionDescription(otherSDP));
        console.log('it should work now');
    };

    $('#acceptOffer').on('click',function(){
        createAnswer();
    });

    $('#createOffer').on('click',function(){
        sendOffer();
    });
});

Я также читал, что локальный медиа-поток должен быть собран перед отправкой любого предложения. Означает ли это, что я должен добавить его при создании PeerConnection? Т. е. что-то вроде этого:--7-->

// create Connection
var peerConnection = new webkitRTCPeerConnection(
    { 
        "iceServers": [{ "url": "stun:stun.l.google.com:19302" }],
        "mediaStream": stream // attach media stream here?
    }
);

заранее спасибо, я ценю любую помощь!

EDIT2: теперь я немного дальше. похоже, что добавление удаленных ICE-кандидатов (switch-case setIceCandidate в клиент-коде) не работает из-за "недопустимой или незаконной строки". ". формат JSON.данные.объект-кандидат выглядит следующим образом:

candidate: "a=candidate:1663431597 2 udp 1845501695 141.84.69.86 57538 typ srflx raddr 10.150.16.92 rport 57538 generation 0
↵"
sdpMLineIndex: 1
sdpMid: "video"

Я попытался создать новый такой кандидат

 var remoteCandidate = new RTCIceCandidate(json.data.candidate);
 peerConnection.addIceCandidate(remoteCandidate);

но у меня все еще есть синтаксическая ошибка

1 ответов


в последнее время у меня были проблемы с тем же самым, и лучшим советом, который я получил от кого-то другого, было создать версию моей программы, в которой я вручную скопировал и вставил информацию SDP и ICE из одного "peer" (т. е. вкладки браузера) в другой и наоборот.

сделав это, я понял несколько вещей:

  1. необходимо вызвать метод addStream объекта однорангового соединения до вы пытаетесь создавайте любые предложения / ответы.

  2. при вызове метода createOffer или createAnswer мгновенно генерируются кандидаты ICE для этого клиента. Однако, как только вы отправили информацию ICE другому одноранговому узлу, вы не можете установить информацию ICE до тех пор, пока не будет установлено удаленное описание (используя полученное предложение/ответ).

  3. убедитесь, что вы правильно кодируете всю информацию, которая будет отправлена по проводу. В JS, это означает, что вы должны использовать функция encodeURIComponent для всех данных, которые будут отправлены по проводу. У меня была проблема, в которой информация SDP и ICE иногда устанавливалась правильно, а иногда нет. Это было связано с тем, что я не кодировал данные URI, что привело к тому, что любые знаки плюса в данных были превращены в пробелы, что все испортило.

в любом случае, как я уже сказал, я рекомендую создать версию программы, в которой у вас есть куча текстовых областей для выплюнув все данные на экран, а затем другие текстовые области, в которые можно вставить скопированные данные для настройки для другого однорангового узла.
Это действительно прояснило весь процесс WebRTC, который, честно говоря, не очень хорошо объясняется в любых документах/учебниках, которые я видел.

удачи, и дайте мне знать, если я могу помочь.