Удаленный VideoStream не работает с WebRTC

РЕДАКТИРОВАТЬ: я написал подробное руководство, объясняющее, как создать простое приложение Videochat, включая сервер сигнализации:

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

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

я пытаюсь заставить потоки работать через WebRTC и Websocket (nodejs-сервер). Насколько я вижу, рукопожатие через SDP работает, и Peerconnection установлен. Проблема в том, что Remote-Video не воспроизводится. Атрибут src получает BLOB-объект и устанавливается автозапуск, но он просто не будет воспроизводиться. Может быть, я делаю что-то не так с кандидатами ICE (они используются для потоковой передачи медиа, верно?). Есть ли способ проверить правильность настройки PeerConnection?

РЕДАКТИРОВАТЬ: Может быть, я должен объяснить, как работает код

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

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

Другой пользователь получает сообщение и сохраняет полученный им SDP

Если пользователь нажимает "принять предложение"SDP добавляется в RemoteDescription (func createAnswer ()), которое затем отправляет ответное сообщение (содержащее SDP отвечающего пользователя) пользователю предложения

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

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

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

Разметка index.html:



    
        
        
            #acceptOffer  {
                display: none;
            }
        
    
    
        Chat
        
            
        
        create Offer
        accept Offer

        My Stream
        
        Remote Stream
        

        
        


Вот код сервера:

"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? То есть что-то вроде этого:

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

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

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

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)

Ваш ответ на вопрос