奥门新浦京官方网站getUserMedia API及HTML5 调用摄像头和麦克风

本文由码农网 –
杨勇原创,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划!

时间: 2019-01-04阅读: 1459标签: 摄像getUserMedia API简介

应该有很多人知道,我们的手机里面有个功能是“抓拍入侵者”,说白了就是在解锁应用时如果我们输错了密码手机就会调用这一功能实现自动拍照。

1. 项目背景

奥门新浦京官方网站 ,公司开发一个网站,在做用户头像修改的时候领导提到增加一个由摄像头拍照实现修改头像的功能。因为我们网站是基于Html5进行开发,所以就直接采用H5来实现拍照。起初觉得这个功能很简单,但是做的时候才发现并不是那么简单的。

奥门新浦京官方网站 1

这是在AngularJs中成功实现调用摄像头拍照并截图上传的例图:

奥门新浦京官方网站 2

奥门新浦京官方网站 3

HTML5的getUserMedia
API
为用户提供访问硬件设备媒体(摄像头、视频、音频、地理位置等)的接口,基于该接口,开发者可以在不依赖任何浏览器插件的条件下访问硬件媒体设备。

其实在手机上还有很多我们常用的软件都有类似于这样的功能,比如微信扫一扫二维码,玩图……

2. 如何调用摄像头

$scope.photoErr = false;
$scope.photoBtnDiable = true;
var mediaStream = null,track = null;

navigator.getMedia = (navigator.getUserMedia ||
                      navigator.webkitGetUserMedia || navigator.mozGetUserMedia ||
                      navigator.msGetUserMedia);
        if (navigator.getMedia) {
            navigator.getMedia(
           {
               video: true
           },
           // successCallback
           function (stream) {
               var s = window.URL.createObjectURL(stream);
               var video = document.getElementById('video');
               video.src = window.URL.createObjectURL(stream);
               mediaStream = stream;
               track = stream.getTracks()[0];
               $scope.photoBtnDiable = false;               $scope.$apply();
           },
           // errorCallback
           function (err) {
               $scope.errorPhoto();
               console.log("The following error occured:" + err);
           });
              } else {
            $scope.errorPhoto();
        }

代码解析:

navigator为浏览器对象,包含浏览器的信息,这里就是用这个对象打开摄像头。$scope为AndularJs语法。第一步声明navigator.getMedia来调用浏览器不同的打开摄像头函数,目前仅有getUserMedia、webkitGetUserMedia、mozGetUserMedia、msGetUserMedia四种方式分别对应通用浏览器、Google浏览器、火狐浏览器和IE浏览器,浏览器会自动判断调用哪一个函数。第二步是调用打开浏览器,包含三个参数,分别为需要使用的多媒体类型、获取成功返回的流数据处理函数以及操作失败返回错误消息处理函数。其中,使用时不仅可以设置视频还能设置使用麦克风,设置方式为:

{
      video: true,
      audio: true
}

调用成功即打开摄像头后返回视频流数据,我们可以将流数据设置到video标签在界面上实时显示图像。mediaStream用来记录获取到的流数据,track在Chrome浏览器中用来跟踪摄像头状态,这两个变量都能用来关闭摄像头。

getUserMedia
API最初是navigator.getUserMedia,目前已被最新Web标准废除,变更为navigator.mediaDevices.getUserMedia(),但浏览器支持情况不如旧版API普及。

感觉挺有趣的,今天,我们就来研究一下这个功能的原理,不过我们不是用Android来做,而是用HTML5和javascript来做,浏览器支持IE9+。

3. 拍照

$scope.snap = function () {
        var canvas = document.createElement('canvas');
            canvas.width = "400";
            canvas.height = "304";

            var ctx = canvas.getContext('2d');
            ctx.drawImage(video, 0, 0, 400, 304);
            $scope.closeCamera();
            $uibModalInstance.close(canvas.toDataURL("image/png"));
};

拍照时需要使用到canvas标签,创建一个canvas标签,设置我们需要拍照的尺寸大小,通过drawImage函数将video当前的图像保存到canvas标签,最后将图像数据转换为base64数据返回并关闭摄像头,这样就完成了我们的拍照功能。这里的$uibModalInstance对象是我们项目中打开弹出层的一个对象,用来控制弹出层的显示。

