WebRTC系列-RTCDataChannel发送非音视频数据

Posted 简简单单lym

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WebRTC系列-RTCDataChannel发送非音视频数据相关的知识,希望对你有一定的参考价值。

文章目录


即时通讯中我们除了媒体数据的发送需求外,有时候需要发送非媒体数据;WebRTC提供了一种发送非音视频数据的方式就是打他RTCDataChannel,在之前的版本里是使用rtp来传输数据,在最新的版本里已经采用sctp协议来发送数据;这篇文章主要介绍js端的实现;

1. RTCDataChannel基本介绍

RTCDataChannel 接口是WebRTC API的一个功能,可以在两个设备间建立一个对等的通道(ice穿透通道),然后就可以通过该通道发送和接收任意数据。
RTCDataChannel 可以通过RTCPeerconnection 创建,接口函数定义如下:

   createDataChannel(label: string, dataChannelDict?: RTCDataChannelInit): RTCDataChannel;

其中label是一个标识表示两边的通道是一个,dataChannelDict是一个可选参数用来配置datachannal的一个设置项目,定义如下:


    id?: number;
    maxPacketLifeTime?: number;
    maxRetransmits?: number;
    negotiated?: boolean;
    ordered?: boolean;
    protocol?: string;
 

这些参数一般不用设置,也能使用,如果特殊要求需要设置,这里介绍一下参数的含义:

  • ordered 表示通过 RTCDataChannel 的发送的信息的到达顺序需要和发送顺序一致(true), 或者到达顺序不需要和发送顺序一致 (false).也就是是否保证包的顺序性; 属性的默认值是: true.
  • maxPacketLifeTime 最大存活时间,在不可靠模式下尝试传输消息的最大毫秒数,也就是一个包持续尝试发送的超时时间,默认是null;
  • maxRetransmits 消息尝试重新发送的次数,这个和maxPacketLifeTime只能同时设置一个,默认值是null;
  • protocol datachanal 使用的字协议名称,默认是 空字符串;
  • negotiated false的时候其中一方调用createDataChannel创建通道,另一方在peerConnection的onDataChannal事件中处理另外一方的datachannal,如果是true那么两端都可以使用createDataChannel来创建数据通道,使用ID来标识数据通道;默认是false;
  • id 通道的唯一标识ID ,如果不设置会默认设置一个作为标识;
    接下来,看下实际项目中的使用;

2. RTCDataChannel实战

2.1 negotiated=false 创建及事件处理

在之前的demo中新增两个变量:

let sendDC = null;
let recvDC = null;

sendDC是发送方的ID通过peer的create方法生成:

 sendDC = peerconnetion.createDataChannel('my channal');
    sendDC.onopen = function () 
        console.log("sendDC datachannel open");
    ;
  
    sendDC.onclose = function () 
        console.log("sendDC datachannel close");
    ;

通过设置两个监听用于接收数据通道的状态变化;
recvDC是在对方创建成功后,用于接收远端的消息的,通过peerconnection的ondatachannel可以获取到:

peerconnetion.ondatachannel = (ev)=>
        recvDC = ev.channel;
        recvDC.onmessage = function (event) 
            console.log(" recvDC received: " + event.data);
        ;
    
        recvDC.onopen = function () 
            console.log("recvDC datachannel open");
        ;
      
        recvDC.onclose = function () 
            console.log("recvDC datachannel close");
        ;
    ;

上述代码中同senDC一样监听的打开和关闭的方法;同时需要监听收消息的方法,用来处理对方发送过来的数据;

2.2 negotiated= true 创建及事件处理

同上一节不同的是这里本地和远端的dataChannal都可以使用createDataChanal来创建;创建及监听的设置如下:

const opt = 
        negotiated: true,
        id : 0
    ;
    sendDC = peerconnetion.createDataChannel('my channal',opt);
    sendDC.onopen = function () 
        console.log("sendDC datachannel open");
    ;
  
    sendDC.onclose = function () 
        console.log("sendDC datachannel close");
    ;
    sendDC.onmessage = function (event) 
            console.log(" recvDC received: " + event.data);
        ;

