使用 getUserMedia/WebRTC 实现跨浏览器摄像头捕获

概述

随着 Firefox 添加了对 getUserMedia 的支持,三大桌面浏览器现在都能够在无需插件的情况下获取摄像头数据。然而,由于这项技术仍处于早期阶段,不同浏览器之间的实现略有差异。下面是一个解决这些差异的示例,以及一个为您完成繁重工作的脚本。但在开始之前,让我们先概述一下这三种浏览器的比较。

截至 2013 年 2 月,浏览器中 getUserMedia 行为的比较
Firefox 18 Opera 12 Chrome 24
是否需要供应商前缀 是 (moz) 是 (webkit)
是否通过 `autoplay` 属性触发
是否需要用户启用 1
是否触发 `playing` 事件 重复触发 触发一次 触发一次
是否支持 `file://` 协议
标签播放通知 图标 动画图标
权限请求 每次页面加载 仅第一次页面加载 每次页面加载

在 Firefox 中,需要通过将 about:config 中的 `media.peerconnection.enabled` 选项设置为 `true` 来启用 getUserMedia。

在开始编写代码后,我们还会发现一些其他的差异,所以让我们逐步讲解。我们将把实现 getUserMedia 的方法分解成以下几个简单的步骤:

  1. 一份 HTML5
  2. 一勺特性检测
  3. 一勺流媒体
  4. 准备上菜
  5. 最后一个小贴士

深呼吸 - 我们开始吧…

一份 HTML5

在本教程中,我们的主要任务只是在页面上显示一个动态图像。在这方面,它与普通的视频没有什么不同,所以第一步是在 HTML 中使用一个简单的 `<video>` 元素。

就是这样。没有 `controls`,没有 `src`,什么也没有。

接下来是 JavaScript 部分。显然,我们需要获取 `<video>` 元素的引用,我们可以像这样(或者使用 `id`)来实现:

var video = document.querySelector('video');

一勺特性检测

现在开始变得有趣起来,因为我们要检查 getUserMedia 是否受支持。我们肯定不会使用不可靠的用户代理嗅探来实现这一点 - 不,我们将采用简单的方法,即检查 `navigator.getUserMedia` 对象是否存在。在 Firefox 和 Chrome 中,它需要使用前缀,因此首先将它分配给所有浏览器通用的对象会很方便。同时,我们也为稍后将要使用的 `window.URL` 对象做同样的事情。

navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;

接下来,进行实际的存在性检查。

if (navigator.getUserMedia) {
    // Call the getUserMedia method here
} else {
    console.log('Native device media streaming (getUserMedia) not supported in this browser.');
    // Display a friendly "sorry" message to the user.
}

如果支持 getUserMedia,我们需要传递三个参数 - 一个选项对象、一个成功回调函数和一个错误回调函数。请注意,Firefox 中需要错误回调函数,而在 Opera 和 Chrome 中则可选。选项参数是一个 JSON 样式的对象,用于指定要使用音频、视频还是两者。以下示例代码仅用于视频:

navigator.getUserMedia({video: true}, successCallback, errorCallback);

请求摄像头访问权限的对话框

Firefox 中的对话框
Google Chrome 中的对话框
Opera 中的对话框

一勺流媒体

到目前为止一切顺利,所以让我们定义接下来会发生什么。成功回调函数接收一个包含来自摄像头的视频流的参数,我们希望将该流发送到我们的 `<video>` 元素。我们通过设置其 `src` 属性来实现这一点,但需要记住以下几点:

  • Firefox 使用 `mozSrcObject` 属性,而 Opera 和 Chrome 使用 `src`。
  • Chrome 使用 `createObjectURL` 方法,而 Firefox 和 Opera 直接发送流。

在 Firefox 中,`video.mozSrcObject` 最初为 null 而不是 undefined,因此我们可以依靠它来检测 Firefox 的支持(感谢 Florent 的提示)。一旦流知道要发送到哪里,我们就可以告诉视频流开始播放。

function successCallback(stream) {
    if (video.mozSrcObject !== undefined) {
        video.mozSrcObject = stream;
    } else {
        video.src = (window.URL && window.URL.createObjectURL(stream)) || stream;
    };
    video.play();
}

准备上菜

就是这样。添加一个简单的错误回调函数,我们就得到了一个可工作的跨浏览器脚本,它看起来像这样:

