Подтвердить что ты не робот

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

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

Учебное пособие: Создайте свой собственный видеочат-приложение с помощью HTML и JavaScript

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


Я пытаюсь заставить Streams работать через WebRTC и Websocket (nodejs-server). Насколько я могу видеть рукопожатие через SDP, и Peerconnection установлен. Проблема в том, что Remote-Video не воспроизводится. Src-Attribute получает Blob и автовоспроизведение, но он просто не играет. Может быть, я делаю что-то неправильно с ICE-кандидатами (они используются для потоковой передачи мультимедиа, правда?). Есть ли способ проверить правильность настройки PeerConnection?

EDIT: Может быть, я должен объяснить, как работает код.

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

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

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

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

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

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

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

Разметка 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>

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

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

И здесь Client-Code:

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

но я все еще получил синтаксическую ошибку

4b9b3361

Ответ 1

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

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

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

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

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

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

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