使用WebRTC搭建前端视频聊天室——入门篇

什么是WebRTC?

众所周知,浏览器自己不支持相互之间直接创建信道进行通讯,都是经过服务器进行中转。好比如今有两个客户端,甲和乙,他们俩想要通讯,首先须要甲和服务器、乙和服务器之间创建信道。甲给乙发送消息时,甲先将消息发送到服务器上,服务器对甲的消息进行中转,发送到乙处,反过来也是同样。这样甲与乙之间的一次消息要经过两段信道,通讯的效率同时受制于这两段信道的带宽。同时这样的信道并不适合数据流的传输,如何创建浏览器之间的点对点传输,一直困扰着开发者。WebRTC应运而生javascript

WebRTC是一个开源项目,旨在使得浏览器能为实时通讯(RTC)提供简单的JavaScript接口。说的简单明了一点就是让浏览器提供JS的即时通讯接口。这个接口所创立的信道并非像WebSocket同样,打通一个浏览器与WebSocket服务器之间的通讯,而是经过一系列的信令,创建一个浏览器与浏览器之间(peer-to-peer)的信道,这个信道能够发送任何数据,而不须要通过服务器。而且WebRTC经过实现MediaStream,经过浏览器调用设备的摄像头、话筒,使得浏览器之间能够传递音频和视频html

WebRTC已经在咱们的浏览器中

这么好的功能,各大浏览器厂商天然不会置之不理。如今WebRTC已经能够在较新版的Chrome、Opera和Firefox中使用了,著名的浏览器兼容性查询网站caniuse上给出了一份详尽的浏览器兼容状况html5

WebRTC浏览器兼容性from caniuse.com

另外根据36Kr前段时间的新闻Google推出支持WebRTC及Web Audio的Android 版Chrome 29@36krAndroid版Opera开始支持WebRTC,容许用户在没有任何插件的状况下实现语音和视频聊天,Android也开始支持WebRTCjava

三个接口

WebRTC实现了三个API,分别是:
* MediaStream:经过MediaStream的API可以经过设备的摄像头及话筒得到视频、音频的同步流
* RTCPeerConnection:RTCPeerConnection是WebRTC用于构建点对点之间稳定、高效的流传输的组件
* RTCDataChannel:RTCDataChannel使得浏览器之间(点对点)创建一个高吞吐量、低延时的信道,用于传输任意数据node

这里大体上介绍一下这三个APIpython

MediaStream(getUserMedia)

MediaStream API为WebRTC提供了从设备的摄像头、话筒获取视频、音频流数据的功能git

W3C标准

W3C标准传送门github

如何调用

同门能够经过调用navigator.getUserMedia(),这个方法接受三个参数:
1. 一个约束对象(constraints object),这个后面会单独讲
2. 一个调用成功的回调函数,若是调用成功,传递给它一个流对象
3. 一个调用失败的回调函数,若是调用失败,传递给它一个错误对象web

浏览器兼容性

因为浏览器实现不一样,他们常常会在实现标准版本以前,在方法前面加上前缀,因此一个兼容版本就像这样express

javacriptvar getUserMedia = (navigator.getUserMedia || 
                    navigator.webkitGetUserMedia || 
                    navigator.mozGetUserMedia || 
                    navigator.msGetUserMedia);

一个超级简单的例子

这里写一个超级简单的例子,用来展示getUserMedia的效果:

html<!doctype html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>GetUserMedia实例</title>
</head>
<body>
    <video id="video" autoplay></video>
</body>


<script type="text/javascript">
    var getUserMedia = (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia);

    getUserMedia.call(navigator, {
        video: true,
        audio: true
    }, function(localMediaStream) {
        var video = document.getElementById('video');
        video.src = window.URL.createObjectURL(localMediaStream);
        video.onloadedmetadata = function(e) {
            console.log("Label: " + localMediaStream.label);
            console.log("AudioTracks" , localMediaStream.getAudioTracks());
            console.log("VideoTracks" , localMediaStream.getVideoTracks());
        };
    }, function(e) {
        console.log('Reeeejected!', e);
    });
</script>


</html>

将这段内容保存在一个HTML文件中,放在服务器上。用较新版本的Opera、Firefox、Chrome打开,在浏览器弹出询问是否容许访问摄像头和话筒,选赞成,浏览器上就会出现摄像头所拍摄到的画面了

注意,HTML文件要放在服务器上,不然会获得一个NavigatorUserMediaError的错误,显示PermissionDeniedError,最简单方法就是cd到HTML文件所在目录下,而后python -m SimpleHTTPServer(装了python的话),而后在浏览器中输入http://localhost:8000/{文件名称}.html

