JavaScript 使用 mediaDevices API 选择摄像头
大多数智能手机都有前置和后置摄像头,当你在创建视频应用时你可能想要选择或者切换前置、后置摄像头。
如果你开发的是一款聊天应用,你很可能会想调用前置摄像头,但如果你开发的是一款拍照软件,那么你会更倾向于使用后置摄像头。在这篇文章中我们将探讨如何通过 mediaDevices API 和 media constraints (媒体约束) 选择或者切换摄像头。
准备工作
要跟着本文一起动手实践你需要:
一款拥有两个可供测试的摄像头的 iOS 或 Android 设备,如果你的电脑有两个摄像头那也可以
ngrok 以便你能通过移动设备轻松访问到你的项目(也因为我觉得 ngrok 炒鸡棒)
这个 GitHub 库 的代码让你起步
要获取代码,先把这个项目 clone 下来然后 checkout 到 initial-project tag 下。
git clone https://github.com/philnash/mediadevices-camera-selection.git -b initial-project
cd mediadevices-camera-selection
这个起步项目已经为你准备好了一些 HTML 和 CSS,所以我们就可以把注意集中到 JavaScript 上了。你可以直接打开 index.html,但我建议你用一款 webserver 把这些文件托管起来。我喜欢用 npm 的 serve 模块。我在这个库里已经引入了 serve,要使用它你需要先用 npm 安装依赖然后启动这个服务。
npm install
npm start
服务运行起来后,我们要用 ngrok 开启一条隧道。serve 用 5000 端口托管文件,要用 ngrok 开隧道通到这个端口,新开一个命令行窗口输入以下命令:
`ngrok http 5000`
好了你现在可以公网访问这个站点了,你可以在移动设备上打开这个网站,这样接下来就可以测试啦。确保你打开的是 HTTPS 的 URL,因为我们用的 API 只能在安全环境下使用。
网站看起来像这样:
获取 media stream
我们的第一个任务是从任意摄像头获取视频流显示到屏幕上。完成这个之后我们再调研如何选择特定摄像头。打开 app.js , 我们以从 DOM 中选择按钮和 video 元素开始:
// app.js
const video = document.getElementById('video');
const button = document.getElementById('button');
当用户点击或触摸按钮时,我们要使用 mediaDevices API 请求摄像头权限。要这样做,我们要调用 navigator.mediaDevices.getUserMedia ,传递 media constraints 对象。让我们从简单的 constraints 开始,我们只需要视频,因此我们把 video 设置为 true,audio 设置为 false。
getUserMedia 会返回一个 promise,当 resolve 的时候我们就可以访问到摄像头的媒体流了。把媒体流赋值给 video 元素的 srcObj 属性,我们就能从屏幕上看到视频了。
button.addEventListener('click', event => {
const constraints = {
video: true,
audio: false
};
navigator.mediaDevices
.getUserMedia(constraints)
.then(stream => {
video.srcObject = stream;
})
.catch(error => {
console.error(error);
});
});
保存文件,重新加载页面然后点击按钮。你应该能看到一个权限对话框请求访问你的摄像头,一旦授权屏幕上就应该会出现视频。在你的电脑和手机上试一试,我在我的 iPhone 上试了,被选择的是前置摄像头。
如果你用的是一部 iPhone 手机,确认你在 Safari 里尝试,因为其他浏览器貌似并没有效果。
可用摄像头
media Devices API 为我们提供了一种枚举所有可用音频和视频输入设备的方式。我们要用 enumerateDevices 函数来为 框构建选项,这样我们就能用它来选择我们想看的摄像头了。再次打开 app.js,从 DOM 中选出 元素:
const video = document.getElementById('video');
const button = document.getElementById('button');
const select = document.getElementById('select');
enumerateDevices 会返回一个 promise,所以让我们写一个用来接受 promise 结果的函数吧。这个函数接收一个 media device 数组作为参数。
首先要做的是清空 现有的任何选项,然后插入一个空的
。接着循环遍历所有设备,过滤掉非 “videoinput”类型的设备。然后我们创建一个
元素,用设备 ID 当作 option value,设备 label 当作 option text。我们还要处理一种情况,如果一个设备没有 label 存在,生成一个简单的 “Camera n” 作为标签。 const video = document.getElementById('video'); const button = document.getElementById('button'); const select = document.getElementById('select'); function gotDevices(mediaDevices) { select.innerHTML = ''; select.appendChild(document.createElement('option')); let count = 1; mediaDevices.forEach(mediaDevice => { if (mediaDevice.kind === 'videoinput') { const option = document.createElement('option'); option.value = mediaDevice.deviceId; const label = mediaDevice.label || `Camera ${count++}`; const textNode = document.createTextNode(label); option.appendChild(textNode); select.appendChild(option); } }); } 在 app.js 末尾调用一下 enumerateDevices。 `navigator.mediaDevices.enumerateDevices().then(gotDevices);` 刷新页面,看一下按钮旁边的下拉选择框。如果你用的是 Android ,或者使用 Chrome 或 Firefox,你就能看到可用的摄像头名称了。 然而在 iPhone 上,你将看到我们函数生成的通用名字 “Camera 1” 和 “Camera 2”。在 iOS 上只有你授权至少一个摄像头给网站,你才能看到摄像头的名字。这让在我们的界面上选择摄像头变得更不方便,因为尽管你能获取到设备 ID,你还是不能分辨哪个摄像头是哪个。 目前我们还没有处理下拉选择框来改变摄像头。在这之前,让我们来看另一种能改变哪个摄像头被使用的方法。 FacingMode FacingMode 约束是一个可以用来选择摄像头的替代方法。这个方法比起通过 enumerateDevices 函数获取 ID 来说更不那么精确,但在移动设备上效果非常好。对于这个约束,一共有四种选项可供你选择:用户(user),环境(environment),左(left),右(right)。MDN 上的文档对这个约束做了详细介绍, 以本文的目的我们将使用用户和环境模式,在移动设备上它们正好对应到前置和后置摄像头。 要使用 facingMode 约束我们需要修改调用 getUserMedia 时使用的 constraints 对象。对于 video 我们需要一个对象来控制具体的约束,而不是给一个 true 值。像这样修改代码来使用前置摄像头: button.addEventListener('click', event => { const videoConstraints = { facingMode: 'user' }; const constraints = { video: videoConstraints, audio: false }; 现在可以用你的手机测试。你应该能看到前置摄像头被使用。更改 facingMode 为 environment 再试一次, 使用的应该是后置摄像头。让我们把这些代码和上面通过 enumerateDevices 获取到的结果放到一块儿,只要我们获得了读取摄像头数据的权限,就能构建一个摄像头切换器了。 切换摄像头 现在我们有在首次选择时挑选用户或环境摄像头的代码了,但如果我们要切换摄像头那还有一丢丢额外的工作要做。 首先,我们应该保留对当前流的引用,这样当我们切换到另一个流时就能停止当前流。在 app.js 的最前面添加一个额外的变量和辅助函数来停止流中的轨。 const video = document.getElementById('video'); const button = document.getElementById('button'); const select = document.getElementById('select'); let currentStream; function stopMediaTracks(stream) { stream.getTracks().forEach(track => { track.stop(); }); } 函数 stopMediaTracks 接收一个媒体流,循环遍历流中的每一个媒体轨道,调用 stop 方法停止媒体轨。 我们要在点击同一个按钮时改变摄像头,所以我们需要更新一下按钮的事件监听器了。如果当前有媒体流,我们应该先停止掉它。然后我们要检查
元素看是否选择了特定的设备,然后基于此构造 media constraints 对象。
这样修改按钮的点击处理函数和 video constraints:
button.addEventListener('click', event => {
if (typeof currentStream !== 'undefined') {
stopMediaTracks(currentStream);
}
const videoConstraints = {};
if (select.value === '') {
videoConstraints.facingMode = 'environment';
} else {
videoConstraints.deviceId = { exact: select.value };
}
const constraints = {
video: videoConstraints,
audio: false
};
当我们想通过 deviceId 来选择设备时,使用 exact 约束。 可是对于 facingMode,我们没有使用 exact 约束, 否则在一个无法识别有没有用户或环境模式的设备上将会失败,导致我们什么媒体设备也拿不到。
当我们获得使用视频的权限时,在点击处理函数内,我们还要修改一些别的东西。把传递给函数的新流赋值给 currentStream 以便后续调用 stop,触发另一次 enumerateDevices 的调用。
enumerateDevices 返回一个 promise,所以在我们的 then 函数中可以直接返回它,然后链式创建一个新的 then 把结果传递给 gotDevices 函数处理。
用以下代码替换现有的 getUserMedia 调用:
button.addEventListener('click', event => {
if (typeof currentStream !== 'undefined') {
stopMediaTracks(currentStream);
}
const videoConstraints = {};
if (select.value === '') {
videoConstraints.facingMode = 'environment';
} else {
videoConstraints.deviceId = { exact: select.value };
}
const constraints = {
video: videoConstraints,
audio: false
};
navigator.mediaDevices
.getUserMedia(constraints)
.then(stream => {
currentStream = stream;
video.srcObject = stream;
return navigator.mediaDevices.enumerateDevices();
})
.then(gotDevices)
.catch(error => {
console.error(error);
});
});
当你添加完所有的代码,你的 app.js 应该看起来像这个文件一样。刷新页面然后你就能愉快地选择和改变摄像头了。这个页面在移动设备和电脑上都有效。
下一步
我们已经看到如何通过使用 facingMode 和 deviceId 约束来选择用户的摄像头。记住,在你有权限使用摄像头之前,facingMode 更可靠,但是选择 deviceId 更加精确。你可以从 GitHub 仓库 中得到所有本文中的代码,你也可以从这里尝试在线版的应用。
如果你正在使用 Twilio Video 构建视频应用,你可以在调用 connect 或者 createLocalVideoTrack的时候使用这些 constraints。
对于视频聊天来说,选择和切换摄像头是非常有用的功能,允许用户在你的应用界面准确地选择他们想用的摄像头,并且还能做到在视频通话时分享你的屏幕。
JavaScript 使用 mediaDevices API 选择摄像头的更多相关文章
- ArcGIS JavaScript API4.8 底图选择的几种方案
创建一个HTML页面,引入ArcGIS JavaScript API,在<body>标签内创建一个div并添加ID值,在<head>标签内设置样式<style>,在 ...
- Javascript实现百度API
百度地图JavaScript API是一套由JavaScript语言编写的应用程序接口,可帮助您在网站中构建功能丰富.交互性强的地图应用,支持PC端和移动端基于浏览器的地图应用开发,且支持HTML5特 ...
- JavaScript 对象所有API解析【2020版】
JavaScript 对象所有API解析[2020版] 写于 2019年08月20日,虽然是2019年写的文章,但现在2020年依旧不过时,现在补充了2019年新增的ES10 Object.fromE ...
- javascript 百度地图API - demo
<!DOCTYPE html> <html> <head> <meta name="viewport" content="ini ...
- 在线聊天室的实现(1)--websocket协议和javascript版的api
前言: 大家刚学socket编程的时候, 往往以聊天室作为学习DEMO, 实现简单且上手容易. 该Demo被不同语言实现和演绎, 网上相关资料亦不胜枚举. 以至于很多技术书籍在讲解网络相关的编程时, ...
- 第二篇、JavaScript常用的API
下面是我整理的一些JavaScript常用的API清单. 目录 元素查找 class操作 节点操作 属性操作 内容操作 css操作 位置大小 事件 DOM加载完毕 绑定上下文 去除空格 Ajax JS ...
- [Javascript] JSON.parse API
JSON (JavaScript Object Notation) is a standard method to serialize JavaScript objects and is common ...
- 每天一个JavaScript实例-动态省份选择城市
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...
- 《JAVASCRIPT高级程序设计》选择框脚本和富文本编辑
一.选择框脚本 选择框也是表单的一个字段,是通过<select>和<option>元素来创建的,需要使用javascript来控制.选择框拥有以下的属性和方法: 以下介绍一些选 ...
随机推荐
- maveb安装与配置(win10)
转载:https://www.cnblogs.com/eagle6688/p/7838224.html 看了几篇博客,感觉这篇博客写的含金量最高了,因为我电脑的系统是win10的,所以配置有细微的差别 ...
- 学习windows编程 day4 之 自定义映射
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRU ...
- idea中的常用快捷键
idea中常用的快捷键以及一些奇淫技巧 , 加快我们的开发效率 !!! 知道类名查找你本地以的类 (你pom中依赖的类+你自己创建的类)-------------> ctrl+shi ...
- golang匿名结构体
go语言定义结构体类型时可以仅指定字段类型而不指定字段名字.这种字段叫做匿名字段(anonymous field). Go语言有一个特性允许只声明一个成员对应的数据类型而不指名成员的名字:这类成员就 ...
- VS Code调试Golang提示Failed to continue:Check the debug console for details.
解决方法: 打开调试面板 VSCode->查看->调试 添加调试目标 在"没有调试"的下拉框中点击"添加配置.."添加目标调试配置 在" ...
- QT_文本编辑器_源码下载
源码下载: 链接: http://pan.baidu.com/s/1c21EVRy 密码: qub8 实现主要的功能有:新建,打开,保存,另存为,查找(查找的时候需要先将光标放到最下面位置才能查全,不 ...
- Linux 命令详解(九)轻易删除OpenSSL 的后果
警告自己,不要轻易的去删除系统的软件 1.composer自动更新出现错误 www@TinywanAliYun:~/web/go-study-line$ composer self-update Ke ...
- web.py 模板错误记录
错误信息 Traceback (most recent call last): File , in process return self.handle() File , in handle retu ...
- Python探测主机端口是否存活
#!/usr/bin/python3 import socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) result = s ...
- Javaweb学习笔记——(四)——————JavaScript基础&DOM目录
1.案例一:在末尾添加节点 第一个:获取到ul标签 第二部:创建li标签 document.createElement("标签名称")方法 第三步:创建文本 document.cr ...