使用 HTML5 实现摄像头实时视频流并拍照

4,675 阅读4分钟

这是一篇技术笔记,记录开发调试 H5 调用摄像头实时展示视频流并拍照过程中遇到的问题。

现代浏览器提供了越来越强大的功能,我们完全可以通过 HTML5 来调用硬件资源,例如摄像头、麦克风等。 最近遇到一个需求需要调用摄像头,实时展示视频流,并拍照。 这里我们可以用到浏览器的 navigator.mediaDevices.getUserMedia API,来完成此需求。

getUserMedia API

MediaStream

在介绍 getUserMedia API 前我们先来了解下 MediaStreamMediaStream 接口是一个媒体内容的流。一个流包含几个轨道,比如视频和音频轨道。 就类似我们再上传图片时,通常会将图片文件转换为 Blob 对象或 File 对象,并通过 FormData 对象将其作为数据流发送到服务器。MediaStream 也是一种流。

getUserMedia

getUserMedia 作用时,我们可以通过该 API 实时捕获视频或音频流,然后在网页中使用这些数据。
它返回一个 Promise 对象,我们可以通过 .then() 方法处理成功返回的视频流,通过 .catch() 方法处理拒绝授权或发生错误的情况。

var promise = navigator.mediaDevices.getUserMedia(constraints);

其中 constraints 是一个对象,用来指定我们希望访问哪种媒体设备。它可以包含两个属性:video 和 audio,分别用于视频和音频。

navigator.mediaDevices.getUserMedia({
	video: true,
	audio: true,
});

设置前后置摄像头

同时我们也可以增加对 video 的配置,来设置摄像头,比如设置使用前置摄像头还是后置摄像头。

前置摄像头:

{ audio: true, video: { facingMode: "user" } }

后置摄像头:

{ audio: true, video: { facingMode: { exact: "environment" } } }

设置分辨率

强制要求获取特定的尺寸时,可以使用关键字minmax 或者 exact(就是 min == max)。以下参数表示要求获取最低为 1280x720 的分辨率。

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

如果直接设置 width 和 height,如下设置:

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

浏览器会试着满足这个请求参数,但是如果无法准确满足此请求中参数要求或者用户选择覆盖了请求中的参数时,有可能返回其他的分辨率。

实时视频展示

我们可以通过 getUserMedia API 和 <video> 配合实时展示摄像头拍摄的视频。 原理是把 getUserMedia 生成的 MediaStream 放入到 <video> 的 srcObject 中。

完整代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Camera Capture</title>
</head>
<body>
	<video id="video" width="640" height="480" autoplay></video>
	<button id="capture">Capture</button>
	<canvas id="canvas" width="640" height="480"></canvas>
	<img id="photo" alt="The captured image will appear in this box.">

	<script>
		const getCameraStream = async () => {
			if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia)) {
				return navigator.mediaDevices.getUserMedia({
					video: {
						facingMode: 'environment',
						width: 3840,
						height: 2160,
					},
					audio: false,
				});
			} else {
				return Promise.reject(new Error('设备不支持开启摄像头'));
			}
		};

		const video = document.getElementById('video');
		const canvas = document.getElementById('canvas');
		const photo = document.getElementById('photo');
		const captureButton = document.getElementById('capture');

		// 获取摄像头视频流
		try {
			const stream = await getCameraStream()
			video.playsInline = true;
			video.autoplay = true;
			video.srcObject = stream;
		} catch (err) {
			console.error('Error accessing the camera.', err);
		}
		
		// 捕捉按钮点击事件
		captureButton.addEventListener('click', () => {
			// 使用 canvas 截图
			const context = canvas.getContext('2d');
			context.drawImage(video, 0, 0, canvas.width, canvas.height);
			// 将截图转换为图像 URL
			const dataURL = canvas.toDataURL('image/png');
			// 显示截图
			photo.src = dataURL;
		});
	</script>
</body>
</html>
  • 使用 navigator.mediaDevices.getUserMedia() 方法请求获取视频流。
  • 成功时,我们通过 video.srcObject = stream; 将返回的视频流绑定到 <video> 元素上。
  • 如果用户拒绝了摄像头权限,或者浏览器不支持这个 API,会进入 .catch(),并在控制台打印错误信息。

HTTPS 安全限制

现代浏览器要求在 HTTPS 连接下才能访问摄像头等敏感设备。因此,确保你在本地开发时,使用 localhost 或者使用 HTTPS 部署网页。

但有时我们需要在本地接口使用测试等环境,为了解决跨域,一般我们会把某个测试域名在本地指向 localhost。

hosts 文件:

127.0.0.1 xxx.com

这是本地调试使用的是 http 协议,而且不是 localhost, Chrome 浏览器会限制对摄像头的访问。

这时需要做一些特殊设置。

1、配置Insecure origins treated as secure 在浏览器中输入 chrome://flags/#unsafely-treat-insecure-origin-as-secure 并打开。 填写域名或 IP 地址,并选择 Enabled

Pasted image 20241121171000.png

2、打开对应网站设置

Pasted image 20241121171355.png

3、选择允许的权限

Pasted image 20241121171439.png

设置之后刷新页面,可以发现已经可以使用摄像头了。

捕获照片

我们还可以把摄像头获取的视频流中的一帧渲染在 <canvas> 中,实现拍照功能。

/ 使用 canvas 截图
const context = canvas.getContext('2d');
context.drawImage(video, 0, 0, canvas.width, canvas.height);
// 将截图转换为图像 URL
const dataURL = canvas.toDataURL('image/png');
// 显示截图
photo.src = dataURL;

总结

通过简单的几行代码和调试设置,我们能够充分利用 HTML5 的 getUserMedia API,轻松地调用浏览器的摄像头并实时显示视频流。

这一强大的功能在视频会议、实时监控、拍照应用等多个场景中具有广泛的应用价值。

希望这篇文章能为你提供有益的帮助!