添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
痴情的松鼠  ·  迈科唯·  5 月前    · 
寂寞的哑铃  ·  如何使用Open ...·  7 月前    · 

使用JS快速获取video视频任意位置的缩略图

这篇文章发布于 2024年01月15日,星期一,23:55,归类于 JS实例 。 阅读 11664 次, 今日 9 次 12 条评论

一、先看效果

您可以狠狠地点击这里: 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。

    琢磨了下,说不定试试绘制也是可以的,可能会有点延时偏差,应该好解决,再研究研究吧。

    好吧,差不多就说这么多吧。

    年底了,赶书稿,就不多啰嗦了。

    感谢阅读,欢迎

    (本篇完)

  • 我有一个疑问,为什么要在 loop 函数中添加 requestAnimationFrame(loop)

    我添加了这一行代码后 loop函数每秒调用几十次。从视频中获取的图片也重复了好多。

    2024年01月25日 18:02

    您好 我用了您的这个demo代码 发现个别的视频在windows的edge环境存在问题 timeupdate 监听不执行 您这边提供的在线demo 在某些视频下没有反应并没有抽出图片帧 感觉存在兼容问题 期待您的回复。

    2024年09月2日 15:38

    应该是 异步的原因,在 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) 实现

    2024年01月19日 15:09

    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 会有这个报错耶,这是为啥

    张鑫旭 ,09年 华中科技大学 毕业,现 上海 ,就职于 阅文集团 ,专注 web前端 偏前领域,著有 《CSS世界》 《CSS选择器世界》 《CSS新世界》

    邮箱: [email protected]

    关注我: 微信 微博 掘金 知乎 头条 B站 Gitee

  • Stylus-NodeJS下构建更富表现力/动态/健壮的CSS (69)
  • jQuery页面滚动图片等元素动态加载实现 (65)
  • CSS3 transform对普通元素的N多渲染影响 (61)
  • 以20像素为基准的CSS网页布局实践分享 (61)
  • 小tips:了解CSS变量var (61)
  • 今年热议

  •