window.addEventListener('DOMContentLoaded', function() {
    'use strict';
    var video = document.querySelector('video');

    function successCallback(stream) {
        // Set the source of the video element with the stream from the camera
        if (video.mozSrcObject !== undefined) {
            video.mozSrcObject = stream;
        } else {
            video.src = (window.URL && window.URL.createObjectURL(stream)) || stream;
        }
        video.play();
    }

    function errorCallback(error) {
        console.error('An error occurred: [CODE ' + error.code + ']');
        // Display a friendly "sorry" message to the user
    }

    navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
    window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;

    // Call the getUserMedia method with our callback functions
    if (navigator.getUserMedia) {
        navigator.getUserMedia({video: true}, successCallback, errorCallback);
    } else {
        console.log('Native web camera streaming (getUserMedia) not supported in this browser.');
        // Display a friendly "sorry" message to the user
    }
}, false);

GitHub 上可用

要开始以跨浏览器的方式访问 getUserMedia,我们还在 GitHub 上提供了一个可工作的示例:GumWrapper

最后一个小贴士

如果您想对摄像头的流进行一些高级操作,例如捕获静态图像或添加特殊效果,您可能需要将其数据发送到画布上下文。您可以使用`drawImage()` 来实现这一点,在这种情况下,您需要视频的尺寸。这些尺寸可以通过 `video.videoWidth` 和 `video.videoHeight` 属性获得,但请注意,只有在浏览器拥有有关流的信息时才会设置这些属性。这意味着您必须在获取这些属性之前监听某些事件。有一些相关的事件,总是按照以下顺序触发:

  1. play
  2. loadedmetadata
  3. loadeddata
  4. playing

`play` 事件在调用 `video.play()` 方法后触发,但在视频真正开始播放之前可能会有短暂的延迟。这就是 `playing` 事件的作用,但请注意,在 Firefox 中,只要流或视频正在播放,它就会重复触发。在此之前,还有几个数据事件,第一个事件仅用于元数据,但在 Firefox 中,它不包含视频尺寸。因此,最可靠的事件是 `loadeddata` 事件 - 然后您可以确定知道视频流的宽度和高度。您可以像这样编写代码:

video.addEventListener('loadeddata', function() {
console.log('Video dimensions: ' + video.videoWidth + ' x ' + video.videoHeight);
}, false);

顺便说一句,您还可以使用流的尺寸作为进一步的错误检查,例如检查宽度和高度是否大于 0。这将避免诸如用户网络摄像头损坏或未连接等问题。

就是这样。我相信随着技术的成熟,浏览器之间的差异会逐渐消失,但目前,以上代码应该可以帮助您入门。

关于 Daniel Davis

@ourmaninjapan Daniel 的工作经验包括在 Opera Software 担任开发者布道师,以及在英国和日本从事 Web 开发、IT 培训和项目管理。目前是日本“html5j”开发者社区的成员,喜欢尤克里里。

Daniel Davis 的更多文章…

关于 Robert Nyman [荣誉编辑]

Mozilla Hacks 的技术布道师和编辑。发表关于 HTML5、JavaScript 和开放 Web 的演讲和博客文章。Robert 是 HTML5 和开放 Web 的坚定支持者,自 1999 年以来一直从事 Web 前端开发工作 - 在瑞典和纽约市。他还定期在http://robertnyman.com 上发表博客文章,并且喜欢旅行和结识新朋友。

Robert Nyman [荣誉编辑] 的更多文章…


