HTML5之WebRTC的妙用

写在前面:随着HTML5渐进式发展,羽翼日渐丰满,哦不,也许就没有“满”的那一天。

##简介
WebRTC全称Web Real-Time Communication,主推的业务是浏览器端实时视音频通话。
这项技术本身经历了研发、产品化、开源推广,以及最终成为业内标准(WHATWG)。
之所以成为标准,离不开推广,更离不开其原生无插件特性以及跨平台支持。
此前如Adobe公司基于RTMP的一些实现占了半壁江山,然而还是印证了那句话:世界是开放的。

##背景
截止目前该技术已在浏览器端占大半江山(Safari 10、Android 4.x 及以下版本的原生浏览器不支持),过了今年9月份随着新iOS发布与Safari 11的普及,WebRTC将引爆市场。而目前市场上已经在会议通话协同办公等方面展开业务的公司可谓引来又一春。

下面,一幅图看懂WebRTC存在的意义:

WebRTC
WebRTC

以上看起来更像是布道。下面更全面了解下WebRTC功能特点:
1. WebRTC的视频部分,包含采集、编解码(I420/VP8)、加密、媒体文件、图像处理、显示、网络传输与流控(RTP/RTCP)等功能。在windows平台上,WebRTC采用的是dshow技术,来实现枚举视频的设备信息和视频数据的采集,这意味着可以支持大多数的视频采集设备。视频采集支持多种媒体类型,比如I420、YUY2、RGB、UYUY等,并可以进行帧大小和帧率控制。
2. WebRTC的音频部分,包含设备、编解码(iLIBC/iSAC/G722/PCM16/RED/AVT、NetEQ)、加密、声音文件、声音处理、声音输出、音量控制、音视频同步、网络传输与流控(RTP/RTCP)等功能。在windows平台上,WebRTC采用的是Windows Core Audio和Windows Wave技术来管理音频设备,还提供了一个混音管理器。

写的这么专业,可见凝聚了许多造物者的心血。对视音频处理感兴趣的可以深入一探究竟。
简而言之,WebRTC是中间件的一种实现,它向Web开发与浏览器厂商提供了API。

##工作原理

webrtc 架构
webrtc 架构

##Web开发
接下来介绍一些面向Web开发层面的功能。
WebRTC实现了三个API,分别是:
1. MediaStream:通过MediaStream的API能够通过设备的摄像头及话筒获得视频、音频的同步流
2. RTCPeerConnection:RTCPeerConnection是WebRTC用于构建点对点之间稳定、高效的流传输的组件
3. RTCDataChannel:RTCDataChannel使得浏览器之间(点对点)建立一个高吞吐量、低延时的信道,用于传输任意数据

#MediaStream

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

navigator.getUserMedia(),这个方法接受三个参数:
1. 一个约束对象(constraints object)
2. 一个调用成功的回调函数,如果调用成功,传递给它一个流对象
3. 一个调用失败的回调函数,如果调用失败,传递给它一个错误对象

<!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>

将以上部署到服务器端,在Chrome上可以看到效果(允许对摄像头和麦克风的使用)

#RTCDataChannel
通过这个API这样在实时传输信道上传其他数据。而DataChannel是建立在PeerConnection上的,不能单独使用。

channel = pc.createDataCHannel("someLabel");

使用这个方法在PeerConnection的实例上创建Data Channel,并给与它一个标签。

DataChannel支持一些事件:onopen、onclose、onmessage、onerror。

并且可以通过readyState获取其状态:
1. connecting: 浏览器之间正在试图建立channel
2. open:建立成功,可以使用send方法发送数据了
3. closing:浏览器正在关闭channel
4. closed:channel已经被关闭了

还有两个方法:
1. close(): 用于关闭channel
2. send():用于通过channel向对方发送数据

#RTCPeerConnection
顾名思义,用于创建本地到远端WebRTC的连接。
API传送门
直接上代码了!以下实现了一个获取设备IP的方法,将返回一个promise对象。

var getDeviceLocalIP = function(){
        var w = window;
        return new Promise(function(resolve, reject){
            var ip_dups = {};
            //兼容 firefox、chrome
            var RTCPeerConnection = w.RTCPeerConnection
                || w.mozRTCPeerConnection
                || w.webkitRTCPeerConnection;
            var useWebKit = !!w.webkitRTCPeerConnection;
            //绕过低级webrtc限制
            if(!RTCPeerConnection){
                var iframe = document.createElement('iframe');
                iframe.style.display = 'none';
                iframe.sandbox = 'allow-same-origin';
                iframe.addEventListener("DOMNodeInserted", function(e){
                    e.stopPropagation();
                }, false);
                iframe.addEventListener("DOMNodeInsertedIntoDocument", function(e){
                    e.stopPropagation();
                }, false);
                document.body.appendChild(iframe);
                var win = iframe.contentWindow;
                RTCPeerConnection = win.RTCPeerConnection
                    || win.mozRTCPeerConnection
                    || win.webkitRTCPeerConnection;
                useWebKit = !!win.webkitRTCPeerConnection;
            }
            // 再判断一次是否对象可用
            if(!RTCPeerConnection) {
                reject("not support");
            }
            var mediaConstraints = {
                optional: [{RtpDataChannels: true}]
            };
            //链接火狐的stun server,这个环节在上面的原理图中可以找到对应点
            //我们也可以自建stun服务,先不扩展了,继续
            //    media.peerconnection.default_iceservers =
            //    [{"url": "stun:stun.services.mozilla.com"}]
            var servers = undefined;
            //Chrome同理
            if(useWebKit)
                servers = {iceServers: [{urls: "stun:stun.services.mozilla.com"}]};
            //创建连接对象啦
            var pc = new RTCPeerConnection(servers, mediaConstraints);
            function handleCandidate(candidate){
                //匹配IP,这里通过单步调试可以看到handleCandidate会被多次回调
                //因此要进行过滤
                var ip_regex = /([0-9]{1,3}(\.[0-9]{1,3}){3})/
                var ip_addr = ip_regex.exec(candidate)[1];
                if(ip_dups[ip_addr] === undefined)
                    resolve(ip_addr);
                ip_dups[ip_addr] = true;
            }
            pc.onicecandidate = function(ice){
                if(ice.candidate)
                    handleCandidate(ice.candidate.candidate);
            };
            //创建数据通道
            pc.createDataChannel("");
            //create an offer sdp
            pc.createOffer(function(result){
                //响应stun服务器的请求
                pc.setLocalDescription(result, function(){}, function(){});
            }, function(){});
            //人为延时
            setTimeout(function(){
                var lines = pc.localDescription.sdp.split('\n');
                lines.forEach(function(line){
                    if(line.indexOf('a=candidate:') === 0)
                        handleCandidate(line);
                });
            }, 1000);
        });
    }

//调用该方法。
getDeviceLocalIP().then(function(ip) {
            if (ip.match(/^(192\.168\.|169\.254\.|10\.|172\.(1[6-9]|2\d|3[01]))/)) {
                console.log("Local IP:",ip);
            }
        },function(){
            console.log("something happend");
        });

临时Demo

##应用
-什么?获得了本地IP?
在这之前,通过网页获取本地IP似乎有些困难,于是现在有了一个可行的解决方案。

-搭建视音频聊天
虽然没时间做一个完整的聊天应用,不过从以上API的功能来看,实现起来并不难。

##参考及资源:
WebRTC音视频引擎研究(1)–整体架构分析
WebRTC国内资源站

发表评论

电子邮件地址不会被公开。 必填项已用*标注