这里使用getUserMedia得到流以后,须要将其输出,通常是绑定到video标签上输出,须要使用window.URL.createObjectURL(localMediaStream)来创造能在video中使用src属性播放的Blob URL,注意在video上加入autoplay属性,不然只能捕获到一张图片

流建立完毕后能够经过label属性来得到其惟一的标识,还能够经过getAudioTracks()getVideoTracks()方法来得到流的追踪对象数组(若是没有开启某种流,它的追踪对象数组将是一个空数组)

约束对象(Constraints)

约束对象能够被设置在getUserMedia()和RTCPeerConnection的addStream方法中,这个约束对象是WebRTC用来指定接受什么样的流的,其中能够定义以下属性:
* video: 是否接受视频流
* audio:是否接受音频流
* MinWidth: 视频流的最小宽度
* MaxWidth:视频流的最大宽度
* MinHeight:视频流的最小高度
* MaxHiehgt:视频流的最大高度
* MinAspectRatio:视频流的最小宽高比
* MaxAspectRatio:视频流的最大宽高比
* MinFramerate:视频流的最小帧速率
* MaxFramerate:视频流的最大帧速率

详情见Resolution Constraints in Web Real Time Communications draft-alvestrand-constraints-resolution-00

RTCPeerConnection

WebRTC使用RTCPeerConnection来在浏览器之间传递流数据,这个流数据通道是点对点的,不须要通过服务器进行中转。可是这并不意味着咱们能抛弃服务器,咱们仍然须要它来为咱们传递信令(signaling)来创建这个信道。WebRTC没有定义用于创建信道的信令的协议:信令并非RTCPeerConnection API的一部分

信令

既然没有定义具体的信令的协议,咱们就能够选择任意方式(AJAX、WebSocket),采用任意的协议(SIP、XMPP)来传递信令,创建信道,好比我写的demo,就是用的node的ws模块,在WebSocket上传递信令

须要信令来交换的信息有三种:
* session的信息:用来初始化通讯还有报错
* 网络配置:好比IP地址和端口啥的
* 媒体适配:发送方和接收方的浏览器可以接受什么样的编码器和分辨率

这些信息的交换应该在点对点的流传输以前就所有完成,一个大体的架构图以下:

JSEP架构

经过服务器创建信道

这里再次重申,就算WebRTC提供浏览器之间的点对点信道进行数据传输,可是创建这个信道,必须有服务器的参与。WebRTC须要服务器对其进行四方面的功能支持:
1. 用户发现以及通讯
2. 信令传输
3. NAT/防火墙穿越
4. 若是点对点通讯创建失败,能够做为中转服务器

NAT/防火墙穿越技术

创建点对点信道的一个常见问题,就是NAT穿越技术。在处于使用了NAT设备的私有TCP/IP网络中的主机之间须要创建链接时须要使用NAT穿越技术。以往在VoIP领域常常会遇到这个问题。目前已经有不少NAT穿越技术,但没有一项是完美的,由于NAT的行为是非标准化的。这些技术中大多使用了一个公共服务器,这个服务使用了一个从全球任何地方都能访问获得的IP地址。在RTCPeeConnection中,使用ICE框架来保证RTCPeerConnection能实现NAT穿越

ICE,全名叫交互式链接创建(Interactive Connectivity Establishment),一种综合性的NAT穿越技术,它是一种框架,能够整合各类NAT穿越技术如STUN、TURN(Traversal Using Relay NAT 中继NAT实现的穿透)。ICE会先使用STUN,尝试创建一个基于UDP的链接,若是失败了,就会去TCP(先尝试HTTP,而后尝试HTTPS),若是依旧失败ICE就会使用一个中继的TURN服务器。

咱们可使用Google的STUN服务器:stun:stun.l.google.com:19302,因而乎,一个整合了ICE框架的架构应该长这个样子

整合了ICE框架的WebRTC应用架构

浏览器兼容

仍是前缀不一样的问题,采用和上面相似的方法:

javascriptvar PeerConnection = (window.PeerConnection ||
                    window.webkitPeerConnection00 || 
                    window.webkitRTCPeerConnection || 
                    window.mozRTCPeerConnection);

建立和使用

javascript//使用Google的stun服务器
var iceServer = {
    "iceServers": [{
        "url": "stun:stun.l.google.com:19302"
    }]
};
//兼容浏览器的getUserMedia写法
var getUserMedia = (navigator.getUserMedia ||
                    navigator.webkitGetUserMedia || 
                    navigator.mozGetUserMedia || 
                    navigator.msGetUserMedia);
