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

前言

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

最后

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

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

相关推荐
速盾cdn2 分钟前
速盾:vue的cdn是干嘛的?
服务器·前端·网络
四喜花露水35 分钟前
Vue 自定义icon组件封装SVG图标
前端·javascript·vue.js
前端Hardy44 分钟前
HTML&CSS: 实现可爱的冰墩墩
前端·javascript·css·html·css3
web Rookie1 小时前
JS类型检测大全:从零基础到高级应用
开发语言·前端·javascript
Au_ust1 小时前
css:基础
前端·css
帅帅哥的兜兜1 小时前
css基础:底部固定,导航栏浮动在顶部
前端·css·css3
工业甲酰苯胺1 小时前
C# 单例模式的多种实现
javascript·单例模式·c#
yi碗汤园1 小时前
【一文了解】C#基础-集合
开发语言·前端·unity·c#
就是个名称1 小时前
购物车-多元素组合动画css
前端·css
编程一生2 小时前
回调数据丢了?
运维·服务器·前端