如何使用JavaScript的getUserMedia()访问前后摄像头

来自菜鸟教程
跳转至:导航、​搜索

介绍

随着 HTML5 引入了可以访问设备硬件的 API,包括 MediaDevices API。 此 API 提供对音频和视频等媒体输入设备的访问。

在此 API 的帮助下,开发人员可以访问音频和视频设备,以在浏览器中流式传输和显示实时视频源。 在本教程中,您将从用户设备访问视频源,并使用 getUserMedia 方法将其显示在浏览器中。

getUserMedia API 利用媒体输入设备生成 MediaStream。 此 MediaStream 包含请求的媒体类型,无论是音频还是视频。 使用从 API 返回的流,可以在浏览器上显示视频源,这对于浏览器上的实时通信很有用。

当与MediaStream Recording API一起使用时,您可以记录和存储在浏览器上捕获的媒体数据。 此 API 仅适用于其他新引入的 API 的安全来源,但它也适用于 localhost 和文件 URL。

先决条件

  • JavaScript 的基本知识。 如果您是 JavaScript 新手,请尝试查看 如何在 JavaScript 中编写代码系列。

本教程将首先解释概念并使用 Codepen 演示示例。 在最后一步中,您将为浏览器创建一个正常工作的视频源。

第 1 步 — 检查设备支持

首先,您将看到如何检查用户的浏览器是否支持 mediaDevices API。 此 API 存在于 navigator 接口中,包含用户代理的当前状态和身份。 使用可以粘贴到 Codepen 中的以下代码执行检查:

if ('mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices) {
  console.log("Let's get this party started")
}

首先,这将检查 mediaDevices API 是否存在于 navigator 中,然后检查 getUserMedia API 是否存在于 mediaDevices 中。 如果返回 true,您就可以开始了。

第 2 步 — 请求用户许可

在确认浏览器支持 getUserMedia 后,您需要在用户代理上请求使用媒体输入设备的权限。 通常,在用户授予权限后,会返回一个 Promise 来解析为媒体流。 当权限被用户拒绝时不会返回此 Promise,这会阻止对这些设备的访问。

将以下行粘贴到 Codepen 以请求权限:

navigator.mediaDevices.getUserMedia({video: true})

作为 getUserMedia 方法的参数提供的对象称为 constraints。 这决定了您请求访问哪些媒体输入设备。 例如,如果对象包含 audio: true,则会要求用户授予对音频输入设备的访问权限。

第 3 步 — 了解媒体约束

本节将介绍 约束 的一般概念。 constraints 对象是一个 MediaStreamConstraints 对象,它指定要请求的媒体类型和每种媒体类型的要求。 您可以使用 constraints 对象为请求的流指定要求,例如要使用的流的分辨率(frontback)。

发出请求时,您必须指定 audiovideo。 如果在用户的浏览器上找不到请求的媒体类型,将返回 NotFoundError

如果您打算请求 1280 x 720 分辨率的视频流,您可以将 constraints 对象更新为如下所示:

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

通过此更新,浏览器将尝试匹配流的指定质量设置。 如果视频设备无法提供此分辨率,浏览器将返回其他可用分辨率。

为确保浏览器返回的分辨率不低于提供的分辨率,您必须使用 min 属性。 以下是如何更新 constraints 对象以包含 min 属性:

{
  video: {
    width: {
      min: 1280,
    },
    height: {
      min: 720,
    }
  }
}

这将确保返回的流分辨率至少为 1280 x 720。 如果不能满足这个最低要求,promise 将被 OverconstrainedError 拒绝。

在某些情况下,您可能会担心保存数据并需要流不超过设定的分辨率。 当用户的计划有限时,这可以派上用场。 要启用此功能,请更新约束对象以包含 max 字段:

{
  video: {
    width: {
      min: 1280,
      max: 1920,
    },
    height: {
      min: 720,
      max: 1080
    }
  }
}

通过这些设置,浏览器将确保返回流不低于 1280 x 720 并且不超过 1920 x 1080

可以使用的其他术语包括 exactidealideal 设置通常与 minmax 属性一起使用,以找到最接近提供的理想值的最佳可能设置。

您可以更新约束以使用 ideal 关键字:

{
  video: {
    width: {
      min: 1280,
      ideal: 1920,
      max: 2560,
    },
    height: {
      min: 720,
      ideal: 1080,
      max: 1440
    }
  }
}

要告诉浏览器使用设备上的前置或后置(在移动设备上)摄像头,您可以在 video 对象中指定 facingMode 属性:

{
  video: {
    width: {
      min: 1280,
      ideal: 1920,
      max: 2560,
    },
    height: {
      min: 720,
      ideal: 1080,
      max: 1440
    },
    facingMode: 'user'
  }
}

