这篇文章发布于 2024年01月15日,星期一,23:55,归类于 JS实例。 阅读 13390 次, 今日 25 次 12 条评论
by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=11103 鑫空间-鑫生活
本文欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可以联系授权。
一、先看效果
您可以狠狠地点击这里: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。
琢磨了下,说不定试试绘制也是可以的,可能会有点延时偏差,应该好解决,再研究研究吧。
好吧,差不多就说这么多吧。
年底了,赶书稿,就不多啰嗦了。
感谢阅读,欢迎。
本文为原创文章,欢迎分享,勿全文转载,如果实在喜欢,可收藏,永不过期,且会及时更新知识点及修正错误,阅读体验也更好。
本文地址:https://www.zhangxinxu.com/wordpress/?p=11103
(本篇完)
- JS audio加图片序列或canvas转webM/MP4的实现 (0.635)
- 使用JS提取视频中的音频资源 (0.196)
- 纯前端实现可传图可字幕台词定制的GIF表情生成器 (0.183)
- 又get到了,JS复制图片到剪切板 (0.183)
- 让所有浏览器支持HTML5 video视频标签 (0.127)
- 玩转HTML5 Video视频WebVTT字幕使用样式与制作 (0.127)
- 揭秘视频网站video视频倍速播放的实现 (0.127)
- HTML5 video视频播放Picture-in-Picture画中画技术 (0.127)
- HandBrake乃MP4免费压缩webm转换工具不二之选 (0.127)
- 从天猫某活动视频不必要的3次请求说起 (0.127)
- 纯JS实现图像的人脸识别功能 (RANDOM - 0.103)
我有一个疑问,为什么要在 loop 函数中添加 requestAnimationFrame(loop)
我添加了这一行代码后 loop函数每秒调用几十次。从视频中获取的图片也重复了好多。
浏览器如何获取ios设备上传的.mov格式的视频封面图呀。试了。video标签不支持.mov格式的视频。纯前端有办法解决吗!
估计得走 ffmpeg + wasm 了
您好 我用了您的这个demo代码 发现个别的视频在windows的edge环境存在问题 timeupdate 监听不执行 您这边提供的在线demo 在某些视频下没有反应并没有抽出图片帧 感觉存在兼容问题 期待您的回复。
有对应的在线视频地址吗,我看看怎么回事?
把step函数中的 loop 用setTimeout包裹起来再尝试一下。
应该是 异步的原因,在 video.currentTime = seekTime 之后 timeupdate还未监听到,就直接去执行了loop 函数,导致 loop 函数中 isTimeUpdated 还是 false。
我尝试把 loop 保存到draw函数外部,timeupdate 回调中去执行 loop 就可以解决这个问题。
又看到大大的文章了,最近我也做 Web 音视频相关的项目,
这个 DEMO 可以获取任意指定时间的视频帧(或图片):
https://hughfenghen.github.io/WebAV/demo/1_4-mp4-previewer
这个 DEMO 可以遍历视频的所有帧: https://hughfenghen.github.io/WebAV/demo/1_1-decode-video
基于 WebAV (mp4box.js + WebCodecs) 实现
Uncaught TypeError: Failed to execute ‘readAsArrayBuffer’ on ‘FileReader’: parameter 1 is not of type ‘Blob’.
at file.onchange (js-get-mp4-video-image-demo.php:358:12)
我打开demo 会有这个报错耶,这是为啥
这个好用,客户上传视频,不用自己截图了
你好 大神,看你文章很久了。然后最近发现一个东西 html5原生支持的 dialog 标签,能讲一讲这个东西吗? …
这都用了不知道多少年了,明年我的新书会有介绍,可以关注下