🌟纯前端获取视频任意一帧的方法🌟

前言

之前接触过一个获取视频的第一帧当作封面图的需求,当时好像是后端处理的。那纯前端能不能拿到视频的第一帧甚至第 N 帧呢?

今天我们就来看看这个问题,不依赖服务端看看是否可以实现

获取第一帧

获取第一帧这个需求可能更常见一些,一般就是用来作为封面图展示。前端获取第一帧主要用到 videoloadeddata 事件以及 canvas 相关的 API

loadeddata 事件会在视频的第一帧加载完毕后触发,表示视频至少有一帧数据可用。我们可以监听这个时间的触发,然后把 video 元素绘制到 canvas上 ,就可以拿到第一帧的图像信息。

封装一个 getFirstFrame 函数如下:

js 复制代码
const getFirstFrame = (url) => {
  return new Promise((resolve) => {
    const video = document.createElement("video");
    const canvas = document.createElement("canvas");
    video.src = url;
    video.addEventListener("seeked", async () => {
      const ctx = canvas.getContext("2d");
      canvas.width = video.videoWidth;
      canvas.height = video.videoHeight;
      ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
      const frameDataURL = canvas.toDataURL("image/png");
      resolve(frameDataURL);
    });

    video.addEventListener("loadeddata", () => {
      video.currentTime = 0;
    });
  });
};

直接在 loadeddata 中获取第一帧可能会导致空白,这里我们采用 loadeddata+seeked 的方式来获取。尝试使用 seeked 事件来确保视频已经加载并且能够渲染,然后再获取第一帧的图像数据。

在组件中使用如下:

js 复制代码
const Fps = () => {
  const [url, setUrl] = useState("");
  useEffect(() => {
    const run = async () => {
      const src = await getFirstFrame("/flower.webm");
      setUrl(src);
    };
    run();
  }, []);
  return (
    <div className="container">
      <img src={url} />
    </div>
  );
};

export default Fps;

这样就能获取到视频的第一帧,并渲染出来。

再拓展一下,我们可以拿这个生成好的 URL 转换成一个 file 对象,发送给后端,让后端存一下这第一帧的图片,以便后续当作视频封面来使用。

实现一个 dataURLtoFile 函数,这样就可以获取到一个 file 对象:

js 复制代码
const dataURLtoFile = (dataurl, filename) => {
  const arr = dataurl.split(",");
  const mime = arr[0].match(/:(.*?);/)[1];
  const bstr = atob(arr[1]);
  let n = bstr.length;
  const u8arr = new Uint8Array(n);
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
  }
  return new File([u8arr], filename, { type: mime });
};
js 复制代码
  const file = dataURLtoFile(frameDataURL, "first_frame.png");

ffmpeg

ffmpeg 是一个强大的音视频处理工具集合库,一般跑在服务器上。但是它也有 wasm 版本,利用它的 wasm 版本我们就可以在前端做一些音视频处理的功能,包括我们今天要介绍的获取视频的任意一帧。

首先安装一下依赖:npm i @ffmpeg/ffmpeg @ffmpeg/util,然后通过以下方式加载 ffmpeg

js 复制代码
  const ffmpegRef = useRef(new FFmpeg());
  useEffect(() => {
    const init = async () => {
      const baseURL = "";
      const ffmpeg = ffmpegRef.current;
      ffmpeg.on("log", ({ message }) => {
        console.log(message);
      });
      await ffmpeg.load({
        coreURL: await toBlobURL(
          `${baseURL}/ffmpeg-core.js`,
          "text/javascript"
        ),
        wasmURL: await toBlobURL(
          `${baseURL}/ffmpeg-core.wasm`,
          "application/wasm"
        ),
      });
    };
    init();
  }, []);

我是提前把 ffmpeg 依赖的一些文件下载到了本地,最简版本的加载方式可以参见它的文档------ffmpeg.wasm

然后可以使用 ffmpeg 来获取某一帧,我们以获取第 10 帧为例:

js 复制代码
  const getFrame = async () => {
    const ffmpeg = ffmpegRef.current;
    await ffmpeg.writeFile("input.webm", await fetchFile("/flower.webm"));
    await ffmpeg.exec([
      "-i",
      "input.webm",
      "-vf",
      "select='eq(n,9)'",
      "-vframes",
      "1",
      "output.png",
    ]);
    const data = await ffmpeg.readFile("output.png");
    const blob = new Blob([data.buffer], { type: "image/png" });
    const url = URL.createObjectURL(blob);
    setUrl(url);
  };

解释一下上面的代码:

  • -i input.webm:定义输入的视频文件名是 input.webm
  • -vf "select=eq(n,9)":视频过滤器 (Video Filter) 的参数,它的功能是从视频中选择出满足某种条件的帧。在这里,select=eq(n,9) 表示选择出第 10 帧(注意,帧的起始计数从 0 开始,所以第 10 帧的序号是 9 )。
  • -vframes 1:指定输出的帧数。在这里,我们只需要输出一帧,所以这个值是 1
  • output.png:输出文件的名字。
  • writeFile,往 wasm 虚拟文件系统中写入文件,同理, readFile 读取文件
  • 最后构造一个 url ,供页面展示

最后

以上就是本文的全部内容,介绍了"常见的"获取视频第一帧的方法,以及介绍了一个"不常见的"获取视频任意一帧的解决方案。

如果你觉得有意思的话,点点关注点点赞吧~

相关推荐
xiaoqi92218 分钟前
React Native鸿蒙跨平台如何实现分类页面组件通过searchQuery状态变量管理搜索输入,实现了分类的实时过滤功能
javascript·react native·react.js·ecmascript·harmonyos
打小就很皮...31 分钟前
Tesseract.js OCR 中文识别
前端·react.js·ocr
qq_1777673741 分钟前
React Native鸿蒙跨平台实现应用介绍页,实现了应用信息卡片展示、特色功能网格布局、权限/联系信息陈列、评分展示、模态框详情交互等通用场景
javascript·react native·react.js·ecmascript·交互·harmonyos
2603_949462101 小时前
Flutter for OpenHarmony社团管理App实战:预算管理实现
android·javascript·flutter
wuhen_n1 小时前
JavaScript内存管理与执行上下文
前端·javascript
Hi_kenyon1 小时前
理解vue中的ref
前端·javascript·vue.js
jin1233222 小时前
基于React Native鸿蒙跨平台地址管理是许多电商、外卖、物流等应用的重要功能模块,实现了地址的添加、编辑、删除和设置默认等功能
javascript·react native·react.js·ecmascript·harmonyos
2501_920931702 小时前
React Native鸿蒙跨平台医疗健康类的血压记录,包括收缩压、舒张压、心率、日期、时间、备注和状态
javascript·react native·react.js·ecmascript·harmonyos
落霞的思绪3 小时前
配置React和React-dom为CDN引入
前端·react.js·前端框架
Hacker_Z&Q3 小时前
CSS 笔记2 (属性)
前端·css·笔记