WebRTC技术
在线视频传输,传统做法是做一个中继服务器,负责客户端的发现和数据的中介传输,那么就会产生一个很明显的问题,中继服务器需要
传输大量的数据,不仅如此还有复杂的流信息控制以及同步等问题。而且,随着数据量的增大,中继服务器单机无法承载,不得不做负载
均衡甚至地区分发等,大大增加系统复杂度,增加了各种成本,降低了稳定性。而且服务器作为中介,有记录用户传输数据的能力,用户
的隐私问题也值得关注。所以,如果能够让客户机P2P的连接以及传输数据,让客户机自己去处理同步以及控制问题,自己去传输流数据,
这样即可大大减小系统的复杂度。WebRTC就是致力于建立统一的浏览器标准,来完成这种P2P的传输工作。
本文声明
由于WebRTC的大量功能还处于实验阶段,即使在MDN上面,很多接口也没有详细的介绍和说明,部分没有翻译,而网上的代码大多也过时
,因为WebRTC已经duplicate一部分函数了:例如RTCPeerConnection
中createOffer
函数的successCallback
参数等。所以写
此文,大略的介绍一下RTC里面的部分基础组件和常用流程。另外由于这些API处于实验阶段,仍然可能变化,本文仅限写作时的时效性。
获取流
视频,音频是以流(stream)的形式进行网络传输,为了获取一个流,可以使用HTML的getUserMedia
,由于目前支持该对象的浏览器
各不相同,暂时可以用下列代码获得:
getUserMedia = (navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia);
getUserMedia
可以用来获取用户的视频/音频流,使用如下:
getUserMedia.call(navigator, {
"audio": true,
"video": true
}, function(stream) {
//绑定本地媒体流到video标签用于输出
localVideoElement.src = URL.createObjectURL(stream);
}, function(error) {
//处理错误
});
就像代码中所描述的,处理流的第二个参数中的匿名函数,将stream使用URL.createObjectURL
创建一个blob的URL,这个URL可以
绑定到HTML5的Video标签播放(记得Video标签加上autoplay属性,不然就只有一张图了)。
信令传输
要实现Client到Client的直接传输,还需要服务器协调一些数据,比如最基本的,两个客户端的IP地址是什么,好让他们互相发现。另外
由于因特网的历史原因,NAT广泛用于全世界,所以,要实现P2PNAT穿透也是一个问题,NAT穿透的问题已在上一篇讲过,这盘文章在局域
网内做一个视频传输。和服务器的传输,到了这个时代,使用websocket
有很多好处,不一一列举。websocket
的基本使用如下:
//没有TLS协议的话用ws://,因为chrome等浏览器要求获取用户流的网站必须是安全的,所以一般都用了TLS(HTTPS)
var socket = new WebSocket('wss://0.0.0.0/xxx');
socket.onopen = function() { ... }
socket.onmessage = function(event) { //event.data是具体信息 }
socket.send(....);
客户端(浏览器)传输
浏览器间流的传输使用PeerConnection
,这个对象封装了底层的传输,以及流数据的编码、同步控制,使用起来相当简易。同样,获取这个
对象也要兼容不同浏览器:
PeerConnection = (window.PeerConnection ||
window.webkitPeerConnection00 ||
window.webkitRTCPeerConnection ||
window.mozRTCPeerConnection);
该对象的传输涉及几个概念,candidate
是ICE候选信息,包括了对端的IP地址等信息,用于互相发现,offer
和answer
可能是用来同步
数据等等的,每次发送数据时,发送方都要发送一个offer
过去,接收方收到后,根据offer
更新自己的会话,接收方也可以发送answer
信令让发送方更新会话。发送方和接收方一开始就要确定,身份在整个传输中不变(确定谁是发送谁是接收就交给协调服务器好了)。同时,answer
信令在接收到offser
之前是不能发送的,而且在发送offer
信令的时候,也会发送candidate
过去,所以,传输流程如下:
- 接收方准备好
PeerConnection
- 发送方准备好
PeerConnection
,并在有流数据获取到的时候发送offer
信令 - 当接收方收到
offer
信令,则更新本地会话,并开始在有流数据到达时发送answer
信令 - 当发送方收到
answer
信令,更新本地会话 - 现在P2P通道已经建立
//准备PeerConnection
pc = new PeerConnection({
"iceServers": []});
//收到ICE候选时发送ICE候选到其他客户端
pc.onicecandidate = function(event){
socket.send(JSON.stringify({
"type": "__ice_candidate",
"candidate": event.candidate
}));
};
//当收到candidate信令(比如通过websocket)
pc.addIceCandidate(new RTCIceCandidate(data.candidate));
//当流数据到达时,接收方的处理(注意写法,回调函数的写法已经过时了):