Zdalny strumień wideo nie działa z WebRTC

EDYCJA: Napisałem szczegółowy samouczek wyjaśniający, jak zbudować prostą aplikację Videochat, w tym serwer sygnalizacji:

Samouczek: Stwórz własną aplikację Videochat z HTML i JavaScript

Poinformuj mnie, jeśli uznasz to za pomocne i zrozumiałe. Dzięki!

Próbuję uzyskać strumieni do pracy za pośrednictwem WebRTC i Websocket (serwer nodejs). O ile widzę, uzgadnianie przez prace SDP i ustanowienie połączenia Peerconnection. Problem polega na tym, że zdalne wideo nie jest odtwarzane. Atrybut src pobiera Blob, a autoplay jest ustawiony, ale po prostu nie grał. Może robię coś złego z kandydatami do ICE (są one używane do przesyłania strumieniowego multimediów, prawda?). Czy jest jakiś sposób sprawdzenia, czy połączenie PeerConnection jest poprawnie skonfigurowane?

EDIT: Może powinienem wyjaśnić, jak działa kod

Przy obciążeniu strony internetowej nawiązywane jest połączenie z serwerem websocket, tworzony jest PeerConnection z użyciem serwera Google STUN, a strumienie wideo i audio są gromadzone i dodawane do PeerConnection

Gdy jeden użytkownik kliknie przycisk „Utwórz ofertę”, wiadomość zawierająca jego opis sesji (SDP) jest wysyłana na serwer (klient func sendOffer ()), który nadaje go innemu użytkownikowi

Drugi użytkownik otrzymuje wiadomość i zapisuje otrzymany SDP

Jeśli użytkownik kliknie „zaakceptuj ofertę”, SDP zostanie dodany do RemoteDescription (func createAnswer ()), który następnie wyśle ​​wiadomość odpowiedzi (zawierającą SDP użytkownika odpowiadającego) do użytkownika oferującego

Po stronie oferującego użytkownika wykonywane jest func offerAccepted (), które dodaje SDP innego użytkownika do jego RemoteDesription.

Nie jestem pewien, w jakim momencie dokładnie wywoływane są procedury obsługi icecandidate, ale myślę, że powinny działać, ponieważ dostaję oba dzienniki po obu stronach.

Oto mój kod (jest to tylko testowanie, więc nawet jeśli istnieje funkcja nazywana transmisją, oznacza to, że tylko 2 użytkowników może być jednocześnie na tej samej stronie):

Markup of index.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>

Oto kod serwera:

"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
    };
});

A tutaj kod klienta:

$(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();
    });
});

Czytałem również, że strumień mediów lokalnych musi zostać zebrany przed wysłaniem jakiejkolwiek oferty. Czy to znaczy, że muszę go dodać, gdy tworzone jest połączenie PeerConnection? To znaczy. coś takiego:

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

Z góry dziękuję, doceniam każdą pomoc!

EDIT2: jestem teraz trochę dalej. wygląda na to, że dodanie zdalnych kandydatów do lodu (setIceCandidate w przypadku przełącznika w kodzie klienckim) nie działa, ponieważ „Podano niepoprawny lub niedozwolony łańcuch”. obiekt json.data.candidate wygląda tak:

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"

Próbowałem stworzyć nowego kandydata takiego jak ten

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

ale wciąż mam błąd składniowy

questionAnswers(1)

yourAnswerToTheQuestion