MediaDevices.getUserMedia()方法提示用户允许使用一个视频和/或一个音频输入设备,例如相机或屏幕共享和/或麦克风。如果用户给予许可,就返回一个Promise对象,MediaStream对象作为此Promise对象的Resolved[成功]状态的回调函数参数,相应的,如果用户拒绝了许可,或者没有媒体可用的情况下PermissionDeniedError或者NotFoundError作为此Promise的Rejected[失败]状态的回调函数参数。注意,由于用户不会被要求必须作出允许或者拒绝的选择,所以返回的Promise对象可能既不会触发resolve也不会触发
reject。

布局很简单,就是设置一个“拍照”按钮的监听器,调用摄像头video,然后显示出来画像。(需要用户权限)

4. 如何关闭摄像头

$scope.closeCamera = function () {
            if (mediaStream != null) {
                if (mediaStream.stop) {
                    mediaStream.stop();
                }
                $scope.videosrc = "";
            }
            if (track != null) {
                if (track.stop) {
                    track.stop();
                }
            }
        }

正如前面所说,关闭摄像头的方式是通过mediaStream和track变量,只不过,track只能关闭Chrome浏览器中的摄像头,这也是Chrome 45版本以上关闭摄像头的方式。

浏览器兼容性语法

首先,我们要允许网页宽度自动调整,我们在网页头添加viewport标签:

5. 集成到AndularJs

事实上,前面所说的都是在AndularJs中实现的,当然,这里只是实现了拍照并返回拍照数据,我们想要在其他地方也使用,就需要将这部分独立出来,这里我们用到了AngularJs中的service机制,将这部分单独做成一个service并在项目中注入,然后就可以在其他地方调用了。

service注册:

app().registerService("h5TakePhotoService", function ($q, $uibModal) {

        this.photo = function () {
            var deferred = $q.defer();
            require([config.server + "/com/controllers/photo.js"], function () {
                $uibModal.open({
                    templateUrl: config.server + "/com/views/modal_take_photo.html",
                    controller: "photoModalController",
                    windowClass: "modal-photo"
                }).result.then(function (e) {
                    deferred.resolve(e);
                });
            });
            return deferred.promise;
        }

    });

调用方式:

$scope.takePhoto = function () {
      h5TakePhotoService.photo().then(function (res) {
           if (res != null && res != "") {
               $scope.myImage = res;
           }
      });
}

h5TakePhotoService为控制器中注入的拍照service对象,最后处理返回的图像数据,设置数据显示到界面上。

navigator.mediaDevices.getUserMedia(constraints).then(function(mediaStream) { ... }).catch(function(error) { ... })
<meta name="viewport" content="width=device-width, initial-scale=1" />

6. 兼容问题

主要存在Chrome浏览器中,本地测试时,Chrome浏览器中能够正常使用,但是部署到服务器后就不能正常使用,报错消息为 [object NavigatorUserMediaError],这是因为Chrome浏览器在使用摄像头时只支持安全源访问,所以只能通过https访问才能正常使用。

最后需要说一下,测试时只能通过 Studio、 java web、php中完成。

参数

还有一个重点知识,就是getUserMedia(获取用户多媒体)。

containers:指定请求的媒体类型,主要包含video和audio,必须至少一个类型或者两个同时可以被指定。如果浏览器无法找到指定的媒体类型或者无法满足相对应的参数要求,那么返回的Promise对象就会处于rejected[失败]状态,NotFoundError作为rejected[失败]回调的参数。

使用这个getUserMedia API可以访问多媒体设备,利用该API与<video>和canvas元素,可以在浏览器里面捕获许多漂亮的图片。

【例】同时请求不带任何参数的音频和视频:

所以除了视频,还有音频,所以接口要变成类似{video:
true,audio:false},可以设定音视频的获取开关。

{ audio: true, video: true }

 navigator对象包含的属性描述了正在使用的浏览器,可以使用这些属性进行平台专用的配置。

【例】使用1280×720的摄像头分辨率:

如果浏览器检测不到摄像头,则会提示:

{ audio: true, video: { width: 1280, height: 720 }}

奥门新浦京官方网站 4

【例】要求获取最低为1280×720的分辨率:

That’s all right,我们直接走起看Demo吧:

{ audio: true, video: { width: { min: 1024, ideal: 1280, max: 1920 }, height: { min: 776, ideal: 720, max: 1080 } }}

