ccforward / cc

Code & Blog
1.59k stars 193 forks source link

19.简单了解的 HTML5 WebRTC #20

Open ccforward opened 9 years ago

ccforward commented 9 years ago

About HTML5 的 WebRTC

WebRTC是Web Real-Time Communication(网页实时通信)。WebRTC 包含有三个组件:

  1. 访问用户摄像头及麦克风的 getUserMedia
  2. 穿越 NAT 及防火墙建立视频会话的 PeerConnection
  3. 在浏览器之间建立点对点数据通讯的 DataChannels

分别对应三个API接口:

  1. Network Stream API 代表媒体数据流
  2. RTCPeerConnection 一个RTCPeerConnection对象允许用户在两个浏览器之间直接通讯;
  3. Peer-to-peer Data API 一个在两个节点之间的双向的数据通道

    浏览器对 WebRTC 的支持

最新支持成都可在 caniuse.com 上查询

  1. pc上浏览器的支持情况[pc浏览器对WebRTC的支持情况](http://gtms04.alicdn.com/tps/i4/TB1weeHGpXXXXcDXFXX0etz8VXX-1310-1090.png)
  2. Andriod chrome浏览器从29版开始支持webRTC,但是默认关闭,如果不能正常使用webRTC,请在chrome://flags,开启webRTC[移动端的浏览器支持情况](http://gtms04.alicdn.com/tps/i4/TB1hGuVGpXXXXaiXpXX1mXeGVXX-1134-374.png)
  3. iOS 还不支持,不过苹果很快会支持

    WebRTC API

    • MediaStream
      getUserMedia()与WebRTC相关,因为它是通向这组API的门户。它提供了访问用户本地相机/麦克风媒体流的手段。在移动设备上,目前只有andriod中webview内核为36或者大于36才支持(即只有andriod L或者更高版本),ios设备暂时还不支持。
    • 功能检测
      各家浏览器对getUserMedia支持不同,因此在使用该API之前需要检测用户浏览器是否支持getUserMedia
function hasGetUserMedia() {
    return !!(navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia);
}
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.moZGetUserMedia || navigator.msGetUserMedia;
var video = document.querySelector('video');
navigator.getUserMedia({
    audio : true,
    video : true
    }, function (stream) {
            video.src = window.URL.creatObjectURL(stream);
    }, function (error) {
            console.log(error);
});

这样就可以通过video标签成功将视频流输出到页面上。

var video = document.querySelector('video');
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
navigator.getUserMedia({
     audio: true,
     video: true
}, function(stream) {
     video.src = window.URL.creatObjectURL(stream);
}, function(error) {
     console.log(error);
});
video.addEventListener('click', function() {
     ctx.drawImage(video, 0, 0);
     var img = new Image();
     img.src = canvas.toDataURL('image/png');
     document.appendChild(img);
}, false)
var imgData = ctx.getImageData();
var filter = {
    // 灰度效果
    grayscale: function(pixels) {
        var d = pixels.data;

        for (var i = 0, len = d.length; i < len; i += 4) {
            var r = d[i],
                g = d[i + 1],
                b = d[i + 2];
            d[i] = d[i + 1] = d[i + 2] = (r + g + b) / 3;
        }

        return pixels;
    },

    // 复古效果
    sepia: function(pixels) {
        var d = pixels.data;

        for (var i = 0, len = d.length; i < len; i += 4) {
            var r = d[i],
                g = d[i + 1],
                b = d[i + 2];

            d[i] = (r * 0.393) + (g * 0.769) + (b * 0.189);
            d[i + 1] = (r * 0.349) + (g * 0.686) + (b * 0.168);
            d[i + 2] = (r * 0.272) + (g * 0.534) + (b * 0.131);
        }

        return pixels;
    },

    // 红色蒙版效果
    red: function(pixels) {
        var d = pixels.data;

        for (var i = 0, len = d.length; i < len; i += 4) {
            var r = d[i],
                g = d[i + 1],
                b = d[i + 2];

            d[i] = (r + g + b) / 3;
            d[i + 1] = d[i + 2] = 0;
        }

        return pixels;
    },

    // 反转效果
    invert: function(pixels) {
        var d = pixels.data;

        for (var i = 0, len = d.length; i < len; i += 4) {
            var r = d[i],
                g = d[i + 1],
                b = d[i + 2];

            d[i] = 255 - r;
            d[i + 1] = 255 - g;
            d[i + 2] = 255 - b;
        }
        return pixels;
    }
};
ctx.putImageData(filter[type](imgData));
var range = document.querySelector('input');
window.AudioContext = window.AudioContext || window.webkitAudioContext;
var audioCtx = new AudioContext();
navigator.getUserMedia({
    audio: true
}, function(stream) {
    // 创建音频流
    var source = audioCtx.createMediaStreamSource(stream);
    // 双二阶滤波器
    var biquadFilter = audioCtx.createBiquadFilter();
    biquadFilter.type = 'lowshelf';
    biquadFilter.frequenc.value = 1000;
    biquadFilter.gain.value = range.value;

    source.connect(biquadFilter);
    biquadFilter.connect(audioCtx.destination);
}, function(error) {
    console.log(error);
});

RTCPeerConnection

RTCPeerConnection,用于peer跟peer之间呼叫和建立连接以便传输音视频数据流;

WebRTC是实现peer to peer的实时通信(可以两个或多个peer之间),在能够通信前peer跟peer之间必须建立连接,这是RTCPeerConnection的任务,为此需要借助一个信令服务器(signaling server)来进行,信令包括3种类型的信息:

var PeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
navigator.getUserMedia = navigator.getUserMedia ? "getUserMedia" :
    navigator.mozGetUserMedia ? "mozGetUserMedia" :
    navigator.webkitGetUserMedia ? "webkitGetUserMedia" : "getUserMedia";
var v = document.createElement("video");

// 创建信令
var pc = new PeerConnection();
pc.addStream(video);
pc.createOffer(function(desc) {
    pc.setLocalDescription(desc, function() {
        // send the offer to a server that can negotiate with a remote client
    });
})

// 创建回复
var pc = new PeerConnection();
pc.setRemoteDescription(new RTCSessionDescription(offer), function() {
    pc.createAnswer(function(answer) {
        pc.setLocalDescription(answer, function() {
            // send the answer to the remote connection
        });
    });
})

peer跟peer之间一旦建立连接就可以直接传输音视频数据流,并不需要借助第三方服务器中转。
具体文档可以查看:https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection。

RTCDataChannel

RTCDataChannel可以建立浏览器之间的点对点通讯。常用的通讯方式有webSocket, ajax和 Server Sent Events等方式,websocket虽然是双向通讯,但是无论是websocket还是ajax都是客户端和服务器之间的通讯,这就意味着你必须配置服务器才可以进行通讯。而RTCDATAChannel采用另外一种实现方式

WebRTC并未规定使用何种信令机制和消息协议,象SIP、XMPP、XHR、WebSocket这些技术都可以用作WebRTC的信令通信。
除了信令服务器,peer跟peer建立连接还需要借助另一种服务器(称为STUN server)实现NAT/Firewall穿越,因为很多peer是处于私有局域网中,使用私有IP地址,必须转换为公有IP地址才能相互之间传输数据。这其中涉及到一些专业术语包括STUN、TURN、ICE等,其实我对这些概念也不是很理解。网上找到的WebRTC demo好象都用的是Google提供的STUN server。

参考文章:http://www.html5rocks.com/en/tutorials/webrtc/datachannels/?redirect_from_locale=zh
https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel

WebRTC的目的是为了简化基于浏览器的实时数据通信的开发工作量,但实际应用编程还是有点复杂,尤其调用RTCPeerConnection必须对如何建立连接、交换信令的流程和细节有较深入的理解。因此我们可以使用已经封装好的WebRTC库,这些WebRTC库对原生的webRTC的API进行进一步的封装,包装成更简单的API接口。同时屏蔽了不同浏览器之间的差异。
目前网上主要有两种WebRTC的封装库:

最后是用WebRTC写的一个小demo:https://ccforward.github.io/demos/webrtc/index.html

关于 WebRTC 的相关文章推荐:http://www.html5rocks.com/en/tutorials/webrtc/basics/

lgyjg commented 7 years ago

总结翻译的不错,有些小问题开发中还真没有注意过