运行后在收消息里断点,如下:

2.3 发送消息

RTCDataChannel支持发送文本,二进制数据等内容的数据,其接口定义如下:

    send(data: string): void;
    send(data: Blob): void;
    send(data: ArrayBuffer): void;
    send(data: ArrayBufferView): void;

在demo中为了便于测试先发送文本数据,如: sendDC.send('你好 我是 ' + selfid);;这样就将数据发送出去,在两外一端的recvDC的onmessage中就能收到消息;
如果需要关闭数据通道直接调用close方法就可以关闭,如下:

sendDC.close();
recvDC.close();
sendDC = null;
recvDC = null;

datachannal的基本使用就是以上的接口;

RTCDataChannel 用于信令?

【中文标题】RTCDataChannel 用于信令?【英文标题】:RTCDataChannel for signaling? 【发布时间】:2015-05-03 16:53:35 【问题描述】:

我一直在阅读 this article 以获取信号解决方案。作者提到了在建立连接时使用 RTCDataChannel 发出信号。

使用 RTCDataChannel 发送信号

需要信令服务来启动 WebRTC 会话。

但是,一旦在两个对等方之间建立了连接,理论上 RTCDataChannel 就可以接管信号通道。这可能会减少信令的延迟——因为消息是直接传送的——并有助于减少信令服务器的带宽和处理成本。我们没有演示,但请注意这个空间!

既然已经建立了连接,为什么还需要信令?

【问题讨论】:

如果ice 服务器发生变化,则需要重新协商。或者只是添加其他用户。 【参考方案1】:

每一方最初都声明它将发送哪些音频和/或视频轨道,以便可以打开正确数量的端口,并且可以确定适用于双方的分辨率和格式。需要一个信令通道将生成的 SDP 提议/答案以及每个端口的涓流 ICE 候选发送到另一端。

一旦连接,如果您不理会此设置 - 基本上永远不会在连接中添加轨道、删除任何轨道或​​显着更改轨道属性 - 那么您将不再需要信令服务器。

但是,如果您确实更改了其中任何一项,则需要重新协商,这就是听起来的样子:在信令通道上再进行一轮,就像第一轮一样。 p>

添加轨道的原因可能是第二个摄像头、另一个视频源(可能来自另一个参与者),或者可能是屏幕共享,诸如此类。

可以使用数据通道的文章是正确的。这是a demo! (目前仅限火狐浏览器。)

这篇文章关于需要信号服务的说法是错误的——前提是你有另一种发现方式——正如这个演示所证明的那样。

初始连接仅限聊天,但任何一方都可以推送以将视频添加到组合中。对此的重新协商是通过数据通道完成的(因为没有信令服务器!)

小提琴使用说明:

    没有服务器(因为它是一个小提琴),所以按Offer 按钮并复制报价。 将报价粘贴到另一个选项卡或另一台计算机上同一小提琴中的同一位置。 按 ENTER,然后复制您得到的答案并将其粘贴回第一个小提琴中。 再次按 ENTER(还不是 addTrack!) 您现在连接到两个数据通道:一个用于聊天,另一个用于发信号。 现在按下任一端的addTrack,视频应显示在另一端。 在另一个方向按addTrack,您应该可以双向播放视频。

var dc = null, sc = null, pc = new mozRTCPeerConnection(), live = false;
pc.onaddstream = e => v2.mozSrcObject = e.stream;
pc.ondatachannel = e => dc? scInit(sc = e.channel) : dcInit(dc = e.channel);
v2.onloadedmetadata = e =>  log("Face time!"); ;

function addTrack() 
  navigator.mediaDevices.getUserMedia(video:true, audio:true)
  .then(stream => pc.addStream(v1.mozSrcObject = stream));


pc.onnegotiationneeded = e => 
  pc.createOffer().then(d => pc.setLocalDescription(d)).then(() => 
    if (live) sc.send(JSON.stringify( "sdp": pc.localDescription ));
  ).catch(failed);