<!DOCTYPE html> <head> <title>HTML5 GetUserMedia
Demo</title> <meta charset=”utf-8″> <meta name=”viewport”
content=”width=device-width, initial-scale=1.0, maximum-scale=1.0″ />
</head> <body> <input type=”button” title=”开启摄像头”
value=”开启摄像头” onclick=”getMedia();” /><br /> <video
height=”120px” autoplay=”autoplay”></video><hr />
<input type=”button” title=”拍照” value=”拍照” onclick=”getPhoto();”
/><br /> <canvas id=”canvas1″ height=”120px”
></canvas><hr /> <input type=”button” title=”视频”
value=”视频” onclick=”getVedio();” /><br /> <canvas
id=”canvas2″ height=”120px”></canvas> <script
type=”text/javascript”> var video = document.querySelector(‘video’);
var audio, audioType; var canvas1 = document.getElementById(‘canvas1’);
var context1 = canvas1.getContext(‘2d’); var canvas2 =
document.getElementById(‘canvas2’); var context2 =
canvas2.getContext(‘2d’); navigator.getUserMedia =
navigator.getUserMedia || navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia || navigator.msGetUserMedia; window.URL =
window.URL || window.webkitURL || window.mozURL || window.msURL; var
exArray = []; //存储设备源ID MediaStreamTrack.getSources(function
(sourceInfos) { for (var i = 0; i != sourceInfos.length; ++i) { var
sourceInfo = sourceInfos[i]; //这里会遍历audio,video,所以要加以区分
if (sourceInfo.kind === ‘video’) { exArray.push(sourceInfo.id); } } });
function getMedia() { if (navigator.getUserMedia) {
navigator.getUserMedia({ ‘video’: { ‘optional’: [{ ‘sourceId’:
exArray[1] //0为前置摄像头,1为后置 }] }, ‘audio’:true },
successFunc, errorFunc); //success是获取成功的回调函数 } else {
alert(‘Native device media streaming (getUserMedia) not supported in
this browser.’); } } function successFunc(stream) { //alert(‘Succeed to
get media!’); if (video.mozSrcObject !== undefined) {
//Firefox中,video.mozSrcObject最初为null,而不是未定义的,我们可以靠这个来检测Firefox的支持
video.mozSrcObject = stream; } else { video.src = window.URL &&
window.URL.createObjectURL(stream) || stream; } //video.play(); // 音频
audio = new Audio(); audioType = getAudioType(audio); if (audioType) {
audio.src = ‘polaroid.’ + audioType; audio.play(); } } function
errorFunc(e) { alert(‘Error!’+e); } //
将视频帧绘制到Canvas对象上,Canvas每60ms切换帧,形成肉眼视频效果 function
drawVideoAtCanvas(video,context) { window.setInterval(function () {
context.drawImage(video, 0, 0,90,120); }, 60); } //获取音频格式 function
getAudioType(element) { if (element.canPlayType) { if
(element.canPlayType(‘audio/mp4; codecs=”mp4a.40.5″‘) !== ”) { return
(‘aac’); } else if (element.canPlayType(‘audio/ogg; codecs=”vorbis”‘)
!== ”) { return (“ogg”); } } return false; } //
vedio播放时触发,绘制vedio帧图像到canvas //
video.addEventListener(‘play’, function () { // drawVideoAtCanvas(video,
context2); // }, false); //拍照 function getPhoto() {
context1.drawImage(video, 0, 0,90,120);
//将video对象内指定的区域捕捉绘制到画布上指定的区域,实现拍照。 } //视频
function getVedio() { drawVideoAtCanvas(video, context2); }
</script> </body>

当请求包含一个ideal(应用最理想的)值时,这个值有着更高的权重,意味着浏览器会先尝试找到最接近指定的理想值的设定或者摄像头(如果设备拥有不止一个摄像头)。

【例】优先使用前置摄像头(如果有的话):

{ audio: true, video: { facingMode: "user" } }

【例】强制使用后置摄像头

{ audio: true, video: { facingMode: { exact: "environment" } } }

成功回调函数seccessCallback的参数stream:stream是MediaStream的对象,表示媒体内容的数据流,可以通过URL.createObjectURL转换后设置为Video或Audio元素的src属性来使用,部分较新的浏览器也可以直接设置为srcObject属性来使用。

注意:新版的谷歌浏览器不能直接将MediaStream对象直接作为URL.createObjectURL的参数使用,会报TypeError
Failed to execute ‘createObjectURL’ on ‘URL’: No function was found that
matched the signature provided的错误,具体用法在后面说明。

失失败回调函数errorCallback的参数error,可能的异常有:

AbortError:硬件问题NotAllowedError:用户拒绝了当前的浏览器实例的访问请求;或者用户拒绝了当前会话的访问;或者用户在全局范围内拒绝了所有媒体访问请求。NotFoundError:找不到满足请求参数的媒体类型。NotReadableError:操作系统上某个硬件、浏览器或者网页层面发生的错误导致设备无法被访问。OverConstrainedError:指定的要求无法被设备满足。SecurityError:安全错误,在getUserMedia()
被调用的
Document上面,使用设备媒体被禁止。这个机制是否开启或者关闭取决于单个用户的偏好设置。TypeError:类型错误,constraints对象未设置[空],或者都被设置为false。示例:HTML
5调用媒体设备摄像头

这个例子中,请求访问用户硬件设备的摄像头,并把视频流通过Video元素显示出来。网页中提供一个”拍照”的按钮,通过Canvas将Video的画面截取并绘制,核心代码如下:

HTML

!--video用于显示媒体设备的视频流,自动播放--video autoplay /video!--拍照按钮--divbutton 拍照/button/div!--描绘video截图--canvas width="480" height="320"/canvas

JavaScript

//访问用户媒体设备的兼容方法function getUserMedia(constrains,success,error){ if(navigator.mediaDevices.getUserMedia){ //最新标准API navigator.mediaDevices.getUserMedia(constrains).then(success).catch(error); } else if (navigator.webkitGetUserMedia){ //webkit内核浏览器 navigator.webkitGetUserMedia(constrains).then(success).catch(error); } else if (navigator.mozGetUserMedia){ //Firefox浏览器 navagator.mozGetUserMedia(constrains).then(success).catch(error); } else if (navigator.getUserMedia){ //旧版API navigator.getUserMedia(constrains).then(success).catch(error); }}var video = document.getElementById("video");var canvas = document.getElementById("canvas");var context = canvas.getContext("2d");//成功的回调函数function success(stream){ //兼容webkit内核浏览器 var CompatibleURL = window.URL || window.webkitURL; //将视频流设置为video元素的源 video.src = CompatibleURL.createObjectURL(stream); // 此处的代码将会报错 解决的办法是将video的srcObject属性指向stream即可 //播放视频 video.play();}//异常的回调函数function error(error){ console.log("访问用户媒体设备失败:",error.name,error.message);}if (navigator.mediaDevices.getUserMedia || navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia){ //调用用户媒体设备,访问摄像头 getUserMedia({ video:{width:480,height:320} },success,error);} else { alert("你的浏览器不支持访问用户媒体设备");}//注册拍照按钮的单击事件document.getElementById("capture").addEventListener("click",function(){ //绘制画面 context.drawImage(video,0,0,480,320);});

关闭摄像头或者麦克风:需要注意的是,MediaStream.getTracks()返回的Tracks数组是按第一个参数倒序排列的

比如现在定义了

{ video: true, audio: true}

想关闭摄像头,就需要调用MediaStream.getTracks()[1].stop();

同理,0对应于audio的track

进阶

对本示例进行功能加强,比如使用CSS 3 的滤镜实现模糊、黑白等效果。

麦克风

因为纯粹用一个audio标签来播放麦克风拾取到的声音显得太没特色了,于是我用到了以前写的一个音频可视化库Vudio.js,代码如下:

创建一个canvas来显示音频波形图

canvas /canvas

通过Vudio.js和getUserMedia来显示麦克风拾取到的音频的波形

var canvas = document.querySelector('#canvas')navigator.mediaDevices.getUserMedia({ audio: true}).then((stream) = { // 调用Vudio var vudio = new Vudio(stream, canvas, { accuracy: 256, width: 1024, height: 200, waveform: { fadeSide: false, maxHeight: 200, verticalAlign: 'middle', horizontalAlign: 'center', color: '#2980b9' } }) vudio.dance()}).catch((error) = { console.error(error.name || error)})

vudio.js源码:

效果如下图所示:

来源:

发表评论

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