zoukankan      html  css  js  c++  java
  • 【译文】纯HTML5捕获音频流和视频流

    原文地址:Capturing Audio & Video in HTML5

    前言

    长期以来音视频捕获一直是web开发的珍宝。多年来,我们不得不依赖浏览器插件( flash 或者 silverlight )来完成这个工作.

    HTML5完成了救赎。可能并不明显,但是HTML5的兴起带来了大量对这杯硬件的访问。Geolocation(GPS)、Orientation API(加速度计)、WebGL(GPU)和Web Audio (音频硬件)都是完美的例子。这些功能非常强大,暴露了位于系统底层硬件功能之上的高级JS api。

    本篇教程介绍 navigator.mediaDevices.getUserMedia() ,这使得web应用能够访问用户的摄像头和麦克风.

    探寻getUserMedia() 之路

    如果你不清楚这段历史,那我们这段探寻之旅将是一个非常有趣的故事。

    在过去的几年中, "Media Capture APIs" 的几个变种都得到了发展。许多人意识到在web上需要访问本设备,而这出事了每一个人共同制定一套新规范。事情变得很混乱(每个浏览器都有自己的规范),W3C最终决定成立一个工作组。他们唯一的目的就是去掉混乱。Device APIs Policy (DAP) 工作组已经把整合化和标准化大量提案作为他们的任务。

    我将会尝试总结下2011年发生的事情。。。

    第一轮 HTML Media Capture

    HTML Media Capture 是DAP首次在web上标准化如何访问多媒体。它通过重载 <input type="file"> 标签并新增accept参数的值来实现.

    如果你想让用户使用网络摄像头来获取一个快照,可以使用 capture=camera

    <input type="file" accept="image/*;capture=camera">
    

    录制视频和视频也是类似的:

    <input type="file" accept="video/*;capture=camcorder">
    <input type="file" accept="audio/*;capture=microphone">
    

    看起来不错吧?我特别喜欢它重用文件输入。从语义上来说很好理解。这个api的不足之处是实时效果(比如:渲染实时网络摄像头数据到 <canvas> 并应用到 WebGL过滤器).HTML Media Capture仅允许你录制和获取快照(无法进行实时渲染).

    第二轮 device 元素标签

    许多人认为 HTML Media Capture api过于局限,因此出现了支持任何类型(未来)设备的新规范。该设计不意外的使用了新标签 ,该标签是 getUserMedia() 的前身。

    Opera是最早一批基于 <device> 标签实现视频捕获的浏览器厂商之一。不久之后(准确来说是同一天),WhatWG (网页超文本技术工作小组)决定取消 device 标签转向另一个方案:navigator.getUserMedia() 。一个星期后,Opera发布了支持getUserMedia()提案的新版本(这迭代速度刚刚的。。。)从那年以后,微软也加入了新提案的大家庭:IE9 实验室版支持新特性.

    <device>就长这样:

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

    支持

    不幸的是,浏览器正式版中一直没有支持过 <device><device>有两个好处:1. 语义化良好 2. 更容易去扩展而不仅仅支持音频和视频设备。

    第三轮 WebRTC

    <device>标签最终被放弃了。。。

    由于WebRTC(web实时通信)的努力使得找到合适api的速度加快了。这个规范由 W3C WebRTC小组监督。谷歌,Opera,火狐和一些公司已经实现。

    getUserMedia()自从Chrome 21,Opera 18和Firefox 17开始支持。最初由Navigator.getUserMedia()提供支持的方法现在已经被废除。你现在应该使用navigator.MediaDevices.getUserMedia(),这个方法普遍都支持。

    有了getUserMedia,我们无需使用任何插件就可使用网络摄像头和麦克风输入。访问摄像头只需要一个调用,而不是安装插件。它直接被内置在浏览器中。有没有点小激动!

    功能检测

    对 navigator.mediaDevices.getUserMedia 是否支持进行简单检查:

    function hasGetUserMedia() {
      return !!(navigator.mediaDevices &&
        navigator.mediaDevices.getUserMedia);
    }
    
    if (hasGetUserMedia()) {
      // Good to go!
    } else {
      alert('getUserMedia() is not supported by your browser');
    }
    

    获取一个输入设备的访问权限

    为了使用摄像头和麦克风,我们需要申请权限。getUserMedia()的参数是一个对象:对每一种你想访问的每一种多媒体说明了具体和要求。比如,如果你想访问摄像头,参数应该为 {video: true}。同时使用摄像头和麦克风,传入 {video: true, audio: true}:

    <video autoplay></video>
    
    <script>
    const constraints = {
      video: true
    };
    
    const video = document.querySelector('video');
    
    navigator.mediaDevices.getUserMedia(constraints).
      then((stream) => {video.srcObject = stream});
    </script>
    

    ok.现在感觉怎么样。。。媒体捕获是HTML5协同工作的完美示例。它可以和其他HTML5小伙伴一起工作:

    我们告诉为autoplay,不让它会在第一帧的时候卡住。你也可向vedit添加控件。

    设置媒体限制(解析度,宽,高)

    传给getUserMedia()的参数可以用来对返回的媒体流做更多要求(或者限制)。比如,不仅仅指明你想访问vedio(比如{vedio: true),你还可以添加额外的配置要求返回HD的流:

    const hdConstraints = {
      video: { {min: 1280}, height: {min: 720}}
    };
    
    navigator.mediaDevices.getUserMedia(hdConstraints).
      then((stream) => {video.srcObject = stream});
    
    ...
    
    const vgaConstraints = {
      video: { {exact: 640}, height: {exact: 480}}
    };
    
    navigator.mediaDevices.getUserMedia(vgaConstraints).
      then((stream) => {video.srcObject = stream});
    

    如果当前被选择的摄像头不满足解析度的要求,getUserMedia()将会被rejected一个OverconstrainedError,并且不会向用户申请访问摄像头的提示。

    选择媒体源

    navigator.mediaDevices.enumerateDevices() 方法提供了可用的输入、输出设备的信息,并可以选择相机和麦克风(注:MediaStreamTrack.getSources() api已经被弃用)

    这个例子允许用户选择音视频源:

    const videoElement = document.querySelector('video');
    const audioSelect = document.querySelector('select#audioSource');
    const videoSelect = document.querySelector('select#videoSource');
    
    navigator.mediaDevices.enumerateDevices()
      .then(gotDevices).then(getStream).catch(handleError);
    
    audioSelect.onchange = getStream;
    videoSelect.onchange = getStream;
    
    function gotDevices(deviceInfos) {
      for (let i = 0; i !== deviceInfos.length; ++i) {
        const deviceInfo = deviceInfos[i];
        const option = document.createElement('option');
        option.value = deviceInfo.deviceId;
        if (deviceInfo.kind === 'audioinput') {
          option.text = deviceInfo.label ||
            'microphone ' + (audioSelect.length + 1);
          audioSelect.appendChild(option);
        } else if (deviceInfo.kind === 'videoinput') {
          option.text = deviceInfo.label || 'camera ' +
            (videoSelect.length + 1);
          videoSelect.appendChild(option);
        } else {
          console.log('Found another kind of device: ', deviceInfo);
        }
      }
    }
    
    function getStream() {
      if (window.stream) {
        window.stream.getTracks().forEach(function(track) {
          track.stop();
        });
      }
    
      const constraints = {
        audio: {
          deviceId: {exact: audioSelect.value}
        },
        video: {
          deviceId: {exact: videoSelect.value}
        }
      };
    
      navigator.mediaDevices.getUserMedia(constraints).
        then(gotStream).catch(handleError);
    }
    
    function gotStream(stream) {
      window.stream = stream; // make stream available to console
      videoElement.srcObject = stream;
    }
    
    function handleError(error) {
      console.error('Error: ', error);
    

    访问Sam Dutton's great demo,这个例子显示了如何让用户选择媒体源。

    安全

    getUserMedia() 只能在HTTPS URL,localhost 或者 file://URL下使用,其他情况会被rejected掉。getUserMedia()也不能在跨域的iframe中使用。

    所有的浏览器在调用getUserMedia()是都会弹出一个信息框,让用户可以选择授权或者拒绝其摄像头或者麦克风的使用权限。下图是chrome的权限弹框:

    这个授权是永久的。也就是说,用户不用每次去授权(决绝/允许)。如果用户后来改变了主意,他们可以浏览器设置中心针对每一个域进行设置。

    贴士:摄像头在使用的情况下,MediaStreamTrack处于激活, 这会占用资源,并且打开摄像头(保持摄像头灯打开),当你不再使用track的时候,确保调用 track.stop() 来使摄像头关闭。

    截屏

    api可以调用ctx.drawImage(video, 0, 0) 方法来绘制的帧到上。当然,我们通过getUserMedia()获得视频输入,使用实时视频创建图片phone booth引用很简单。

    <video autoplay></video>
    <img src="">
    <canvas style="display:none;"></canvas>
    
    <script>
    const captureVideoButton =
      document.querySelector('#screenshot .capture-button');
    const screenshotButton = document.querySelector('#screenshot-button');
    const img = document.querySelector('#screenshot img');
    const video = document.querySelector('#screenshot video');
    
    const canvas = document.createElement('canvas');
    
    captureVideoButton.onclick = function() {
      navigator.mediaDevices.getUserMedia(constraints).
        then(handleSuccess).catch(handleError);
    };
    
    screenshotButton.onclick = video.onclick = function() {
      canvas.width = video.videoWidth;
      canvas.height = video.videoHeight;
      canvas.getContext('2d').drawImage(video, 0, 0);
      // Other browsers will fall back to image/png
      img.src = canvas.toDataURL('image/webp');
    };
    
    function handleSuccess(stream) {
      screenshotButton.disabled = false;
      video.srcObject = stream;
    }
    </script>
    

    应用效果

    css滤镜

    使用css滤镜,我们可以在上捕获的使用一些css效果.

    <video autoplay></video>
    <p><button class="capture-button">Capture video</button>
    <p><button id="cssfilters-apply">Apply CSS filter</button></p>
    
    <script>
    const captureVideoButton =
      document.querySelector('#cssfilters .capture-button');
    const cssFiltersButton =
      document.querySelector('#cssfilters-apply');
    const video =
      document.querySelector('#cssfilters video');
    
    let filterIndex = 0;
    const filters = [
      'grayscale',
      'sepia',
      'blur',
      'brightness',
      'contrast',
      'hue-rotate',
      'hue-rotate2',
      'hue-rotate3',
      'saturate',
      'invert',
      ''
    ];
    
    captureVideoButton.onclick = function() {
      navigator.mediaDevices.getUserMedia(constraints).
        then(handleSuccess).catch(handleError);
    };
    
    cssFiltersButton.onclick = video.onclick = function() {
      video.className = filters[filterIndex++ % filters.length];
    };
    
    function handleSuccess(stream) {
      video.srcObject = stream;
    }
    </script>
    

    WebGL纹理

    视频捕获一个神奇的应用就是作为一个WegGL纹理实时渲染。因为我对WebGL一无所知(除了它很牛逼),所以我建议你们看一个教程示例。它讲了如何在WebGL里使用getUserMedia() 和Three.js去渲染实时vedio。

    对web audio api使用getUserMedia

    我的梦想之一就是在浏览器中构建autotune(不知道啥意思),而不是开放的web技术。

    chrome支持从getUserMedia()到web audio api的实时麦克风输入,以实现实时效果。

    window.AudioContext = window.AudioContext ||
                          window.webkitAudioContext;
    
    const context = new AudioContext();
    
    navigator.mediaDevices.getUserMedia({audio: true}).
      then((stream) => {
        const microphone = context.createMediaStreamSource(stream);
        const filter = context.createBiquadFilter();
        // microphone -> filter -> destination
        microphone.connect(filter);
        filter.connect(context.destination);
    });
    

    总结

    从发展历程上来看,在web上访问设备一直是个棘手的难题。做了许多尝试,只有少部分获得了成功。大多数早起的想法从未得到广泛的采用,也没有在特定环境之外被接受。主要的问题可能是web的安全模型与原生系统有很大的不同。特别是,您可能并不希望每个网站都可以随机访问您的摄像头和麦克风。总之很难找到权衡之际。

    在移动设备日益普及的功能驱动下,web开始提供更丰富的功能。我们现在可以利用很多api去拍照,控制摄像头设置,录制音频和视频,并访问其他类型的传感器数据,比如位置,运动和设备方向。通过传感器框架将他们串在一起,并与api一起配合使用,使得we应用能访问usb并与蓝牙进行交互。

    getUserMedia()只是与硬件交互的第一波热潮。

  • 相关阅读:
    添加远程库
    远程仓库
    删除url中某个参数
    html2canvas.js——HTML转Canvas工具
    vue-cli3与vue-cli2的区别和vue-cli 怎么修改配置
    vue打包后出现一些map文件的解决方法
    微信app右上角自带分享功能
    微信授权获取用户openId的方法和步骤
    支付宝小程序webview里的h5跳转回小程序
    new Date
  • 原文地址:https://www.cnblogs.com/hanshuai/p/13617313.html
Copyright © 2011-2022 走看看