一、先看效果
您可以狠狠地点击这里:JS每隔一秒提取视频缩略图demo
例如,选择本地视频后:
输入在线的视频文件地址后,注意,该地址要允许跨域,也可以看到缩略图出现了,前后也就几秒钟的时间,还是挺快的。
二、如何实现?
方法一,对视频帧进行完全解码,然后进行提取,相关实现可以参考"mp4box.js加WebCodecs 解码MP4视频帧并渲染"一文。
不过此方法对资源占用较大,而缩略图仅仅是视频的部分帧信息,有些高射炮打蚊子,不划算。
方法二,利用video元素进行绘制。
具体描述为:
希望绘制哪一帧视频,就让视频跳到这一帧的画面,然后使用canvas直接绘制该video元素。
实现细节
不过具体实现的时候还是有不少细节需要注意的。
-
视频的src地址必须是本地地址,而不能是在线地址,否则可能会有绘制异常的问题,例如,你要绘制最后1秒的缩略图,在线视频往往当前画面资源是未加载的,你再去绘制,就会有很长的等待时间,画面就可能是黑色的。
-
video.currentTime = xxx可以让视频定位到具体的时间,但是此过程并不是即时的,需要使用 timeupdate 事件进行监控,例如:
video.addEventListener('timeupdate', () => { // 绘制当前video }); video.currentTime = 1;
完整代码
为了方便大家的使用与学习,对于缩略图的提取,我直接弄了个方法,以1秒为间隔,批量返回视频的缩略图,大家可以直接使用,源码如下:
const handleGetVideoThumb = function (url, options = {}) {
if (typeof url != 'string') {
return;
}
// 默认参数
const defaults = {
onLoading: () => {},
onLoaded: () => {},
onFinish: (arr) => {}
};
`const params = Object.assign({}, defaults, options);
// 基于视频元素绘制缩略图,而非解码视频
const video = document.createElement('video');
// 静音
video.muted = true;
// 绘制缩略图的canvas画布元素
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d', {
willReadFrequently: true
});
// 绘制缩略图的标志量
let isTimeUpdated = false;
// 几个视频事件
// 1. 获取视频尺寸
video.addEventListener('loadedmetadata', () => {
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
// 开始执行绘制
draw();
});
// 2. 触发绘制监控
video.addEventListener('timeupdate', () => {
isTimeUpdated = true;
});
// 获取视频数据
params.onLoading();
// 请求视频地址,如果是本地文件,直接执行
if (/^blob:|base64,/i.test(url)) {
video.src = url;
} else {
fetch(url).then(res => res.blob()).then(blob => {
params.onLoaded();
// 赋予视频
video.src = URL.createObjectURL(blob);
});
}
// 绘制方法
const draw = () => {
const arrThumb = [];
const duration = video.duration;
let seekTime = 0.1;
const loop = () => {
if (isTimeUpdated) {
context.clearRect(0, 0, canvas.width, canvas.height);
context.drawImage(video, 0, 0, canvas.width, canvas.height);
canvas.toBlob(blob => {
arrThumb.push(URL.createObjectURL(blob));
seekTime += 1;
if (seekTime > duration) {
params.onFinish(arrThumb);
return;
}
step();
}, 'image/jpeg');
return;
}
// 监控状态
requestAnimationFrame(loop);
}
// 逐步绘制,因为currentTime修改生效是异步的
const step = () => {
isTimeUpdated = false;
video.currentTime = seekTime;
loop();
}
step();
}
`
};
语法
handleGetVideoThumb(url, {
onLoading: () => {},
onLoaded: () => {},
onFinish: (arr) => {}
})
其中,最主要的可选参数就是 onFinish 方法,其支持一个参数,为数组,里面就是1:1和原始视频尺寸一样的缩略图的 blob 地址,以秒为间隔。
更具体的使用可以参考demo页面的代码示意。
如果你希望获得某一具体时间的缩略图,可以参考本文提供的方法,稍加改造就可以。
例如将 seekTime 设置为你需要的时间,只绘制一次就执行params.onFinish方法。
三、有感而发
在之前,我一直以为<video>
元素和<audio>
元素一样,是个比较鸡肋的特性,不能应对复杂的需求。
最近发现,我的认识浅薄了,video还是可以的,不然不会只有Web Audio API而没有Web Video API。
琢磨了下,说不定试试绘制也是可以的,可能会有点延时偏差,应该好解决,再研究研究吧。
好吧,差不多就说这么多吧。
年底了,赶书稿,就不多啰嗦了。
感谢阅读,欢迎分享。
(本篇完)