25 条评论

  1. Arindam Chakraborty

    太棒了。这个周末肯定要试一试。我对用例有点不清楚。我是否理解正确,它的目的是允许代码访问摄像头并在浏览器中创建类似“Hangout”的功能,而无需插件等?

    2013年2月13日 上午 08:12

    1. Robert Nyman [编辑]

      基本上,任何您想在 Web 浏览器中使用视频的功能,都不需要插件!

      2013年2月13日 上午 09:05

  2. Brett Zamir

    很酷…

    2013年2月13日 下午 19:49

  3. VN

    这段代码在 Chrome 和 Opera 上运行流畅,但在 Safari 和 Firefox 上没有反应。

    2013年2月14日 上午 11:56

    1. Robert Nyman [编辑]

      您是否尝试过 Firefox Nightly 并启用了文章中描述的设置?

      2013年2月15日 上午 05:11

    2. Daniel Davis

      在撰写本文时,Safari 尚未支持 getUserMedia(有关当前浏览器的支持情况,请参阅https://caniuse.cn/#feat=stream)。

      至于 Firefox,about:config 中的标志取决于您使用的版本。如果是 Firefox 17 nightly,则需要创建一个名为“media.navigator.enabled”的设置并将其设置为“true”。如果您使用的是 Firefox 18 稳定版,则需要将“media.peerconnection.enabled”设置设置为“true”。

      2013年2月15日 上午 07:01

  4. Florent F.

    不错!感谢这篇文章。

    > 在 Firefox 中,我们无法检查 video.mozSrcObject 是否存在,因为它在接收到流之前是 undefined 的。
    实际上,`video.mozSrcObject` 属性在您第一次调用 successCallback 时为 null(而不是 undefined)。因此,以下代码应该可以工作(但我没有测试过)

    function successCallback(stream) {
    if (video.mozSrcObject !== undefined) {

    Florent

    2013年2月14日 下午 13:16

  5. Daniel Davis

    Florent 的建议很好,谢谢。我们已将其纳入文章中。

    2013年2月15日 上午 07:43

  6. udev

    在 Mozilla 18.0.2 中,将 media.navigator.enabled 或 media.peerconnection.enabled 设置为 true 即可使 getUserMedia 工作,但是第二个标志可能是用于激活点对点连接的,对吧?

    2013年2月17日 上午 00:57

    1. Robert Nyman [编辑]

      是的,正是如此。

      2013年2月18日 上午 03:00

  7. Craig

    使用 JavaScript 在画布上识别条形码图像

    http://badassjs.com/post/654334959/barcode-scanning-in-javascript

    2013年2月18日 下午 16:58

    1. Robert Nyman [编辑]

      非常棒!

      2013年2月19日 上午 01:44

  8. jamie McDonnell

    很棒的文章,谢谢你们!

    @Daniel / Robert,
    我目前正在参与一个使用 getUserMedia / PeerConnection 密集型的资助项目。我们在实现跨浏览器点对点视频连接方面遇到了一些麻烦,非常感谢您能抽出时间帮助我们解决这个问题。

    如果您有时间协助我们完成这项工作,请发送私信或电子邮件给我!

    再次感谢。

    Jamie

    2013年2月21日 上午 03:43

    1. Robert Nyman [编辑]

      谢谢,很高兴您喜欢!
      我认为最好的办法是加入并发送电子邮件到dev-media 邮件列表,看看他们是否可以提供帮助。

      祝您好运!

      2013年2月21日 上午 04:11

      1. jamie McDonnell

        谢谢 Robert,非常感谢。
        此致
        Jamie

        2013年2月21日 上午 07:25

        1. jamie McDonnell

          看起来列表目前无法访问;(稍后会再试。同时,您是否知道 Firefox 和 Chrome 之间的 PeerConnection 实现标准化需要多长时间?它似乎仍然存在很多错误。
          谢谢
          Jamie

          2013年2月21日 上午 07:28

          1. Robert Nyman [编辑]

            列表对您来说仍然无法访问吗?我可以访问它。
            关于 Firefox 和 Google Chrome 之间的连接,我建议您查看我们的Hello Chrome, it’s Firefox calling! 文章。

            在其中,有一个链接到该演示的代码和更多信息

            2013年2月21日 下午 13:28

  9. Merih Akar

    getUserMedia 是否在 Android 版 Firefox 上有效?在 Android 版 Firefox 更新到 19 版、Android 版 Firefox Beta 更新到 20 版以及 Android 版 Firefox Aurora 更新到 21 版后,我无法在任何一个版本上使其工作。我检查了 about:config,即使将 media.navigator.enabled 设置为 true,它也无法工作。

    这是我为使用 getUserMedia 进行二维码解码创建的演示页面(借助此处提供的代码:https://github.com/LazarSoft/jsqrcode

    http://www.ceng.metu.edu.tr/~e1559848/demos/qrdecode/index.html

    目前,它似乎与您提到的 Firefox 18+、Chrome 21+、Opera 12+ 和 Opera Mobile 12+ 兼容。对于移动浏览器,我添加了使用文件输入和接受属性的备用方案,但我认为使用实时视觉辅助进行解码对用户来说更加友好。

    2013年2月26日 上午 08:57

    1. Robert Nyman [编辑]

      Android 版 Firefox 中的 WebRTC 正在开发中,但尚未推出。

      2013年2月26日 16:47

      1. Merih Akar

        期待中:) Firefox for Android 中是否有关于 WebRTC 的 Bugzilla 条目,以便我们跟踪进度?

        2013年2月26日 18:04

        1. Robert Nyman [编辑]

          我们目前在巴塞罗那,正在展示它的第一个测试示例。 这里有一个 Bug 可以跟踪进度

          2013年2月26日 18:09

  10. Hasit Mistry

    当我点击“拍照”时,照片没有显示。你能帮我解决这个问题吗?我很好奇。我正在使用 Firefox 20。

    2013年4月4日 11:58

  11. Robert Nyman [编辑]

    你在哪里看到“拍照”?

    2013年4月5日 01:28

  12. guska076

    示例在 Firefox 20 移动版(Android 手机上)上无法运行。这是正常的吗?

    2013年4月10日 06:14

    1. Robert Nyman [编辑]

      是的。WebRTC/getUserMedia 尚未在移动设备上得到支持。

      2013年4月10日 07:49

本文的评论已关闭。