;

function scInit() 
  sc.onmessage = e => 
    var msg = JSON.parse(e.data);
    if (msg.sdp) 
      var desc = new mozRTCSessionDescription(JSON.parse(e.data).sdp);
      if (desc.type == "offer") 
        pc.setRemoteDescription(desc).then(() => pc.createAnswer())
        .then(answer => pc.setLocalDescription(answer)).then(() => 
          sc.send(JSON.stringify( "sdp": pc.localDescription ));
        ).catch(failed);
       else 
        pc.setRemoteDescription(desc).catch(failed);
      
     else if (msg.candidate) 
      pc.addIceCandidate(new mozRTCIceCandidate(msg.candidate)).catch(failed);
    
  ;


function dcInit() 
  dc.onopen = () =>  live = true; log("Chat!"); ;
  dc.onmessage = e => log(e.data);


function createOffer() 
  button.disabled = true;
  dcInit(dc = pc.createDataChannel("chat"));
  scInit(sc = pc.createDataChannel("signaling"));
  pc.createOffer().then(d => pc.setLocalDescription(d)).catch(failed);
  pc.onicecandidate = e => 
    if (e.candidate) return;
    if (!live) 
      offer.value = pc.localDescription.sdp;
      offer.select();
      answer.placeholder = "Paste answer here";
     else 
      sc.send(JSON.stringify( "candidate": e.candidate ));
    
  ;
;

offer.onkeypress = e => 
  if (e.keyCode != 13 || pc.signalingState != "stable") return;
  button.disabled = offer.disabled = true;
  var obj =  type:"offer", sdp:offer.value ;
  pc.setRemoteDescription(new mozRTCSessionDescription(obj))
  .then(() => pc.createAnswer()).then(d => pc.setLocalDescription(d))
  .catch(failed);
  pc.onicecandidate = e => 
    if (e.candidate) return;
    if (!live) 
      answer.focus();
      answer.value = pc.localDescription.sdp;
      answer.select();
     else 
      sc.send(JSON.stringify( "candidate": e.candidate ));
    
  ;
;

answer.onkeypress = e => 
  if (e.keyCode != 13 || pc.signalingState != "have-local-offer") return;
  answer.disabled = true;
  var obj =  type:"answer", sdp:answer.value ;
  pc.setRemoteDescription(new mozRTCSessionDescription(obj)).catch(failed);
;

chat.onkeypress = e => 
  if (e.keyCode != 13) return;
  dc.send(chat.value);
  log(chat.value);
  chat.value = "";
;

var log = msg => div.innerHTML += "<p>" + msg + "</p>";
var failed = e => log(e.name + ": " + e.message + " line " + e.lineNumber);
<video id="v1"   autoplay muted></video>
<video id="v2"   autoplay></video><br>
<button id="button" onclick="createOffer()">Offer:</button>
<textarea id="offer" placeholder="Paste offer here"></textarea><br>
Answer: <textarea id="answer"></textarea><br>
<button id="button" onclick="addTrack()">AddTrack</button>
<div id="div"></div><br>
Chat: <input id="chat"></input><br>

【讨论】:

很好的答案。即使你不做“复杂”的事情,比如添加其他对等点或第二个摄像头,仍然有信号要发送。想想结束通话,聊天消息等......这并不多,但如果你能获得几个字节的带宽而不是每次通话都通过你的服务器,那么它仍然值得研究。

以上是关于WebRTC系列-RTCDataChannel发送非音视频数据的主要内容,如果未能解决你的问题,请参考以下文章

Socket.io vs RTCDataChannel,作为信令服务器?

EasyRTC 通话报错 `Failed to execute ‘send‘ on ‘RTCDataChannel‘: RTCDataChannel.readyState is not ‘open‘`

11┃音视频直播系统之 WebRTC 进行文本聊天并实时传输文件

腾讯IVWEB团队:WebRTC 点对点直播

WebRTC基本概念(一)

WebRTC[54] - WebRTC之RTCP详解