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

简介

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

在这个接口的帮助下,开发者可以访问音视频设备,在浏览器中直播和显示视频提要。在本教程中,您将从用户设备访问视频提要,并使用getUserMedia方法在浏览器中显示它。

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

媒体流录制API,配合使用时,您可以录制并存储在浏览器上捕获的媒体数据。与其他新推出的接口一样,该接口只支持安全来源,但也支持localhost和文件URL。

前提条件

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

第一步-检查设备支持

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

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

首先检查Navigator中是否存在mediaDevices接口,然后检查mediaDevices中是否存在getUserMedia接口。如果返回`true‘,您就可以开始了。

第二步-请求用户权限

确认浏览器支持getUserMedia后,您需要申请许可才能使用用户代理上的媒体输入设备。一般情况下,用户授权后,会返回一个Promise,解析为一个媒体流。当用户拒绝访问这些设备时,不会返回该Promise

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

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

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

Step 3 -了解媒体约束

本节将涵盖_合同_的一般概念。constraints对象是一个MediaStreamConstraints对象,它指定了要请求的媒体类型以及每种媒体类型的要求。您可以使用constraints对象来指定对请求的流的要求,比如要使用的流的分辨率(Frontback)。

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

如果您想请求1280 x 720分辨率的视频流,可以更新constraints对象如下:

1{
2  video: {
3    width: 1280,
4    height: 720,
5  }
6}

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

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

 1{
 2  video: {
 3    width: {
 4      min: 1280,
 5    },
 6    height: {
 7      min: 720,
 8    }
 9  }
10}

这样可以保证返回的流分辨率至少为1280 x 720。如果无法满足此最低要求,则承诺将被拒绝,并返回OverconstrainedError

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

 1{
 2  video: {
 3    width: {
 4      min: 1280,
 5      max: 1920,
 6    },
 7    height: {
 8      min: 720,
 9      max: 1080
10    }
11  }
12}

有了这些设置,浏览器会确保返回的流不会低于1280 x 720,也不会超过1920 x 1080

其他可以使用的术语包括精确理想ideal设置通常与minMax属性一起使用,以查找与提供的理想值最接近的最佳设置。

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

 1{
 2  video: {
 3    width: {
 4      min: 1280,
 5      ideal: 1920,
 6      max: 2560,
 7    },
 8    height: {
 9      min: 720,
10      ideal: 1080,
11      max: 1440
12    }
13  }
14}

要告诉浏览器在设备上使用前置或后置摄像头,可以在avio对象中指定一个facingMode属性:

 1{
 2  video: {
 3    width: {
 4      min: 1280,
 5      ideal: 1920,
 6      max: 2560,
 7    },
 8    height: {
 9      min: 720,
10      ideal: 1080,
11      max: 1440
12    },
13    facingMode: 'user'
14  }
15}

此设置将在所有设备中始终使用前置摄像头。若要使用移动端的后置摄像头,可以将facingMode属性修改为Environmental

1{
2  video: {
3    ...
4    facingMode: {
5      exact: 'environment'
6    }
7  }
8}

第四步-使用枚举设备方法

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

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

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

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

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

1{
2  deviceId: "23e77f76e308d9b56cad920fe36883f30239491b8952ae36603c650fd5d8fbgj",
3  groupId: "e0be8445bd846722962662d91c9eb04ia624aa42c2ca7c8e876187d1db3a3875",
4  kind: "audiooutput",
5  label: "",
6}

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

Step 5-在浏览器上显示视频流

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

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

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

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

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

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

 1[label index.html]
 2<!doctype html>
 3<html lang="en">
 4<head>
 5    <meta charset="UTF-8">
 6    <meta name="viewport"
 7          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 8    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 9    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
10    <link rel="stylesheet" href="style.css">
11    <title>Document</title>
12</head>
13<body>
14<div class="display-cover">
15    <video autoplay></video>
16    <canvas class="d-none"></canvas>
17
18    <div class="video-options">
19        <select name="" id="" class="custom-select">
20            <option value="">Select camera</option>
21        </select>
22    </div>
23
24    <img class="screenshot-image d-none" alt="">
25
26    <div class="controls">
27        <button class="btn btn-danger play" title="Play"><i data-feather="play-circle"></i></button>
28        <button class="btn btn-info pause d-none" title="Pause"><i data-feather="pause"></i></button>
29        <button class="btn btn-outline-success screenshot d-none" title="ScreenShot"><i data-feather="image"></i></button>
30    </div>
31</div>
32
33<script src="https://unpkg.com/feather-icons"></script>
34<script src="script.js"></script>
35</body>
36</html>

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

现在,让我们稍微调整一下这些组件的样式。

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

  1[label style.css]
  2.screenshot-image {
  3    width: 150px;
  4    height: 90px;
  5    border-radius: 4px;
  6    border: 2px solid whitesmoke;
  7    box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.1);
  8    position: absolute;
  9    bottom: 5px;
 10    left: 10px;
 11    background: white;
 12}
 13
 14.display-cover {
 15    display: flex;
 16    justify-content: center;
 17    align-items: center;
 18    width: 70%;
 19    margin: 5% auto;
 20    position: relative;
 21}
 22
 23video {
 24    width: 100%;
 25    background: rgba(0, 0, 0, 0.2);
 26}
 27
 28.video-options {
 29    position: absolute;
 30    left: 20px;
 31    top: 30px;
 32}
 33
 34.controls {
 35    position: absolute;
 36    right: 20px;
 37    top: 20px;
 38    display: flex;
 39}
 40
 41.controls > button {
 42    width: 45px;
 43    height: 45px;
 44    text-align: center;
 45    border-radius: 100%;
 46    margin: 0 6px;
 47    background: transparent;
 48}
 49
 50.controls > button:hover svg {
 51    color: white !important;
 52}
 53
 54@media (min-width: 300px) and (max-width: 400px) {
 55    .controls {
 56        flex-direction: column;
 57    }
 58
 59    .controls button {
 60        margin: 5px 0 !important;
 61    }
 62}
 63
 64.controls > button > svg {
 65    height: 20px;
 66    width: 18px;
 67    text-align: center;
 68    margin: 0 auto;
 69    padding: 0;
 70}
 71
 72.controls button:nth-child(1) {
 73    border: 2px solid #D2002E;
 74}
 75
 76.controls button:nth-child(1) svg {
 77    color: #D2002E;
 78}
 79
 80.controls button:nth-child(2) {
 81    border: 2px solid #008496;
 82}
 83
 84.controls button:nth-child(2) svg {
 85    color: #008496;
 86}
 87
 88.controls button:nth-child(3) {
 89    border: 2px solid #00B541;
 90}
 91
 92.controls button:nth-child(3) svg {
 93    color: #00B541;
 94}
 95
 96.controls > button {
 97    width: 45px;
 98    height: 45px;
 99    text-align: center;
100    border-radius: 100%;
101    margin: 0 6px;
102    background: transparent;
103}
104
105.controls > button:hover svg {
106    color: white;
107}

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

 1[label script.js]
 2feather.replace();
 3
 4const controls = document.querySelector('.controls');
 5const cameraOptions = document.querySelector('.video-options>select');
 6const video = document.querySelector('video');
 7const canvas = document.querySelector('canvas');
 8const screenshotImage = document.querySelector('img');
 9const buttons = [...controls.querySelectorAll('button')];
10let streamStarted = false;
11
12const [play, pause, screenshot] = buttons;
13
14const constraints = {
15  video: {
16    width: {
17      min: 1280,
18      ideal: 1920,
19      max: 2560,
20    },
21    height: {
22      min: 720,
23      ideal: 1080,
24      max: 1440
25    },
26  }
27};
28
29const getCameraSelection = async () => {
30  const devices = await navigator.mediaDevices.enumerateDevices();
31  const videoDevices = devices.filter(device => device.kind === 'videoinput');
32  const options = videoDevices.map(videoDevice => {
33    return `<option value="${videoDevice.deviceId}">${videoDevice.label}</option>`;
34  });
35  cameraOptions.innerHTML = options.join('');
36};
37
38play.onclick = () => {
39  if (streamStarted) {
40    video.play();
41    play.classList.add('d-none');
42    pause.classList.remove('d-none');
43    return;
44  }
45  if ('mediaDevices' in navigator && navigator.mediaDevices.getUserMedia) {
46    const updatedConstraints = {
47      ...constraints,
48      deviceId: {
49        exact: cameraOptions.value
50      }
51    };
52    startStream(updatedConstraints);
53  }
54};
55
56const startStream = async (constraints) => {
57  const stream = await navigator.mediaDevices.getUserMedia(constraints);
58  handleStream(stream);
59};
60
61const handleStream = (stream) => {
62  video.srcObject = stream;
63  play.classList.add('d-none');
64  pause.classList.remove('d-none');
65  screenshot.classList.remove('d-none');
66  streamStarted = true;
67};
68
69getCameraSelection();

在上面的代码片段中,有几件事正在发生。让我们来分析一下它们:

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

接下来,您将向页面上的按钮控件添加单击侦听器,以pausestopscreenshots。此外,您还将向 元素添加一个侦听器