[译] 用HTML5捕获音频和视频

原文地址:http://www.html5rocks.com/en/tutorials/getusermedia/intro/

概述

有了HTML5,我们就可以在不借助Flash或者Silverlight的情况下完成这项工作了。

HTML5能够使我们访问设备的硬件,比如GPS,WebGL等等。

这篇文章,我们就来看看一个新的API——navigator.getUserMedia(),她允许网页应用去访问用户的摄像机和麦克风。

getUserMedia之路

第一阶段:HTML Media Capture

当我们设置<input type="file">标签,并设置此标签的accept属性,她就可以正常的工作了。

<input type="file" accept="image/*;capture=camera"> <!--拍照-->
<input type="file" accept="video/*;capture=camcorder"> <!--摄像-->
<input type="file" accept="audio/*;capture=micophone"> <!--录音-->

这个"API"的短板之处在于她做实时处理的能力比较弱,比如用canvas来渲染和应用于WebGL.

HTML Media Capture只允许你去及时的记录视频和拍照。

第二阶段:device element

很多人认为HTML Media Capture太有局限性了,所以一个强大的支持所有设备的元素出现了。好不惊奇的,她的名字就是<device>,她就是getUserMedia要处理的对象。

Opera、WhatWG、Microsoft等都相继实现此元素与API。

<device>看起来如下:

<device type="media" onchange="update(this.data)"></device>
<video autoplay></video>
<script>
    function update(stream){
        document.querySelector('video').src = stream.url;
    }
</script>

不幸的是,没有发布的浏览器支持此标签。

尽管如此她还是有两个好处的:

  1. 语义化
  2. 高扩展性不仅仅是audio/video.

第三阶段:WebRTC

<device>最终还是流产了。

多亏了WebRTC才让需找合适的API的步伐得意继续。

现在的getUserMedia()就是参考的WebRTC,因为这是通往一套API的桥梁。她提供了访问用户本地摄像机和麦克风的能力。

正式开始

特性检查

在使用之前我们应该检查一下我们的设备是否支持此方法。

function hasGetUserMedia(){
    return !!(navigator.getUserMedia || navigator.webkitGetUserMedia ||
              navigator.mozGetUserMedia || navigator.msGetUserMedia);
}
if(hasGetUserMedia()){
    alert('Good to go!');
}else{
    alert('Not supported!');
}

获得访问设备的权限

我们应当发送允许我们使用设备的请求。getUserMedia的第一个参数是一个对象,保存的是我们想要请求的设备。例如,如果我们想用摄像机和麦克风,第一个参数就是{video: true, audio: ture};

<video autoplay></video>
<script>
    var errorCallback = function(e){
        console.log('Reeeejected!');
    };
    navigator.getUserMedia = navigator.getUserMedia ||
                             navigator.webkitGetUserMedia ||
                             navigator.mozGetUserMedia ||
                             navigator.msGetUserMedia;
    if(!!navigator.getUserMedia){ //good to go
        console.log('Not Suppoted!');
        navigator.getUserMedia({video: true, audio: ture}, function(localMediaStream){
            var video = document.querySelector('video');
            video.src = window.URL.createObjectURL(localMediaStream);

            video.onloadedmetadata = function(e){
                //Do some stuff
            };
        }, errorCallback);
    }
</script>

定制media

getUserMedia的第一个参数也可以被用实现高清等。

var hdConstraints = {
    video: {
        minWidth: 1280,
        minHeight: 720
    }
}
navigator.getUserMedia(hdConstraints, successCallback, errorCallback);

var vgaConstraints = {
    video: {
        mandatory: {
            maxWidth: 640,
            maxHeight: 360
        }
    }
}
navigator.getUserMedia(vgaConstraints, successCallback, errorCallback);

选择媒体源

在Chrome 30之后,我们可以使用MediaStreamTrack.getSources() API来选择video/audio的源。

MediaStreamTrack.getSources(function(sourceInfos){
    var audioSource = null;
    var videoSource = null;

    for(var i=0; i!=sourceInfos.length; ++i){
        var souceInfo = sourceInfos[i];
        if(sourceInfo.kind === 'audio'){
            console.log(sourceInfo.id, sourceInfo.label || 'microphone');
            audioSource = sourceInfo.id;
        }else if(sourceInfo.kind === 'video'){
            console.log(sourceInfo.id, sourceInfo.label || 'camera');
            videoSource = sourceInfo.id;
        }else{
            console.log('Some other kind of source: ', sourceInfo);
        }
    }
    sourceSelected(audioSource, videoSource);
});
function sourceSelected(audioSource, videoSource){
    var constraints = {
        audio: {
            optional: [{sourceId: audioSource}]
        },
        video: {
            optional: [{sourceId: videoSource}]
        }
    };
    navigator.getUserMedia(constraints, successCallback, errorCallback);
}

安全

当我们调用getUserMedia()时,浏览器会弹出一个对话框来让用户决定接下怎么做,不幸的是,当我们的应用使用的是https时,这将会被拒绝。

后备方法

对用不支持getUserMedia的用户来说,我们可以指定一个存在的video或者抛出错误提示。

function fallback(e){
    video.src = 'fallbackvideo.webm';
}
function success(stream){
    video.src = window.URL.createObjectURL(stream);
}

if(!navigator.getUserMedia){
    fallback();
}else{
    navigator.getUserMedia({video: true}, success, fallback);
}

 

屏幕截图

我们可以使用<canvas>捕获<video>的某一帧,这样就可以实现截图了。

<video autoplay></video>
<img src="">
<canvas style="display:none;"></canvas>
<script>
var video = document.querySelector('video');
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
var localMediaStream = null;

function snapshot(){
    if(localMediaStream){
        ctx.drawImage(video, 0, 0);
        document.querySelector('img').src = canvas.toDataURL('image/webp');
    }
}

video.addEventListener('click', snapshot, false);

navigator.getUserMedia({video: true}, function(stream){
    video.src = window.URL.createObjectURL(stream);
    localMediaStream = stream;
}, errorCallback);
</script> 

应用样式

使用css过滤器

<style>
video {
  width: 307px;
  height: 250px;
  background: rgba(255,255,255,0.5);
  border: 1px solid #ccc;
}
.grayscale {
  +filter: grayscale(1);
}
.sepia {
  +filter: sepia(1);
}
.blur {
  +filter: blur(3px);
}
...
</style>

<video autoplay></video>

<script>
var idx = 0;
var filters = ['grayscale', 'sepia', 'blur', 'brightness',
               'contrast', 'hue-rotate', 'hue-rotate2',
               'hue-rotate3', 'saturate', 'invert', ''];

function changeFilter(e) {
  var el = e.target;
  el.className = '';
  var effect = filters[idx++ % filters.length]; // loop through filters.
  if (effect) {
    el.classList.add(effect);
  }
}

document.querySelector('video').addEventListener(
    'click', changeFilter, false);
</script>

WebGL纹理

有兴趣的可以参考http://learningthreejs.com/blog/2012/02/07/live-video-in-webgl/ 这篇文章。

使用Audio API

window.AudioContext = window.AudioContext ||
                      window.webkitAudioContext;

var context = new AudioContext();

navigator.getUserMedia({audio: true}, function(stream) {
  var microphone = context.createMediaStreamSource(stream);
  var filter = context.createBiquadFilter();

  // microphone -> filter -> destination.
  microphone.connect(filter);
  filter.connect(context.destination);
}, errorCallback);

 

posted @ 2014-04-16 16:19  JChen___  阅读(1132)  评论(0编辑  收藏  举报