【附源码】跨界救场:如何用纯前端的方式获取视频首帧-程序员宅基地

技术标签: css  java  js  html  javascript  

背景

最近在搬砖时遇到一个问题,在商详页面有些商品只有视频,没有封面图。

我们的交互是用户点击视频封面图调用 native 播放器播放视频,没有封面图视频就没有了载体,就不能展示了。

这个问题有3个解决方案

  1. 后端处理:这种方案虽然可行,但是会影响接口性能,在商详这种关键页面得不偿失。

  2. 客户端处理:客户端处理需要在进入商详页面前预加载视频,会影响页面响应速度,也不太合理。

  3. 前端处理:前端处理必然会用到 video 标签来承载视频,考虑到 video 标签在移动端会有很多兼容性问题,处理起来很复杂,同时也会带来加载的性能消耗。

基于上述三个方案,我们决定在源头解决这个问题,在创建商品时动态获取视频封面并保存。

考虑到这是一个公共能力,我们把处理逻辑写成公共的 npmvideo-cover(https://www.npmjs.com/package/video-cover) ,有兴趣的可以去 npm 上下载和使用。

接下来给大家分享一下这个包的实现方案和使用方法。


进入正题

效果展示

首先我们看下实际使用效果。

获取视频首帧整体过程是比较流畅的,当然,具体的获取时间取决于视频的质量和网速。


整体思路
  1. 动态创建 video 标签,加载视频。通过 video 的 timeupdate 事件来获取截取图片的时机。

  2. 创建 canvas 画布,通过 drawImage 来绘制图像,然后通过 toDataURL 来导出图像信息。

  3. 最后封装一些功能方法来方面使用。


兼容性

canvas 对我们来说是一个熟悉又陌生的技术,在一些技术文档种经常有它的身影出现,但是业务中使用到的地方又不是很多。

要使用这个技术,首先我们来看下兼容性,兼容性不好的话再好的技术也难以运用到业务中去。

canvas兼容性

从上图看来大部分浏览器兼容性没问题,话不多说,开始上代码


代码展示

首先我们要创建 video 标签,这步是关键,图像能不能截取成功就取决于视频能不能展示了。

如果你使用的是网络图片,并且是跨域的。
必须要设置 videocrossOrigin = 'Anonymous',对此元素的 CORS 请求将不设置凭据标志。

否则使用canvas的 toDataURL api会报错 提示:

Uncaught DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported

大概意思就是画布被污染。

文档:参考文档[1]

getVideoCover(callback) {
    const self = this;

    const video = this.video || document.createElement("video");
    const currentTime = self.currentTime;
    video.src = self.url;
    video.style.cssText = `position: fixed; top: -100%; width: 400px; visibility: hidden;`;
    video.controls = "controls";
    // 此处是设置跨域,防止污染canvas画布
    video.crossOrigin = "Anonymous";

    // 设置视频播放进度
    video.currentTime = currentTime;

    // 监听播放进度改变,获取对应帧的截图
    video.addEventListener("timeupdate", () => {
        self.setVideoInfo();

        if (self.currentTime <= self.duration) {
            self.generateCanvas(callback);
        } else {
            self.nextTime()
        }
    });

    this.video = video;

    // 此处必须要append到页面中去,否则会由兼容性问题
    document.body.appendChild(video);
}

通过修改 videocurrentTime 属性,来切换视频的进度,从而触发 timeupdate 事件,完成截图操作。

创建完 video ,接下来就是最关键的部分了,图片生成。

主要思路:

  1. 首先创建一个 canvas

  2. 添加对应的尺寸,这个尺寸尽量和视频的比例保持一致,否则生成的图片会变形。

  3. 通过 toDataURL 生成 base64 ,直接在页面展示就可以了。

generateCanvas(callback) {
    const self = this;
    const canvas = this.canvas || document.createElement("canvas");

    // 此处添加 alpha 属性,可以忽略透明度,减少图片体积
    const ctx = canvas.getContext("2d", { alpha: false });
    const imgWidth = this.imgWidth;
    const isCheckImageColor = this.isCheckImageColor;
    let videoWidth = this.videoWidth;
    let videoHeight = this.videoHeight;

    if (!this.canvas) {
        if (imgWidth) {
            videoHeight = imgWidth / (videoWidth / videoHeight);
            videoWidth = imgWidth;
        }

        canvas.width = videoWidth;
        canvas.height = videoHeight;
    }

    ctx.drawImage(this.video, 0, 0, canvas.width, canvas.height);

    // 如果开启图片校验模式
    if (isCheckImageColor) {
        const checkImageResult = this.checkImage(ctx, videoWidth, videoHeight);

        // 如果图片是纯色图片,会获取切换播放时间,获取下一秒的截图
        if (!checkImageResult) {
            this.nextTime();

            return;
        }
    }

    const img = canvas.toDataURL(this.imageType, self.quality);

    callback && callback(img);
}

写到此处,基本的功能已经实现了,接下来就介绍一些工具方法来让我们的功能更加的完善。

  • 图片内容校验。
    有些视频的前 1s 是黑屏,这样截下来的图片是没有任何价值的,为了避免这种问题。我对视频做了是否是纯色的校验。

    使用 canvasgetImageData 来获取图片像素信息。

    getImageData 会返回一个 Uint8ClampedArray 的类型化数组。

    每一个值存储的是 0-255 的整型。

    每4个为一组,分别代表 R G B A。

    遍历整个数组,如果发现颜色值的种类超过配置的数量,即为有图像的图,否则为纯色图。

    如果大家有更好的处理方案,欢迎在评论区域留言。

      checkImage(ctx, width, height) {
          const imgData = ctx.getImageData(0, 0, width, height);
          const imgDataContent = imgData.data;
          const rgbObj = {};
          let differentLen = 0;
    
          for (let i = 0, len = imgDataContent.length; i < len; i += 4) {
              const key = imgDataContent.slice(i, i + 4).join("");
    
              if (!rgbObj[key]) {
                  rgbObj[key] = 1;
                  differentLen++;
              }
    
              // 判断如果颜色超出100种,不是纯图
              if (differentLen > 100) {
                  return true;
              }
          }
    
          return false;
      }
    
  • base64Blob 对象。
    图片本地下载和保存到服务器都需要将base64转换成 Blob对象。

    具体实现步骤为:

      static base64ToBlob(code) {
        if (!code) {
          console.warn("base64不能为空");
          return;
        }
    
        let parts = code.split(";base64,");
        // 获取图片类型
        let contentType = parts[0].split(":")[1];
    
        /**
          * 解码base64
          * Window atob() 方法
          * encodedStr: 必需,是一个通过 btoa() 方法编码的字符串。
          * 该方法返回一个解码的字符串。
          */
        let raw = window.atob(parts[1]);
        let rawLength = raw.length;
    
        // Uint8Array 数组类型表示一个8位无符号整型数组,创建时内容被初始化为0。
        let uInt8Array = new Uint8Array(rawLength);
    
        // 将字符转换成unicode值
        for (let i = 0; i < rawLength; ++i) {
          uInt8Array[i] = raw.charCodeAt(i);
        }
    
        return new Blob([uInt8Array], {
          type: contentType,
        });
      }
    
  1. 通过 window.atob 方法将 base64 解码。

  2. 初始化一个 Uint8Array 类型化数组。

  3. 遍历解码之后的 base64,将每个字符转换成 Unicode 码,并 pushUint8Array 中。

  4. 最后通过 new Blob() 来生成 Blob 对象。

  • 下载文件

    static downloadFile(code) {
      const fileName = Date.now();
    
      if (!code) {
      console.warn("base64不能为空");
      return;
      }
    
      let aLink = document.createElement("a");
      let blob = this.base64ToBlob(code);
      let evt = document.createEvent("HTMLEvents");
      evt.initEvent("click", true, true); //initEvent 不加后两个参数在FF下会报错  事件类型,是否冒泡,是否阻止浏览器的默认行为
      aLink.download = fileName;
      aLink.href = URL.createObjectURL(blob);
      aLink.click();
    }
    
  • 接下来介绍下使用方法

    通过 new VideoCover() 来初始化配置信息

    const cover = new VideoCover({
        // 视频链接
        url: 'https://cdn.huodao.hk/zhaoliangjiadv2.mp4',
        // 初始截图位置,取值范围 1- 视频长度,默认为 1
        currentTime: 1,
        // 生成图片宽度,高度按照视频比例自动计算,默认为800px
        imgWidth: 600,
        // 图片质量,范围 0.2-0.95,默认为 0.95
        quality: 0.9,
        // 图片类型,默认为 image/jpeg
        imageType: 'image/jpeg',
        // 是否开始图片检查,如果为纯图自动获取下一秒,默认为false
        isCheckImageColor: true, 
    })
    

    api方法

    • 生成截图
      getVideoCover
      参数:
      @param { Function } callback 回调函数 示例:

        cover.getVideoCover((res) => {
            console.log(res)
        })
      
    • 获取下一秒视频截图 nextTime 参数:无 示例:

      cover.nextTime()
      
    • 获取指定位置视频截图 jumpTime 参数:@param { Number } time 时间秒数 示例:

      cover.jumpTime(20)
      

    最后试了一下,在 chrome,firefox 等主流浏览器都是可以的。

    由于 video 在移动端的兼容性不是很好,此插件适用于 PC 端。

    未来我们会兼容移动端,希望能在更多的平台得到运用。

    本文源码:https://github.com/18823752727/video-cover 

    npm包地址:https://www.npmjs.com/package/video-cover

    参考文档:

    1. 安全性和“被污染”的 canvas
      https://developer.mozilla.org/zh-CN/docs/Web/HTML/CORS_enabled_image

    2. Uint8Array
      https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array

    3. Blob
      https://developer.mozilla.org/zh-CN/docs/Web/API/Blob/Blob

    4. Base64的编码与解码
      https://developer.mozilla.org/zh-CN/docs/Glossary/Base64

    5. createObjectURL https://developer.mozilla.org/zh-CN/docs/Web/API/URL/createObjectURL

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/P6P7qsW6ua47A2Sb/article/details/115534857

智能推荐

使用nginx解决浏览器跨域问题_nginx不停的xhr-程序员宅基地

文章浏览阅读1k次。通过使用ajax方法跨域请求是浏览器所不允许的,浏览器出于安全考虑是禁止的。警告信息如下:不过jQuery对跨域问题也有解决方案,使用jsonp的方式解决,方法如下:$.ajax({ async:false, url: 'http://www.mysite.com/demo.do', // 跨域URL ty..._nginx不停的xhr

在 Oracle 中配置 extproc 以访问 ST_Geometry-程序员宅基地

文章浏览阅读2k次。关于在 Oracle 中配置 extproc 以访问 ST_Geometry,也就是我们所说的 使用空间SQL 的方法,官方文档链接如下。http://desktop.arcgis.com/zh-cn/arcmap/latest/manage-data/gdbs-in-oracle/configure-oracle-extproc.htm其实简单总结一下,主要就分为以下几个步骤。..._extproc

Linux C++ gbk转为utf-8_linux c++ gbk->utf8-程序员宅基地

文章浏览阅读1.5w次。linux下没有上面的两个函数,需要使用函数 mbstowcs和wcstombsmbstowcs将多字节编码转换为宽字节编码wcstombs将宽字节编码转换为多字节编码这两个函数,转换过程中受到系统编码类型的影响,需要通过设置来设定转换前和转换后的编码类型。通过函数setlocale进行系统编码的设置。linux下输入命名locale -a查看系统支持的编码_linux c++ gbk->utf8

IMP-00009: 导出文件异常结束-程序员宅基地

文章浏览阅读750次。今天准备从生产库向测试库进行数据导入,结果在imp导入的时候遇到“ IMP-00009:导出文件异常结束” 错误,google一下,发现可能有如下原因导致imp的数据太大,没有写buffer和commit两个数据库字符集不同从低版本exp的dmp文件,向高版本imp导出的dmp文件出错传输dmp文件时,文件损坏解决办法:imp时指定..._imp-00009导出文件异常结束

python程序员需要深入掌握的技能_Python用数据说明程序员需要掌握的技能-程序员宅基地

文章浏览阅读143次。当下是一个大数据的时代,各个行业都离不开数据的支持。因此,网络爬虫就应运而生。网络爬虫当下最为火热的是Python,Python开发爬虫相对简单,而且功能库相当完善,力压众多开发语言。本次教程我们爬取前程无忧的招聘信息来分析Python程序员需要掌握那些编程技术。首先在谷歌浏览器打开前程无忧的首页,按F12打开浏览器的开发者工具。浏览器开发者工具是用于捕捉网站的请求信息,通过分析请求信息可以了解请..._初级python程序员能力要求

Spring @Service生成bean名称的规则(当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致)_@service beanname-程序员宅基地

文章浏览阅读7.6k次,点赞2次,收藏6次。@Service标注的bean,类名:ABDemoService查看源码后发现,原来是经过一个特殊处理:当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致public class AnnotationBeanNameGenerator implements BeanNameGenerator { private static final String C..._@service beanname

随便推点

二叉树的各种创建方法_二叉树的建立-程序员宅基地

文章浏览阅读6.9w次,点赞73次,收藏463次。1.前序创建#include&lt;stdio.h&gt;#include&lt;string.h&gt;#include&lt;stdlib.h&gt;#include&lt;malloc.h&gt;#include&lt;iostream&gt;#include&lt;stack&gt;#include&lt;queue&gt;using namespace std;typed_二叉树的建立

解决asp.net导出excel时中文文件名乱码_asp.net utf8 导出中文字符乱码-程序员宅基地

文章浏览阅读7.1k次。在Asp.net上使用Excel导出功能,如果文件名出现中文,便会以乱码视之。 解决方法: fileName = HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8);_asp.net utf8 导出中文字符乱码

笔记-编译原理-实验一-词法分析器设计_对pl/0作以下修改扩充。增加单词-程序员宅基地

文章浏览阅读2.1k次,点赞4次,收藏23次。第一次实验 词法分析实验报告设计思想词法分析的主要任务是根据文法的词汇表以及对应约定的编码进行一定的识别,找出文件中所有的合法的单词,并给出一定的信息作为最后的结果,用于后续语法分析程序的使用;本实验针对 PL/0 语言 的文法、词汇表编写一个词法分析程序,对于每个单词根据词汇表输出: (单词种类, 单词的值) 二元对。词汇表:种别编码单词符号助记符0beginb..._对pl/0作以下修改扩充。增加单词

android adb shell 权限,android adb shell权限被拒绝-程序员宅基地

文章浏览阅读773次。我在使用adb.exe时遇到了麻烦.我想使用与bash相同的adb.exe shell提示符,所以我决定更改默认的bash二进制文件(当然二进制文件是交叉编译的,一切都很完美)更改bash二进制文件遵循以下顺序> adb remount> adb push bash / system / bin /> adb shell> cd / system / bin> chm..._adb shell mv 权限

投影仪-相机标定_相机-投影仪标定-程序员宅基地

文章浏览阅读6.8k次,点赞12次,收藏125次。1. 单目相机标定引言相机标定已经研究多年,标定的算法可以分为基于摄影测量的标定和自标定。其中,应用最为广泛的还是张正友标定法。这是一种简单灵活、高鲁棒性、低成本的相机标定算法。仅需要一台相机和一块平面标定板构建相机标定系统,在标定过程中,相机拍摄多个角度下(至少两个角度,推荐10~20个角度)的标定板图像(相机和标定板都可以移动),即可对相机的内外参数进行标定。下面介绍张氏标定法(以下也这么称呼)的原理。原理相机模型和单应矩阵相机标定,就是对相机的内外参数进行计算的过程,从而得到物体到图像的投影_相机-投影仪标定

Wayland架构、渲染、硬件支持-程序员宅基地

文章浏览阅读2.2k次。文章目录Wayland 架构Wayland 渲染Wayland的 硬件支持简 述: 翻译一篇关于和 wayland 有关的技术文章, 其英文标题为Wayland Architecture .Wayland 架构若是想要更好的理解 Wayland 架构及其与 X (X11 or X Window System) 结构;一种很好的方法是将事件从输入设备就开始跟踪, 查看期间所有的屏幕上出现的变化。这就是我们现在对 X 的理解。 内核是从一个输入设备中获取一个事件,并通过 evdev 输入_wayland

推荐文章

热门文章

相关标签