此设置将始终在所有设备中使用前置摄像头。 要在移动设备上使用后置摄像头,您可以将 facingMode 属性更改为 environment

{
  video: {
    ...
    facingMode: {
      exact: 'environment'
    }
  }
}

第 4 步 — 使用 enumerateDevices 方法

当调用 enumerateDevices 方法时,它返回用户 PC 上所有可用的输入媒体设备。

使用该方法,您可以为用户提供用于流式传输音频或视频内容的输入媒体设备的选项。 此方法返回解析为 MediaDeviceInfo 数组的 Promise 数组,其中包含有关每个设备的信息。

下面的代码片段显示了如何使用此方法的示例:

async function getDevices() {
  const devices = await navigator.mediaDevices.enumerateDevices();
}

每个设备的示例响应如下所示:

{
  deviceId: "23e77f76e308d9b56cad920fe36883f30239491b8952ae36603c650fd5d8fbgj",
  groupId: "e0be8445bd846722962662d91c9eb04ia624aa42c2ca7c8e876187d1db3a3875",
  kind: "audiooutput",
  label: "",
}

注意: 除非有可用的流可用,或者用户已授予设备访问权限,否则不会返回标签。


第 5 步 — 在浏览器上显示视频流

您已经完成了请求和访问媒体设备的过程,配置了约束以包括所需的分辨率,并选择了录制视频所需的摄像机。

完成所有这些步骤后,您至少需要查看流是否根据配置的设置进行传送。 为确保这一点,您将使用 <video> 元素在浏览器上显示视频流。

如前所述,getUserMedia 方法返回一个可以解析为流的 Promise。 可以使用 createObjectURL 方法将返回的流转换为对象 URL。 此 URL 将被设置为视频源。

您将创建一个简短的演示,让用户从可用的视频设备列表中进行选择。 使用 enumerateDevices 方法。

这是一种 navigator.mediaDevices 方法。 它列出了可用的媒体设备,例如麦克风和摄像头。 它返回一个 Promise 可解析为详细描述可用媒体设备的对象数组。

创建一个 index.html 文件并使用以下代码更新内容:

索引.html

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
    <link rel="stylesheet" href="style.css">
    <title>Document</title>
</head>
<body>
<div class="display-cover">
    <video autoplay></video>
    <canvas class="d-none"></canvas>

    <div class="video-options">
        <select name="" id="" class="custom-select">
            <option value="">Select camera</option>
        </select>
    </div>

    <img class="screenshot-image d-none" alt="">

    <div class="controls">
        <button class="btn btn-danger play" title="Play"><i data-feather="play-circle"></i></button>
        <button class="btn btn-info pause d-none" title="Pause"><i data-feather="pause"></i></button>
        <button class="btn btn-outline-success screenshot d-none" title="ScreenShot"><i data-feather="image"></i></button>
    </div>
</div>

<script src="https://unpkg.com/feather-icons"></script>
<script src="script.js"></script>
</body>
</html>

在上面的代码片段中,您已经设置了您需要的元素和视频的几个控件。 还包括一个用于截取当前视频源的按钮。

现在,让我们对这些组件进行一些样式化。

创建一个 style.css 文件并将以下样式复制到其中。 Bootstrap 被包括在内以减少您需要编写的 CSS 数量以使组件运行。

样式.css

.screenshot-image {
    width: 150px;
    height: 90px;
    border-radius: 4px;
    border: 2px solid whitesmoke;
    box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.1);
    position: absolute;
    bottom: 5px;
    left: 10px;
    background: white;
}

.display-cover {
    display: flex;
    justify-content: center;
    align-items: center;
    width: 70%;
    margin: 5% auto;
    position: relative;
}

video {
    width: 100%;
    background: rgba(0, 0, 0, 0.2);
}

.video-options {
    position: absolute;
    left: 20px;
    top: 30px;
}

.controls {
    position: absolute;
    right: 20px;
    top: 20px;
    display: flex;
}

.controls > button {
    width: 45px;
    height: 45px;
    text-align: center;
    border-radius: 100%;
    margin: 0 6px;
    background: transparent;
}

.controls > button:hover svg {
    color: white !important;
}

@media (min-width: 300px) and (max-width: 400px) {
    .controls {
        flex-direction: column;
    }

    .controls button {
        margin: 5px 0 !important;
    }
}

.controls > button > svg {
    height: 20px;
    width: 18px;
    text-align: center;
    margin: 0 auto;
    padding: 0;
}

.controls button:nth-child(1) {
    border: 2px solid #D2002E;
}

.controls button:nth-child(1) svg {
    color: #D2002E;
}

.controls button:nth-child(2) {
    border: 2px solid #008496;
}

.controls button:nth-child(2) svg {
    color: #008496;
}

