abbshr / abbshr.github.io

人们往往接受流行,不是因为想要与众不同,而是因为害怕与众不同

Home Page:http://digitalpie.cf

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

RealTime Web II - WebRTC tech (2)

abbshr opened this issue · comments

编写Signaling服务

所谓signaling说白了就是外带的一个消息传递通道, 像polling, long-polling, long-connection, WebSocket等都可以做到. 至于signal服务具体要传递什么格式的消息, 下面有个signaling流程抽象, 来自HTML5ROCKs:

// 用WebSocket构建signalingChannel最为直观
var signalingChannel = new SignalingChannel();

// call start() to initiate

function start() {
  pc = new RTCPeerConnection(configuration);

  // send any ice candidates to the other peer
  pc.onicecandidate = function (evt) {
    if (evt.candidate)
      // 这里发送signal
      signalingChannel.send(JSON.stringify({
        'candidate': evt.candidate
      }));
  };

  // let the 'negotiationneeded' event trigger offer generation
  pc.onnegotiationneeded = function () {
    pc.createOffer(localDescCreated, logError);
  }

  // once remote stream arrives, show it in the remote video element
  pc.onaddstream = function (evt) {
    remoteView.src = URL.createObjectURL(evt.stream);
  };

  // get a local stream, show it in a self-view and add it to be sent
  navigator.getUserMedia({
    'audio': true,
    'video': true
  }, function (stream) {
    selfView.src = URL.createObjectURL(stream);
    pc.addStream(stream);
  }, logError);
}

function localDescCreated(desc) {
  pc.setLocalDescription(desc, function () {
    // 这里也要发送signal
    signalingChannel.send(JSON.stringify({
      'sdp': pc.localDescription
    }));
  }, logError);
}

// 收到对方发来的signal.
signalingChannel.onmessage = function (evt) {
  if (!pc)
    start();

  var message = JSON.parse(evt.data);
  if (message.sdp)
    pc.setRemoteDescription(new RTCSessionDescription(message.sdp), function () {
      // if we received an offer, we need to answer
      if (pc.remoteDescription.type == 'offer')
        pc.createAnswer(localDescCreated, logError);
    }, logError);
  else
    pc.addIceCandidate(new RTCIceCandidate(message.candidate));
};

function logError(error) {
  log(error.name + ': ' + error.message);
}

STUN/TURN服务器

STUN是为了获取位于NAT背后节点的公开可访问IP和端口而存在的. 而且:

Most WebRTC calls successfully make a connection using STUN: 86%

TURN在之前也介绍过. WebRTC传输层默认使用UDP协议来建立peer, 如果失败的话换用TCP直连, 再不行就让TURN接手, 通过外部TURN服务器转发两个节点的流量了.

这幅图说明了signaling, STUN, TURN的关系:

relation

在构建WebRTC应用时, 为了方便可以选择公共的STUN服务器. 当然, 最好是能部署在可信任的环境下.

多播

WebRTC允许通过浏览器建立多对peers. 如果一个网络中有数量很多的终端建立多方呼叫, 应该在链路上部署MCU设备.

多端控制器(Multipoint Control Unit (MCU))是一种用于多媒体视讯会议(Video Conference)的装置系统,主要功能是在控制多个终端间的视讯传输。

W3C WebRTC API & examples

终于到实际应用了, 来看下前面说的那些流程怎么用真实代码写出来. 下面是一个最简单的连接建立过程:

浏览器对开发者暴露了一个接口RTCPeerConnection函数. 要使用WebRTC, 最先通过它:

var peer = new RTCPeerConnection(servers)

// servers是一个可选的配置对象, 表示要为peer使用的STUN/TURN服务器:
/*
{
  'iceServers': [ { "urls": "stun:stun1.example.net" }, { "urls": "turn:turn.example.org", "username": "user", "credential": "myPassword" } ]
}
*/

Note: 由于目前各大浏览器厂商竞争不断, 导致这个API一直没有遵循W3C规范, 比如在Chrome下需要加webkit前缀, FireFox需要moz前缀

然后连接另一个对等方.

// peer需要首先发送一个offer报文
peer.createOffer(function (desc){
  // 设置本地sessionDescription
  peer.setLocalDescription(desc);
  signalchannel.send({sdp: peer.localDescription})
});
peer.onicecandidate = function (e) {
    signalchannel.send({candidate: e.candidate});
};
signalchannel.onMessage = function (e) {
    peer.setRemoteDescription(new RTCSessionDescription(JSON.parse(e.data.sdp)));
}

对等方:

signalchannel.onMessage = function (e) {
    // 对等方设置发送offer的远程sessionDescription
    dstpeer.setRemoteDescription(new RTCSessionDescription(JSON.parse(e.data.sdp)), function () {
    if (dstpeer.remoteDescription.type == 'offer')
        dstpeer.createAnswer(cb);
    dstpeer.addIceCandidate(new RTCIceCandidate(JSON.parse(e.data.candidate)));
});
};

sessionDescription设置完毕, offer/answer报文发送并且ICE探测成功, WebRTC通信就可以真正开始了. 至于MediaStream和DataChannel等多媒体Web的内容, 等到后面再讲.

详细的API参看W3C WebRTC API