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

前言

之前接触过一个获取视频的第一帧当作封面图的需求,当时好像是后端处理的。那纯前端能不能拿到视频的第一帧甚至第 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 ,供页面展示

最后

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

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

相关推荐
百万蹄蹄向前冲1 分钟前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
朝阳58139 分钟前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路1 小时前
GeoTools 读取影像元数据
前端
ssshooter1 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
你的人类朋友1 小时前
【Node.js】什么是Node.js
javascript·后端·node.js
Jerry2 小时前
Jetpack Compose 中的状态
前端
dae bal3 小时前
关于RSA和AES加密
前端·vue.js
柳杉3 小时前
使用three.js搭建3d隧道监测-2
前端·javascript·数据可视化
lynn8570_blog3 小时前
低端设备加载webp ANR
前端·算法
LKAI.3 小时前
传统方式部署(RuoYi-Cloud)微服务
java·linux·前端·后端·微服务·node.js·ruoyi