WebRTC:ICE、STUN 和 TURN,但我不能只使用我的 SignalR.NET 连接吗?

Posted

技术标签:

【中文标题】WebRTC:ICE、STUN 和 TURN,但我不能只使用我的 SignalR.NET 连接吗?【英文标题】:WebRTC: ICE, STUN and TURN, but can't I just use my SignalR.NET connection? 【发布时间】:2018-04-20 00:53:10 【问题描述】:

我目前正在开发一个会议视频网络应用程序。我决定走WebRTC路线,但我仍然不清楚如何正确使用它。

我的应用程序是在 ASP.NET MVC 中制作的,我还添加了 SignalR 用于聊天/通知功能。我需要在用户之间建立实时连接并记录扬声器的流。扬声器的录音现在可以正常工作了。

我创建了一个页面,启动媒体记录器来捕获用户的网络摄像头,然后我附加到 ondataavailable 事件以捕获网络摄像头数据:

mediaRecorder.ondataavailable = function (e) 

    console.log('Data received: ', e);

    // Increase index
    streamindex++;

    // Save client dates
    var stopdate = new Date();
    var startdate = olddate;
    olddate = stopdate;

    // Format dates
    var startdatestr =
        startdate.getFullYear() + "-" +
        startdate.getMonth() + "-" +
        startdate.getDate() + " " +
        startdate.getHours() + ':' +
        startdate.getMinutes() + ':' +
        startdate.getSeconds() + '.' +
        startdate.getMilliseconds();

    var stopdatestr =
        stopdate.getFullYear() + "-" +
        stopdate.getMonth() + "-" +
        stopdate.getDate() + " " +
        stopdate.getHours() + ':' +
        stopdate.getMinutes() + ':' +
        stopdate.getSeconds() + '.' +
        stopdate.getMilliseconds();

    // Send data to server
    $.ajax(
        url: '/Upload?StreamId=' + streamid + '&Index=' + streamindex + '&StartDate=' + startdatestr + '&StopDate=' + stopdatestr,
        type: 'POST',
        contentType: 'application/octet-stream',
        data: e.data,
        processData: false
    );
;

我在服务器上保存数据,这似乎工作得很好。

现在我想在客户端之间建立点对点连接,我在网上查看了如何做到这一点,但我发现我必须使用 STUN 和 TURN 服务器来创建连接。因为我使用 .NET,所以我不太喜欢必须部署 nodejs 服务器的想法。我在网上找到了这段代码:

function call() 
    callButton.disabled = true;
    hangupButton.disabled = false;
    trace("Starting call");

    if (localStream.getVideoTracks().length > 0) 
        trace('Using video device: ' + localStream.getVideoTracks()[0].label);
    
    if (localStream.getAudioTracks().length > 0) 
        trace('Using audio device: ' + localStream.getAudioTracks()[0].label);
    

    var servers = null;

    localPeerConnection = new RTCPeerConnection(servers);
    trace("Created local peer connection object localPeerConnection");
    localPeerConnection.onicecandidate = gotLocalIceCandidate;

    remotePeerConnection = new RTCPeerConnection(servers);
    trace("Created remote peer connection object remotePeerConnection");
    remotePeerConnection.onicecandidate = gotRemoteIceCandidate;
    remotePeerConnection.onaddstream = gotRemoteStream;

    localPeerConnection.addStream(localStream);
    trace("Added localStream to localPeerConnection");
    localPeerConnection.createOffer(gotLocalDescription, handleError);


function gotLocalDescription(description) 
    localPeerConnection.setLocalDescription(description);
    trace("Offer from localPeerConnection: \n" + description.sdp);
    remotePeerConnection.setRemoteDescription(description);
    remotePeerConnection.createAnswer(gotRemoteDescription, handleError);


function gotRemoteDescription(description) 
    remotePeerConnection.setLocalDescription(description);
    trace("Answer from remotePeerConnection: \n" + description.sdp);
    localPeerConnection.setRemoteDescription(description);


function hangup() 
    trace("Ending call");
    localPeerConnection.close();
    remotePeerConnection.close();
    localPeerConnection = null;
    remotePeerConnection = null;
    hangupButton.disabled = true;
    callButton.disabled = false;


function gotRemoteStream(event) 
    remoteVideo.src = URL.createObjectURL(event.stream);
    trace("Received remote stream");


function gotLocalIceCandidate(event) 
    if (event.candidate) 
        remotePeerConnection.addIceCandidate(new RTCIceCandidate(event.candidate));
        trace("Local ICE candidate: \n" + event.candidate.candidate);
    


function gotRemoteIceCandidate(event) 
    if (event.candidate) 
        localPeerConnection.addIceCandidate(new RTCIceCandidate(event.candidate));
        trace("Remote ICE candidate: \n " + event.candidate.candidate);
    

本示例使用本地 RTCPeerConnection 发送视频/音频数据。如果我想在线使用这个示例,我应该将 STUN / TURN 服务器添加到“servers”变量中。但是由于我没有 STUN / TURN 服务器,有什么方法可以使用 SignalR 建立 WebRTC 连接?

