需求来源于一个页面上会展示很多视频让用户进行选择,如果都是直接以 video 标签的形式放在上面对加载的影响比较大,看了一下爱腾优的视频列表实际上也都只是显示一张图片,因为列表中放视频第一帧的截图基本上就能看出来这是什么视频了。
现在问题变成了上传视频的同时生成对应的第一帧截图,这个工作可以后台处理也可以前台处理,这边主要描述一下怎么在前台生成好截图传给后台。
首先是怎么根据视频截图,可以调用 canvas 的 drawImage (opens new window) 方法来绘制图像,然后通过 toDataURL (opens new window) 返回一个包含图片展示的 data URI (opens new window)。
const canvas = document.createElement('canvas');
const video = document.getElementById('video');
const img = document.getElementById('img');
canvas.width = video.clientWidth;
canvas.height = video.clientHeight;
// 监听media中的首帧已经加载事件
video.onloadeddata = () => {
canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
img.src = canvas.toDataURL('image/png');
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
如果是截图一个已经加载好的视频可以通过上述方式操作,如果是处理本地上传的视频,那么就需要在上传之前先创建 URL 对象给 video,然后再调用上述方法操作。
注意:video 不能直接播放本地的视频,需要部署到服务器作为资源或者是用户上传的时候可以读取文件流。
const video = document.createElement("video");
// 创建一个URL对象作为video的src,不要忘记通过revokeObjectURL释放
video.src = window.URL.createObjectURL(file);
// 需要配置静音且默认播放,不然可能截图的可能是个空白
video.muted = true;
video.autoplay = true;
video.onloadeddata = () => {
// 如果还是出现空白可以手动播放一下
// video.play();
// 进行之前类似的操作进行截图就行了
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
通过上述步骤就可以在用户上传视频的时候截图了,如果把 video 的 src 改成一个在线的 src 也可以对任意在线视频进行截图。不过一个光秃秃的图片看着有点不太像是视频的样子,如果右下角加上视频的时长就更好了。video (opens new window) 在 loadedmetadata 事件之后就可以获取到一些 meta 信息了,比如 duration 就是一个以秒为单位的视频时长,转换成 00: 01 这种表现形式通过 canvas 的 fillText 放在右下角就行了。
完整例子:
const captureImage = (videoDom: HTMLVideoElement) => {
const img = document.getElementById('img');
const canvas = document.createElement("canvas");
canvas.width = 160; //可以根据实际需求调整
canvas.height = (canvas.width * videoDom.videoHeight) / videoDom.videoWidth;
const ctx = canvas.getContext("2d");
if (ctx) {
// 视频第一帧
ctx.drawImage(videoDom, 0, 0, canvas.width, canvas.height);
// 视频时长
ctx.font = "12px";
ctx.fillStyle = "#fff";
const duration = formatSeconds(round(videoDom.duration)); // 格式化秒为hh:mm:ss
// 获取了一下文本宽度,因为fillText的坐标是左上角
const textWidth = ctx.measureText(duration).width;
ctx.fillText(
duration,
canvas.width - textWidth - 10,
canvas.height - 10,
textWidth
);
img.src = canvas.toDataURL('image/png');
}
};
const handleUpload = (file: File) => {
const video = document.createElement("video");
const src = window.URL.createObjectURL(file);
video.src = src;
video.muted = true;
video.autoplay = true;
video.onloadeddata = () => {
captureImage(video);
window.URL.revokeObjectURL(src);
};
};
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
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