MDN’s new design is in Beta! A sneak peek: https://blog.mozilla.org/opendesign/mdns-new-design-beta/

Taking still photos with WebRTC

这篇翻译不完整。请帮忙从英语翻译这篇文章

本文介绍如何使用WebRTC在支持WebRTC的计算机或手机上访问摄像机,并与其一起拍照。 尝试这个Try this sample,样本,然后阅读,了解它的工作原理。

WebRTC-based image capture app — on the left we have a video stream taken from a webcam and a take photo button, on the right we have the still image output from taking the photo

如果你喜欢,你也可以直接跳转到Github的代码code on Github 。

HTML标记

Our HTML interface我们的HTML界面有两个主要的操作部分:流和捕获面板和演示面板。 它们中的每一个都以其自己的<div>并排呈现,以便于造型和控制。

左边的第一个面板包含两个组件:一个<video>元素,它将接收来自WebRTC的流,以及用户点击捕获视频帧的<button>。

  <div class="camera">
    <video id="video">Video stream not available.</video>
    <button id="startbutton">Take photo</button>
  </div>

这很简单,当我们进入JavaScript代码时,我们将看到它如何联系在一起。

接下来,我们有一个<canvas>元素,捕获的帧被存储到其中,可能以某种方式进行操作,然后转换为输出图像文件。 通过使用display:none设置画布来保持画布的隐藏,以避免画面的混乱 - 用户不需要看到这个中间阶段。

我们还有一个<img>元素,我们将绘制图像 - 这是向用户显示的最终显示。

  <canvas id="canvas">
  </canvas>
  <div class="output">
    <img id="photo" alt="The screen capture will appear in this box.">
  </div>

这是所有相关的HTML。 其余的只是一些页面布局和一些文本提供一个链接回到这个页面。

The JavaScript code

现在来看看JavaScript代码。 我们将把它分解成几个小尺寸的碎片,使其更容易解释。

初始化

我们首先将整个脚本包装在匿名函数中,以避免使用全局变量,然后设置我们将要使用的各种变量。

