【问题标题】:Remote VideoStream not working with WebRTC远程视频流不适用于 WebRTC
【发布时间】:2013-06-27 19:45:33
【问题描述】:

编辑:我写了一个详细的教程来解释如何构建一个简单的视频聊天应用程序,包括一个信令服务器:

Tutorial: Create your own Videochat-Application with HTML and JavaScript

如果您觉得它有帮助且易于理解,请告诉我。谢谢!


我正在尝试通过 WebRTC 和 Websocket (nodejs-server) 让 Streams 工作。据我所知,通过 SDP 的握手工作并建立了对等连接。 问题是 - 远程视频没有播放。 src-Attribute 获取 Blob 并设置了自动播放,但它不会播放。 也许我对 ICE 候选人做错了什么(它们用于媒体流,对吗?)。 有什么方法可以检查 PeerConnection 是否设置正确?

编辑:也许我应该解释一下代码是如何工作的

  1. 在加载网站时,会建立与 websocket-server 的连接,使用 googles STUN-server 创建 PeerConnection,并收集视频和音频流并将其添加到 PeerConnection

  2. 当一个用户单击“创建报价”按钮时,一条包含其会话描述 (SDP) 的消息被发送到服务器(客户端函数 sendOffer()),服务器将其广播给另一个用户

  3. 其他用户收到消息并保存他收到的 SDP

  4. 如果用户点击“accept offer”,SDP 被添加到 RemoteDescription (func createAnswer()),然后发送一个应答消息(包含应答用户的 SDP)给提供用户

  5. 在提供用户端执行 func offerAccepted(),它将其他用户的 SDP 添加到他的 RemoteDesription。

我不确定在什么时候调用了 icecandidate-handlers,但我认为它们应该可以工作,因为我在两边都得到了两个日志。

这是我的代码(这只是为了测试,所以即使有一个叫做广播的功能,也意味着一次只能有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
    };
});

这里是客户端代码:

$(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 时添加它? IE。像这样:

// 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-object 看起来像这样:

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

但我仍然遇到语法错误

【问题讨论】:

标签: stream websocket p2p webrtc stun


【解决方案1】:

我最近遇到了基本相同的问题,我从其他人那里得到的最好建议是创建一个我的程序版本,我在其中手动复制并粘贴了来自一个“同行”的 SDP 和 ICE 信息(即浏览器选项卡)到另一个,反之亦然。

通过这样做,我意识到了几件事:

  1. 您必须在尝试创建任何优惠/答案之前调用对等连接对象的 addStream 方法。

  2. 调用 createOffer 或 createAnswer 方法后,会立即生成该客户端的 ICE 候选对象。但是,一旦您将 ICE 信息发送给其他对等方,在设置远程描述(通过使用收到的提议/答案)之前,您无法实际设置 ICE 信息。

  3. 确保您正确编码了所有要通过网络发送的信息。在 JS 中,这意味着您应该对将要在线发送的所有数据使用 encodeURIComponent 函数。我有一个问题,有时 SDP 和 ICE 信息设置正确,有时不正确。这与我没有对数据进行 URI 编码这一事实有关,这导致数据中的任何加号都变成了空格,从而搞砸了一切。

无论如何,就像我说的那样,我建议创建一个程序版本,其中有一堆文本区域用于将所有数据吐出到屏幕上,然后有其他文本区域可以将复制的数据粘贴到其中为其他对等方设置它。
这样做确实澄清了整个 WebRTC 过程,老实说,在我见过的任何文档/教程中都没有很好地解释这一点。

祝你好运,如果我还能提供帮助,请告诉我。

【讨论】:

  • 嘿@HartleySan,非常感谢您抽出宝贵时间!我照你说的做了,复制并粘贴了 SDP 数据。看来我的冰候选人是问题所在。另外,我真的不明白程序应该如何。他们是否需要不断地交换候选人,所以每个“onecandidate”事件都应该将候选人发送给其他用户吗?还是我对“onicecandidate”什么都不做?
  • 现在我做: 1. 创建一个报价,自动导致大约。 20 次“oneeccandidate”电话。我猜这些候选人就是那些提供用户的人,对吧? 2. 将 SDP 发送给其他用户 3. 其他用户然后获取 SDP, 4. 将其添加到其 remoteConnection 然后我应该 5. 在我做之前做一个“addIceCandidate(offeringUsersCandidate)” 6. setLocalDescription()的回答用户被称为,当我读出你的文字,还是我弄错了?那么问题是我此时没有提供用户的候选人。我真的很困惑:)
  • 是的,这很令人困惑,我知道。就像我说的那样,没有好的文档可以解释它。我玩了一下,发现如下: 1) 可以在远程描述(即来自其他对等方的描述)设置后的任何时间设置来自其他对等方的 ICE 候选者。这意味着您可以在设置本地描述之前或之后设置 ICE 候选者(当然是针对回答者)。 2)我倾向于不断转发并检查 ICE 候选人,直到两端都弹出两个视频。那时,可以生成额外的 ICE 候选,但我不使用它们。
  • 总而言之,我会这样做: 1) 每当出现onicecandidate 时,立即将该数据发送给其他对等方。但是,当其他对等方收到该信息时,它不应该设置它,直到设置远程描述。在此之前收到的任何 ICE 候选信息都应该放入一个数组或其他东西中,到时候循环遍历数组,设置所有 ICE 候选信息。 2)对于应答者,一旦你从其他对等点获得SDP信息(offer),将其设置为远程描述,立即设置本地描述并发送,然后设置任何ICE候选信息。
  • 谢谢!!!!它现在起作用了!完成后,我将发布一个演示和一个 HOWTO,然后我将在此处发布链接。再次感谢您的耐心和良好的回答!
【解决方案2】:
function sharescreen(){
getScreenStream(function(screenStream) {
localpearconnection.removeTrack(localStream); 
localpearconnection.addStream(screenStream);
localpearconnection.createOffer().then(description => createdLocalDescription(description)).catch(errorHandler);
document.getElementById('localVideo').srcObject = screenStream;});}

function getScreenStream(callback) {
if (navigator.getDisplayMedia) {
    navigator.getDisplayMedia({
        video: true
    }).then(screenStream => {
        callback(screenStream);
    });
} else if (navigator.mediaDevices.getDisplayMedia) {
    navigator.mediaDevices.getDisplayMedia({
        video: true
    }).then(screenStream => {
        callback(screenStream);
    });
} else {
    getScreenId(function(error, sourceId, screen_constraints) {
        navigator.mediaDevices.getUserMedia(screen_constraints).then(function(screenStream) {
            callback(screenStream);
        });
    });
}}

上面的代码对我有用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-18
    相关资源
    最近更新 更多