//兼容浏览器的PeerConnection写法
var PeerConnection = (window.PeerConnection ||
                    window.webkitPeerConnection00 || 
                    window.webkitRTCPeerConnection || 
                    window.mozRTCPeerConnection);
//与后台服务器的WebSocket链接
var socket = __createWebSocketChannel();
//建立PeerConnection实例
var pc = new PeerConnection(iceServer);
//发送ICE候选到其余客户端
pc.onicecandidate = function(event){
    socket.send(JSON.stringify({
        "event": "__ice_candidate",
        "data": {
            "candidate": event.candidate
        }
    }));
};
//若是检测到媒体流链接到本地,将其绑定到一个video标签上输出
pc.onaddstream = function(event){
    someVideoElement.src = URL.createObjectURL(event.stream);
};
//获取本地的媒体流,并绑定到一个video标签上输出,而且发送这个媒体流给其余客户端
getUserMedia.call(navigator, {
    "audio": true,
    "video": true
}, function(stream){
    //发送offer和answer的函数,发送本地session描述
    var sendOfferFn = function(desc){
            pc.setLocalDescription(desc);
            socket.send(JSON.stringify({ 
                "event": "__offer",
                "data": {
                    "sdp": desc
                }
            }));
        },
        sendAnswerFn = function(desc){
            pc.setLocalDescription(desc);
            socket.send(JSON.stringify({ 
                "event": "__answer",
                "data": {
                    "sdp": desc
                }
            }));
        };
    //绑定本地媒体流到video标签用于输出
    myselfVideoElement.src = URL.createObjectURL(stream);
    //向PeerConnection中加入须要发送的流
    pc.addStream(stream);
    //若是是发送方则发送一个offer信令,不然发送一个answer信令
    if(isCaller){
        pc.createOffer(sendOfferFn);
    } else {
        pc.createAnswer(sendAnswerFn);
    }
}, function(error){
    //处理媒体流建立失败错误
});
//处理到来的信令
socket.onmessage = function(event){
    var json = JSON.parse(event.data);
    //若是是一个ICE的候选,则将其加入到PeerConnection中,不然设定对方的session描述为传递过来的描述
    if( json.event === "__ice_candidate" ){
        pc.addIceCandidate(new RTCIceCandidate(json.data.candidate));
    } else {
         pc.setRemoteDescription(new RTCSessionDescription(json.data.sdp));
    }
};

实例

因为涉及较为复杂灵活的信令传输,故这里不作简短的实例,能够直接移步到最后

RTCDataChannel

既然能创建点对点的信道来传递实时的视频、音频数据流,为何不能用这个信道传一点其余数据呢?RTCDataChannel API就是用来干这个的,基于它咱们能够在浏览器之间传输任意数据。DataChannel是创建在PeerConnection上的,不能单独使用

使用DataChannel

咱们可使用channel = pc.createDataCHannel("someLabel");来在PeerConnection的实例上建立Data Channel,并给与它一个标签

DataChannel使用方式几乎和WebSocket同样,有几个事件:
* onopen
* onclose
* onmessage
* onerror

同时它有几个状态,能够经过readyState获取:
* connecting: 浏览器之间正在试图创建channel
* open:创建成功,可使用send方法发送数据了
* closing:浏览器正在关闭channel
* closed:channel已经被关闭了

两个暴露的方法:
* close(): 用于关闭channel
* send():用于经过channel向对方发送数据

经过Data Channel发送文件大体思路

JavaScript已经提供了File API从input[type='file']的元素中提取文件,并经过FileReader来将文件的转换成DataURL,这也意味着咱们能够将DataURL分红多个碎片来经过Channel来进行文件传输

一个综合的Demo

SkyRTC-demo,这是我写的一个Demo。创建一个视频聊天室,并可以广播文件,固然也支持单对单文件传输,写得还很粗糙,后期会继续完善

使用方式

  1. 下载解压并cd到目录下
  2. 运行npm install安装依赖的库(express, ws, node-uuid)
  3. 运行node server.js,访问localhost:3000,容许摄像头访问
  4. 打开另外一台电脑,在浏览器(Chrome和Opera,还未兼容Firefox)打开{server所在IP}:3000,容许摄像头和话筒访问
  5. 广播文件:在左下角选定一个文件,点击“发送文件”按钮
  6. 广播信息:左下角input框输入信息,点击发送
  7. 可能会出错,注意F12对话框,通常F5能解决

功能

视频音频聊天(链接了摄像头和话筒,至少要有摄像头),广播文件(可单独传播,提供API,广播就是基于单独传播实现的,可同时传播多个,小文件还好说,大文件坐等内存吃光),广播聊天信息

参考资料

相关文章
相关标签/搜索