(function() {
  var width = 320;    // We will scale the photo width to this
  var height = 0;     // This will be computed based on the input stream

  var streaming = false;

  var video = null;
  var canvas = null;
  var photo = null;
  var startbutton = null;

那些变量是:

宽度
无论输入视频的尺寸如何,我们将把所得到的图像缩放到320像素宽。
高度
给定流的宽度和宽高比,计算出图像的输出高度。
流媒体
指示当前是否有活动的视频流正在运行。
视频
这将是页面加载完成后对<video>元素的引用。
画布
这将是页面加载完成后对<canvas>元素的引用。
照片
这将在页面加载完成后引用<img>元素。
开始按钮
这将引用用于触发捕获的<button>元素。 我们会在页面加载完成之后得到。

startup( )函数

当页面加载完成时,startup( )函数运行,由window.addEventListener( )提供。 此功能的作用是请求访问用户的网络摄像头,将输出<img>初始化为默认状态,并建立从相机接收每帧视频所需的事件侦听器,并在点击按钮捕获图像时作出反应 。

获取元素引用

首先,我们参考我们需要访问的主要内容。

 

  function startup() {
    video = document.getElementById('video');
    canvas = document.getElementById('canvas');
    photo = document.getElementById('photo');
    startbutton = document.getElementById('startbutton');

获取媒体流

接下来的任务是获取媒体流:

    navigator.mediaDevices.getUserMedia({ video: true, audio: false })
    .then(function(stream) {
        video.srcObject = stream;
        video.play();
    })
    .catch(function(err) {
        console.log("An error occured! " + err);
    });

在这里,我们正在调用MediaDevices.getUserMedia()并请求视频流(无音频)。 它返回一个承诺,我们附加成功和失败的回调。

成功回调接收流对象作为输入。 它是新视频的<video>元素的源。

一旦流被链接到<video>元素,我们通过调用HTMLMediaElement.play()开始播放。

如果打开流不起作用,调用错误回调函数。 如果没有连接兼容的相机,或者用户拒绝访问,则会发生这种情况。

监听视频开始播放

在<video>上调用HTMLMediaElement.play()之后,在视频流开始流动之前,有一段(希望简短)的时间段过去了。 为了避免阻塞,直到发生这种情况,我们为视频播放,即视频播放实际开始时传送的视频添加事件侦听器。 此时,视频对象中的所有属性都已基于流的格式进行配置。

    video.addEventListener('canplay', function(ev){
      if (!streaming) {
        height = video.videoHeight / (video.videoWidth/width);
      
        video.setAttribute('width', width);
        video.setAttribute('height', height);
        canvas.setAttribute('width', width);
        canvas.setAttribute('height', height);
        streaming = true;
      }
    }, false);

这个回调什么都不做,除非它是第一次被调用; 这是通过查看我们的流变量的值进行测试,这是第一次运行此方法时为false。

如果这是第一次运行,我们会根据视频的实际大小,video.videoWidth和要渲染视频宽度的宽度之间的大小差异来设置视频的高度。

最后,通过在每个元素的两个属性的每一个上调用Element.setAttribute()来设置视频和画布的宽度和高度,并根据需要设置宽度和高度。 最后,我们将流变量设置为true,以防止我们再次无意中运行此设置代码。

处理按钮上的点击

为了在每次用户单击启动按钮时捕获静态照片,我们需要向按钮添加一个事件侦听器,以便在发出点击事件时被调用:

 

    startbutton.addEventListener('click', function(ev){
      takepicture();
      ev.preventDefault();
    }, false);

这个方法很简单:它只是调用我们的takepicture()函数,在从流中捕获一个帧的部分中定义,然后在接收的事件上调用Event.preventDefault(),以防止点击被多次处理。

包装startup()方法

startup()方法中只有两行代码:

    clearphoto();
  }

这就是我们在Clearflow()方法中,我们将在下面的清理照片框中进行描述。

清除照片框

清除照片框包括创建一个图像,然后将其转换为可以显示最近捕获的帧的<img>元素使用的格式。 该代码如下所示:

  function clearphoto() {
    var context = canvas.getContext('2d');
    context.fillStyle = "#AAA";
    context.fillRect(0, 0, canvas.width, canvas.height);

    var data = canvas.toDataURL('image/png');
    photo.setAttribute('src', data);
  }

我们首先得到对我们用于屏幕外渲染的隐藏的<canvas>元素的引用。 接下来,我们将fillStyle设置为#AAA(相当浅灰色),并通过调用fillRect()来填充整个画布。

最后在此功能中,我们将画布转换为PNG图像,并调用photo.setAttribute()以使我们捕获的静止框显示图像。

从流中捕获帧

最后一个定义的功能是整个练习的重点:takepicture()函数,其捕获当前显示的视频帧的作业将其转换为PNG文件,并将其显示在捕获的帧框中。 代码如下所示:

  function takepicture() {
    var context = canvas.getContext('2d');
    if (width && height) {
      canvas.width = width;
      canvas.height = height;
      context.drawImage(video, 0, 0, width, height);
    
      var data = canvas.toDataURL('image/png');
      photo.setAttribute('src', data);
    } else {
      clearphoto();
    }
  }

正如我们需要处理画布内容的情况一样,我们首先得到隐藏画布的2D绘图上下文。

然后,如果宽度和高度都是非零(意味着至少有潜在有效的图像数据),我们将画布的宽度和高度设置为与捕获帧的宽度和高度相匹配,然后调用drawImage()来绘制当前的 将视频帧放入上下文中,用帧图像填充整个画布。

注意:这可以利用HTMLVideoElement接口看起来像任何接受HTMLImageElement作为参数的API的HTMLImageElement,将视频的当前帧呈现为图像的内容。

一旦画布包含捕获的图像,我们通过调用它的HTMLCanvasElement.toDataURL()将它转换为PNG格式; 最后,我们调用photo.setAttribute()来使我们捕获的静态框显示图像。

如果没有可用的有效图像(即宽度和高度均为0),则通过调用clearphoto()清除捕获帧框的内容。

带有过滤器的乐趣


由于我们通过从<video>元素中抓取帧来捕获用户网络摄像头的图像,因此我们可以非常轻松地将过滤器和有趣的效果应用于视频。 事实证明,使用过滤器属性应用于元素的任何CSS过滤器都会影响捕获的照片。 这些过滤器可以从简单(使图像黑白)到极限(高斯模糊和色调旋转)。

您可以使用例如Firefox开发人员工具的风格编辑器来播放此效果; 有关如何执行此操作的详细信息,请参阅编辑CSS过滤器。

See also

文档标签和贡献者

 此页面的贡献者: Move
 最后编辑者: Move,