当然不想了解就直接跳转看完整代码
需要注意的是,此功能仅在本地环境或者线上的安全环境(HTTPS)下可用
言归正传,在日常业务开发中,我们经常会遇到调用摄像头的场景
最常见的就是前端调用摄像头,然后生成一张照片,用于业务开发
常规开发
首先我们来看看常规开发模式
代码其实非常简单,直接上代码(已附上完整注释)
渲染视频流
1const video = document.querySelector('#video')
2
3navigator.mediaDevices.enumerateDevices().then((devices) => {
4
5 const videoDevices = devices.filter(
6 (device) => device.kind === 'videoinput'
7 )
8 if(!videoDevices) return console.error(`暂无摄像头`)
9 deviceId = videoDevices[0].deviceId
10
11 renderStream(deviceId)
12})
13
14function renderStream(deviceId){
15
16
17 const constraints = {
18 video:{
19 width: 500,
20 height: 500,
21 deviceId
22 }
23 }
24 navigator.mediaDevices.getUserMedia(constraints).then(stream => {
25
26 video.srcObject = stream
27
28 video.onloadedmetadata = () => {
29 video.play()
30 }
31 })
32 .catch((err) => {
33 console.log(err.name + ': ' + err.message)
34 })
35}
拍照功能
常规的思路,我们肯定是截取 video 的某一帧,然后渲染到 canvas 上面,利用 canvas.toDataURL
转换成数据
1function generateScreenshot(video) {
2
3 const canvas = document.createElement('canvas');
4 canvas.width = video.videoWidth;
5 canvas.height = video.videoHeight;
6
7
8 canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
9
10
11 const imageSrc = canvas.toDataURL('image/png');
12
13
14 return imageSrc;
15}
其实也是因为笔者在真实项目开发中遇到的
canvas.drawImage
在 chromium
有个内存泄露的问题
详见 issues.chromium.org/issues/4047…
才进行了优化后的改造
注意点
vue
等单页面项目特别要注意的是,在 beforeDestroy
钩子函数我们需要把视频流给释放
1beforeDestroy(){
2
3 this.stream.getTracks().forEach(track => {
4 track.enabled = false
5 track.stop()
6 stream.removeTrack(track)
7 })
8
9 video.srcObject = null
10}
改用 useUserMedia hook
先上完整代码,便于后续阅读
完整代码
- useUserMedia.js
1function useUserMedia(constraints) {
2 if (!navigator.mediaDevices) {
3 return `navigator.mediaDevices is undefined`
4 }
5 return new Promise((resolve, reject) => {
6 navigator.mediaDevices.getUserMedia(constraints)
7 .then(stream => {
8
9 const stop = () => {
10 stream.getTracks().forEach(track => {
11 track.enabled = false
12 track.stop()
13 stream.removeTrack(track)
14 })
15 }
16
17 const track = stream.getVideoTracks()[0]
18 const imageCapture = new ImageCapture(track)
19 resolve({
20 stream,
21 stop,
22 imageCapture
23 })
24 })
25 .catch(reject)
26 })
27}
stop
方法就不做赘述了, 就是基于上面的封装了一下
主要介绍下 ImageCapture api
实验性的:这是一项实验性技术。在将其用于生产之前,请仔细查看浏览器兼容性表。
它可以从 MediaStreamTrack
中捕获静止帧(也就是我们所说的拍照功能)
下文会对拍照功能做详解
渲染视频流
有了这个 hook 我们渲染视频流就非常简单了
1renderStream(deviceId){
2 const constraints = {
3 video:{
4 width: 500,
5 height: 500,
6 deviceId
7 }
8 }
9 const {stream,stop,imageCapture} = useUserMedia(constraints)
10 this.imageCapture = imageCapture
11 this.stop = stop
12 video.srcObject = stream
13 video.onloadedmetadata = () => {
14 video.play()
15 }
16}
17
18beforeDestroy(){
19 this.stop()
20}
拍照功能
重点来看下使用 hook 的拍照功能有多方便吧
1function takePhoto(){
2 this.imageCapture.takePhoto().then((blob) => {
3
4
5
6 })
7}
本期内容,主要还是代码,没有过多的花里胡哨的注释
相信彦祖们能够一眼秒懂
主要还是对于使用业务的 api 的使用和调研
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <title>Document</title>
8</head>
9
10<body>
11 <video id="video">
12
13 </video>
14
15 <img width="200" height="200" id="img"/>
16
17 <button>拍照</button>
18</body>
19<script>
20function useUserMedia(constraints) {
21 if (!navigator.mediaDevices) {
22 return `navigator.mediaDevices is undefined`
23 }
24 return new Promise((resolve, reject) => {
25 navigator.mediaDevices.getUserMedia(constraints)
26 .then(stream => {
27
28 const stop = () => {
29 stream.getTracks().forEach(track => {
30 track.enabled = false
31 track.stop()
32 stream.removeTrack(track)
33 })
34 }
35
36 const track = stream.getVideoTracks()[0]
37 const imageCapture = new ImageCapture(track)
38 resolve({
39 stream,
40 stop,
41 imageCapture
42 })
43 })
44 .catch(reject)
45 })
46}
47
48const video = document.querySelector('#video')
49const img = document.querySelector('#img')
50
51navigator.mediaDevices.enumerateDevices().then((devices) => {
52
53 const videoDevices = devices.filter(
54 (device) => device.kind === 'videoinput'
55 )
56 if(!videoDevices) return console.error(`暂无摄像头`)
57 deviceId = videoDevices[0].deviceId
58
59 renderStream(deviceId)
60})
61let _imageCapture = null
62async function renderStream(deviceId){
63 const constraints = {
64 video:{
65 width:200,
66 height:200,
67 deviceId
68 }
69 }
70 const {stream,imageCapture} = await useUserMedia(constraints)
71 _imageCapture = imageCapture
72 video.srcObject = stream
73 video.onloadedmetadata = () => {
74 video.play()
75 }
76}
77
78document.querySelector('button').addEventListener('click',takePhoto)
79
80function takePhoto(){
81 _imageCapture.takePhoto().then((blob) => {
82 const imgSrc = URL.createObjectURL(blob);
83 img.src = imgSrc
84 })
85}
86
87</script>
88
89</html>
一周的梅农,一辈子的码农,牛马一生,唉~
感谢彦祖们的阅读
个人能力有限
_如有不对,欢迎指正_🌟 _如有帮助,建议小心心大拇指三连_🌟
个人笔记记录 2021 ~ 2025