logo

​为了在QT上实现打开摄像头,拍照等操作。就写了这个。

1. 写一个vue组件

先写一个vue的组件,其内容基本如下:

1
2
3
4
5
6
7
8
9
<el-button type="primary" @click="openVideo">打开摄像头</el-button>
<el-button @click="closeVideo">关闭摄像头</el-button>
<el-button type="danger" @click="screenshot">截图</el-button>
<div>
<video id="video" ref="video" />
</div>
<canvas ref="canvas" width="400" height="300" style="display:none" />
<!-- 截图展示图片 -->
<img ref="screenshot">

2. 打开摄像头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// 打开摄像头
openVideo() {
// 老的浏览器可能根本没有实现 mediaDevices,所以我们可以先设置一个空的对象
console.log("navigator.mediaDevices", navigator.mediaDevices);
if (navigator.mediaDevices === undefined) {
navigator.mediaDevices = {};
}
// 一些浏览器部分支持 mediaDevices。我们不能直接给对象设置 getUserMedia
// 因为这样可能会覆盖已有的属性。这里我们只会在没有getUserMedia属性的时候添加它。
if (navigator.mediaDevices.getUserMedia === undefined) {
navigator.mediaDevices.getUserMedia = constraints => {
// 首先,如果有getUserMedia的话,就获得它
const getUserMedia =
navigator.getUserMedia ||
(navigator.getUserMedia =
navigator.mozGetUserMedia ||
navigator.webkitGetUserMedia ||
navigator.msGetUserMedia);
console.log("getUserMedia", getUserMedia);
// 一些浏览器根本没实现它 - 那么就返回一个error到promise的reject来保持一个统一的接口
if (!getUserMedia) {
return Promise.reject(new Error("该浏览器暂不支持摄像头!"));
}
// 否则,为老的navigator.getUserMedia方法包裹一个Promise
return new Promise((resolve, reject) => {
getUserMedia.call(navigator, constraints, resolve, reject);
});
};
}
const constraints = {
audio: true,
video: {
width: { ideal: 1280, max: 1920 },
height: { ideal: 720, max: 1080 }
}
};
console.log("window.navigator", window.navigator);
window.navigator.mediaDevices
.getUserMedia(constraints)
.then(stream => {
const video = this.$refs.video;
// 旧的浏览器可能没有srcObject
if ("srcObject" in video) {
video.srcObject = stream;
} else {
// 防止在新的浏览器里使用它,因为它已经不再支持了
video.src = window.URL.createObjectURL(stream);
}
video.onloadedmetadata = e => {
video.play();
};
})
.catch(err => {
console.log(err.name + ": " + err.message);
});
}

没错,这个代码是在mdn上的,几乎没有任何改变,例子讲的挺好的。

3. 截图并保存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 截图
screenshot() {
// 获取视频
const video = this.$refs.video;
// 获取截图的地址
const screenshot = this.$refs.screenshot;
// 获取canvas
const canvas = this.$refs.canvas;
// 渲染一个2d平面的视图
const ctx = canvas.getContext("2d");
// 设置canvas 视图文件地址和大小
ctx.drawImage(video, 0, 0, 400, 300);
// 将数据转为base64赋值给img标签的src属性
screenshot.src = canvas.toDataURL("image/png");
console.log("screenshot.src", screenshot.src);
const imgData = canvas
.toDataURL("image/png")
.replace("image/png", "image/octet-stream");
// 下载图片到本地
const save_link = document.createElementNS(
"http://www.w3.org/1999/xhtml",
"a"
);
save_link.href = imgData;
save_link.download = "file_" + new Date().getTime() + ".png";
save_link.click();
}

4. 关闭摄像头

1
2
3
4
5
6
7
8
9
// 关闭摄像头
closeVideo() {
console.log("srcObject", this.$refs.video.srcObject);
const srcObject = this.$refs.video.srcObject ? this.$refs.video.srcObject.getTracks() : this.$refs.video.src.getTracks()
srcObject.forEach(track => {
track.stop();
this.$refs.video.src = null;
});
},

关闭摄像头,是先获取这个这个视频所在的任务,然后关闭每一个任务,并将其地址设为空。

5. 在electron遇到的问题

开发的时候,在electron中使用遇到调用不了摄像头的问题。经过多次debug之后,发现是http和https的原因。

所以打包好后的文件在electron中使用,是可以调用摄像头并截图保存的。