(SignalR 让我可以非常轻松地在客户端之间发送消息/数据)

【问题讨论】:

【参考方案1】:

好的,原来我必须使用 Turn/Stun 服务器,因为它尝试在客户端之间建立点对点连接,并且支持所有 WebRTC 功能。

另一方面,WebRTC 确实需要您拥有自己的信令服务来建立客户端之间的连接。这就是 SignalR 的用武之地,在设置 RTCPeerConnection 时,您需要设置一些事件处理程序,例如您需要向其他客户端发送信号的“oniceccandidate”和“onaddstream”。

我在网上找到了这个例子:https://www.skylinetechnologies.com/Blog/Skyline-Blog/February-2013/Peer-to-Peer-Media-Streaming-with-WebRTC-and-Signa

(function () 
    var _myConnection,
        _myMediaStream;

    function _createConnection(onStreamAddedCallback) 
        // Create a new PeerConnection
        var connection = new webkitRTCPeerConnection(null);

        // ICE Candidate Callback
        connection.onicecandidate = function (event) 
            if (event.candidate) 
                hub.server.send(JSON.stringify( "candidate": event.candidate ));
            
        ;

        // Negotiation needed callback
        connection.onnegotiationneeded = function () 
            connection.createOffer(function (desc) 
                connection.setLocalDescription(desc, function () 
                    hub.server.send(JSON.stringify( "sdp": connection.localDescription ));
                );
            );
        ;

        // Stream handler
        connection.onaddstream = onStreamAddedCallback;

        return connection;
    

    // Set Up SignalR Signaler
    var hub = $.connection.webRtcHub;
    $.support.cors = true;
    $.connection.hub.url = '/signalr/hubs';
    $.connection.hub.start( xdomain: true , function () 
        console.log('connected to hub.');
        init();
    );

    hub.client.newMessage = function (data) 
        var message = JSON.parse(data),
            connection = _myConnection || _createConnection(onAddStream);

        if (message.sdp) 
            connection.setRemoteDescription(new RTCSessionDescription(message.sdp), function () 
                if (connection.remoteDescription.type == "offer") 
                    console.log('received offer, sending response...');
                    connection.createAnswer(function (desc) 
                        connection.setLocalDescription(desc, function () 
                            hub.server.send(JSON.stringify( "sdp": connection.localDescription ));
                        );
                    );
                
            );
         else if (message.candidate) 
            console.log('adding ice candidate...');
            connection.addIceCandidate(new RTCIceCandidate(message.candidate));
        

        _myConnection = connection;
    ;

    function onAddStream(event) 
        console.log('Adding stream id: ' + event.stream.id);

        var newVideoElement = document.createElement('video');
        newVideoElement.className = 'video';
        newVideoElement.src = window.webkitURL.createObjectURL(event.stream);
        newVideoElement.autoplay = 'autoplay';

        document.querySelector('body').appendChild(newVideoElement);
    

    function init() 
        navigator.webkitGetUserMedia(
                
                    // Request Permissions
                    video: true,
                    audio: true
                ,
                function (stream)  // succcess callback
                    var videoElement = document.querySelector('.video.mine');

                    _myMediaStream = stream;
                    videoElement.src = window.webkitURL.createObjectURL(_myMediaStream);

                    console.log('my stream id: ' + stream.id);

                    $('#startBtn').removeAttr('disabled');
                ,
                function (error)  // error callback
                    alert(JSON.stringify(error));
                
            );

        $('#startBtn').click(function () 
            _myConnection = _createConnection(onAddStream);
            _myConnection.addStream(_myMediaStream);

            // done being able to work
            $('#startBtn').attr('disabled', 'disabled');
        );
    
)();

我用这段代码编写了我自己的客户端和服务器库(我想要一个一对多的视频会议系统)。您可以使用从 navigator.webkitGetUserMedia 获得的流来连接多个 RTCPeerConnections。知道了这部分之后,一切就都到位了。

【讨论】:

您好,您的工作解决方案与非工作解决方案有什么不同?你能在不使用 STUN 或 TURN 服务器的情况下让它工作吗?我能看到的唯一区别是您的解决方案使用“webkitRTCPeerConnection” 老问题,不,除非您的一位参与者直接连接到互联网(使用路由器),否则没有眩晕/转弯是不可能让它工作的。 @ikwillem 你能否分享一个工作样本,我需要了解所有活动部件。谢谢 嘿@ikwillem,你找到这个问题的解决方案了吗? 这是一个老问题,今天“onAddStream”和“AddStream”已经被贬低了。所以代码很可能不再起作用。有多个较新的示例可用:webrtc.github.io/samples 这也很有帮助developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/…

以上是关于WebRTC:ICE、STUN 和 TURN,但我不能只使用我的 SignalR.NET 连接吗?的主要内容,如果未能解决你的问题,请参考以下文章

用于 Java 的 STUN、TURN、ICE 库

适用于 iOS iPhone 的 ICE、STUN、TURN 库

WebRTC学习之ICE深入理解

STUN, TURN, ICE介绍

[webrtc] 交互式连接建立(ICE)

webrtc连接方法——TURN服务器和STUN服务器作用简介