.controls button:nth-child(3) {
    border: 2px solid #00B541;
}

.controls button:nth-child(3) svg {
    color: #00B541;
}

.controls > button {
    width: 45px;
    height: 45px;
    text-align: center;
    border-radius: 100%;
    margin: 0 6px;
    background: transparent;
}

.controls > button:hover svg {
    color: white;
}

下一步是为演示添加功能。 使用 enumerateDevices 方法,您将获得可用的视频设备并将其设置为选择元素中的选项。 创建一个名为 script.js 的文件并使用以下代码段对其进行更新:

脚本.js

feather.replace();

const controls = document.querySelector('.controls');
const cameraOptions = document.querySelector('.video-options>select');
const video = document.querySelector('video');
const canvas = document.querySelector('canvas');
const screenshotImage = document.querySelector('img');
const buttons = [...controls.querySelectorAll('button')];
let streamStarted = false;

const [play, pause, screenshot] = buttons;

const constraints = {
  video: {
    width: {
      min: 1280,
      ideal: 1920,
      max: 2560,
    },
    height: {
      min: 720,
      ideal: 1080,
      max: 1440
    },
  }
};

const getCameraSelection = async () => {
  const devices = await navigator.mediaDevices.enumerateDevices();
  const videoDevices = devices.filter(device => device.kind === 'videoinput');
  const options = videoDevices.map(videoDevice => {
    return `<option value="${videoDevice.deviceId}">${videoDevice.label}</option>`;
  });
  cameraOptions.innerHTML = options.join('');
};

play.onclick = () => {
  if (streamStarted) {
    video.play();
    play.classList.add('d-none');
    pause.classList.remove('d-none');
    return;
  }
  if ('mediaDevices' in navigator && navigator.mediaDevices.getUserMedia) {
    const updatedConstraints = {
      ...constraints,
      deviceId: {
        exact: cameraOptions.value
      }
    };
    startStream(updatedConstraints);
  }
};

const startStream = async (constraints) => {
  const stream = await navigator.mediaDevices.getUserMedia(constraints);
  handleStream(stream);
};

const handleStream = (stream) => {
  video.srcObject = stream;
  play.classList.add('d-none');
  pause.classList.remove('d-none');
  screenshot.classList.remove('d-none');
  streamStarted = true;
};

getCameraSelection();

在上面的代码片段中,发生了几件事。 让我们分解它们:

  1. feather.replace():这个方法调用实例化了feather,这是一个web开发的图标集。
  2. constraints 变量保存流的初始配置。 这将扩展到包括用户选择的媒体设备。
  3. getCameraSelection:该函数调用enumerateDevices方法。 然后,从已解析的 Promise 中过滤数组并选择视频输入设备。 根据过滤结果,您为 <select> 元素创建 <option>
  4. 调用 getUserMedia 方法发生在 play 按钮的 onclick 侦听器中。 在这里,您将在启动流之前检查用户的浏览器是否支持此方法。
  5. 接下来,您将调用带有 constraints 参数的 startStream 函数。 它使用提供的 constraints 调用 getUserMedia 方法。 handleStream 使用来自已解析 Promise 的流调用。 此方法将返回的流设置为视频元素的 srcObject

接下来,将点击监听器添加到页面上的按钮控件到pausestop,并采取screenshots。 此外,您将向 <select> 元素添加一个侦听器,以使用所选视频设备更新流约束。

使用以下代码更新 script.js 文件:

脚本.js

...
cameraOptions.onchange = () => {
  const updatedConstraints = {
    ...constraints,
    deviceId: {
      exact: cameraOptions.value
    }
  };
  startStream(updatedConstraints);
};

const pauseStream = () => {
  video.pause();
  play.classList.remove('d-none');
  pause.classList.add('d-none');
};

const doScreenshot = () => {
  canvas.width = video.videoWidth;
  canvas.height = video.videoHeight;
  canvas.getContext('2d').drawImage(video, 0, 0);
  screenshotImage.src = canvas.toDataURL('image/webp');
  screenshotImage.classList.remove('d-none');
};

pause.onclick = pauseStream;
screenshot.onclick = doScreenshot;

现在,当您在浏览器中打开 index.html 文件时,单击 Play 按钮将启动流。

这是一个完整的演示:

https://codepen.io/chrisbeast/pen/ebYwpX

结论

本教程介绍了 getUserMedia API。 这是对 HTML5 的一个有趣的补充,它简化了在网络上捕获媒体的过程。

该 API 采用一个参数 (constraints),可用于配置对音频和视频输入设备的访问。 它还可用于指定应用程序所需的视频分辨率。

您可以进一步扩展演示,让用户可以选择保存截取的屏幕截图,以及在 MediaStream Recording API 的帮助下录制和存储视频和音频数据。