远程VideoStream无法与WebRTC一起使用

时间:2013-06-30 15:07:50

标签: stream websocket p2p webrtc stun

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

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

请告诉我你是否觉得它有用&可以理解的。谢谢!


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

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

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

  2. 当一个用户点击“创建商品”时 - 按钮会将包含其会话描述(SDP)的消息发送到服务器(客户端功能sendOffer()),该服务器将其广播给其他用户

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

  4. 如果用户点击“接受报价”,SDP将被添加到RemoteDescription(func createAnswer()),然后该报告会向产品用户发送应答消息(包含应答用户的SDP)

  5. 在产品用户方面执行func offerAccepted(),将其他用户的SDP添加到他的RemoteDesription中。

  6. 我不确定在什么时候确切地调用了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
        };
    });
    

    这里是客户代码:

    $(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: 我现在更进一步了。似乎添加远程ice-candidate(客户端代码中的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);
    

    但我仍然有语法错误

2 个答案:

答案 0 :(得分:21)

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

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

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

  2. 在调用createOffer或createAnswer方法时,会立即生成该客户端的ICE候选项。但是,一旦您将ICE信息发送给另一个对等方,您就无法在设置远程描述之后(通过使用收到的要约/回答)实际设置ICE信息。

  3. 确保您正确编码要在线路上发送的所有信息。在JS中,这意味着您应该对要在线上发送的所有数据使用encodeURIComponent函数。我有一个问题,其中SDP和ICE信息有时会被正确设置,有时不会。这与我不对数据进行URI编码这一事实有关,这导致数据中的任何加号变成了空格,这搞砸了所有内容。

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

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

答案 1 :(得分:0)

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

上面的代码对我有用。