--- title: Taking still photos with WebRTC slug: Web/API/WebRTC_API/Taking_still_photos tags: - API - Advanced - Example - Sample code - Video - WebRTC - webcam translation_of: Web/API/WebRTC_API/Taking_still_photos ---
{{WebRTCSidebar}}
本文介绍如何使用 WebRTC在 支持 WebRTC 的计算机或手机上访问摄像机,并用其拍照。 尝试一下这个示例,然后继续阅读,了解它的工作原理。
如果你喜欢,你也可以直接跳转到 GitHub 上的代码。
我们的 HTML 界面有两个主要的操作区域:流和捕获面板以及演示面板。 它们俩都在它们自己的 {{HTMLElement("div")}} 中并排呈现,以便于造型和控制。
左边的面板包含两个组件:一个 {{HTMLElement("video")}} 元素,它将接收来自 WebRTC 的流,以及用户点击捕获视频帧的 {{HTMLElement("button")}}。
<div class="camera"> <video id="video">Video stream not available.</video> <button id="startbutton">Take photo</button> </div>
这很简单,当我们进入 JavaScript 代码时,我们将看到他们是如何紧密联系在一起的。
接下来,我们有一个 {{HTMLElement("canvas")}} 元素,捕获的帧被存储到其中,可能以某种方式进行操作,然后转换为输出图像文件。 通过使用样式 {{cssxref("display")}}:none
将画布保持隐藏,以避免画面的混乱 —— 用户不需要看到这个中间过程。
我们还有一个 {{HTMLElement("img")}} 元素,我们将涌起绘制图像——这是让用户看到的最终显示。
<canvas id="canvas"> </canvas> <div class="output"> <img id="photo" alt="The screen capture will appear in this box."> </div>
这是所有相关的HTML。 其余的只是一些页面布局和提供一个返回页面链接的些许文本。
现在来看看 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;
那些变量是:
width
height
width
和宽高比,计算出图像的输出高度。streaming
video
canvas
photo
startbutton
当页面加载完成时,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); });
在这里,我们正在调用 {{domxref("MediaDevices.getUserMedia()")}} 并请求视频流(无音频)。 它返回一个 promise,我们给它附加成功和失败情况下的回调方法。
成功回调接收一个 stream 对象作为输入。它是新视频的 {{HTMLElement("video")}} 元素的源。
一旦流被链接到 {{HTMLElement("video")}}
元素,我们通过调用 HTMLMediaElement.play()
开始播放。
如果打开流失败,则调用失败回调函数。 在没有连接兼容的相机,或者用户拒绝访问时,则会发生这种情况。
在 {{HTMLElement("video")}}
上调用 HTMLMediaElement.play()
之后,在视频流开始流动之前,有一段(希望简短)的时间段过去了。 为了避免在此之前一直阻塞,我们为 {{HTMLElement("video")}}
加上一个 {{event("canplay")}} 事件的监听器,当视频播放实际开始时会触发该事件。 那时,视频对象中的所有属性都已基于流的格式进行配置。
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()清除捕获帧框的内容。
// 加载完毕后开始运行 window.addEventListener("load", startup, false); })();
由于我们通过从<video>元素中抓取帧来捕获用户网络摄像头的图像,因此我们可以非常轻松地将过滤器和有趣的效果应用于视频。 事实证明,使用过滤器属性应用于元素的任何CSS过滤器都会影响捕获的照片。 这些过滤器可以从简单(使图像黑白)到极限(高斯模糊和色调旋转)。
您可以使用例如Firefox开发人员工具的风格编辑器来播放此效果; 有关如何执行此操作的详细信息,请参阅编辑CSS过滤器。