FFmpeg——在Vue项目中使用FFmpeg(安装、配置、使用、SharedArrayBuffer、跨域隔离、避坑...)

2023-12-17 19:33:05

个人简介

👀个人主页: 前端杂货铺
🙋?♂?学习方向: 主攻前端方向,正逐渐往全干发展
📃个人状态: 研发工程师,现效力于中国工业软件事业
🚀人生格言: 积跬步至千里,积小流成江海
🥇推荐学习:🍍前端面试宝典 🍉Vue2 🍋Vue3 🍓Vue2/3项目实战 🥝Node.js🍒Three.js🍖数据结构与算法体系教程

🌕个人推广:每篇文章最下方都有加入方式,旨在交流学习&资源分享,快加入进来吧

内容参考链接
Canvas 录制视频Canvas 录制视频
录制视频的解决方案使用 Canvas 录制视频尚存问题的解决方案
在 Vue 项目中使用 FFmpeg

前言

大家好,这里是前端杂货铺

Canvas 录制视频 一文中,我们使用了 MediaRecorder API 在 Canvas 画布中进行了页面的视频录制,并成功输出了 WebM 格式 的视频。

使用 Canvas 录制视频尚存问题的解决方案 一文中,我们通过下载 FFmpeg,进行了视频格式的转换及视频帧率的设置。

但在 Vue 项目中怎么使用 FFmpeg 呢?接下来,我们一起进行详细探索!


在 Vue 项目中使用 FFmpeg

在浏览器中我们是无法直接使用 FFmpeg 软件的,但好在有个东西叫 FFmpeg.wasm,它可以让 FFmpeg 的功能在浏览器中使用!当然,我们可以在 Vue 项目中使用 FFmpeg.wasm 来代替手动输入命令行操作的 FFmpeg 软件!!

FFmpeg.wasm 简介

什么是 FFmpeg.wasm?

FFmpeg.wasm 是 FFmpeg 的纯 WebAssembly 接口,可以在浏览器内录制音频和视频,并进行转换和流式传输。

什么是 WebAssembly?

WebAssembly(简称wasm)是一个虚拟指令集体系架构(virtual ISA),整体架构包括核心的ISA定义、二进制编码、程序语义的定义与执行,以及面向不同的嵌入环境(如Web)的应用编程接口(WebAssembly API)。其目标是为 C/C++等语言编写的程序经过编译,在确保安全和接近原生应用的运行速度更好地在 Web 平台上运行。

所以,FFmpeg.wasm 就是 C语言 编写的程序,通过编译后,它可以在 Web 平台(比如浏览器)上运行。

FFmpeg.wasm(Github源码)

在这里插入图片描述


在 Vue 项目中安装并配置 FFmpeg

第一步:想要在 Vue 项目中使用 FFmpeg 第一步当然是要有个 Vue 项目,Vue 项目的搭建就不做介绍了,不清楚的同学可以自行百度查询(资料很多)。在此,博主使用的是 Vue2 的项目(具体版本为 ^2.6.11)【说明:大家并不需要刻意与我的项目版本保持一致】。

第二步:找到 package.json 文件,在指定区域输入下图中蓝框的内容【说明:@ffmpeg/ffmpeg 和 @ffmpeg/ffmpeg 并非最新版本,请按照自己需要安装所需版本】

在这里插入图片描述

第三步:终端键入 npm icnpm i, 回车

在这里插入图片描述

我们可以在 node_modules 文件夹中查看是否真正安装完成

在这里插入图片描述

第四步:复制 ffmpeg-core.jsffmpeg-core.wasmffmpeg-core.worker.js 文件,放入静态资源文件夹中【说明:Vue项目创建完之后默认生成 public 文件夹(用来存放静态资源的),所以我们需要把它们三个复制一份放入 public 文件夹中(若静态资源文件夹名称改了,就放入改到的地方即可)】

在这里插入图片描述

第五步:在需要使用 ffmpeg 的 vue 组件中引入 ffmpeg

import FFmpeg from "@ffmpeg/ffmpeg";
const { createFFmpeg, fetchFile } = FFmpeg;
const ffmpeg = createFFmpeg({
  corePath: "./ffmpeg-core.js", // 核心文件的路径
  log: true, // 是否在控制台打印日志,true => 打印
});

至此,安装和配置 FFmpeg 就告一段落…


在 Vue 项目中使用 FFmpeg

Canvas 录制视频 一文中,我们编写了通过 MediaRecorder API 在 Canvas 画布中进行了页面的视频录制的代码,把它稍作改变放入 vue 组件中就得到了下面的代码

简单讲解一下:

  1. draw() 方法用于绘制画布上显示的内容
  2. startRecording() 方法用于开始对画布进行视频录制
  3. stopAndblobDownload() 方法用于结束对画布进行的视频录制,并使用 FFmpeg 对视频流进行处理(转换格式、设置帧率等)
<template>
  <div>
    <canvas id="webm-canvas" height="500" width="500" style="margin: auto"></canvas>
    <button @click="startRecording">开始录制</button>
    <button @click="stopAndblobDownload">停止录制</button>
  </div>
</template>

<script>
import FFmpeg from "@ffmpeg/ffmpeg";
const { createFFmpeg, fetchFile } = FFmpeg;
const ffmpeg = createFFmpeg({
  corePath: "./ffmpeg-core.js", // 核心文件的路径
  log: true, // 是否在控制台打印日志,true => 打印
});

export default {
  name: "HelloWorld",
  data() {
    return {
      recorder: null,
      allChunks: []
    };
  },
  mounted() {
    this.draw();
  },
  methods: {
    draw() {
      let canvas = document.getElementById("webm-canvas");
      let context = canvas.getContext("2d");
      // window.requestAnimationFrame(draw);
      var x = 0;
      var speed = 1;
      var text = "前端杂货铺";
      var fontSize = 20;
      setInterval(() => {
        // 清空画布(否则字体移动后颜色会遗留在画布上)
        context.clearRect(0, 0, canvas.width, canvas.height);
        // 设置字体
        context.font = fontSize + "px Arial"; //设置字体大小和类型
        context.fillStyle = "orange"; //设置文本颜色
        context.fillText(text, x, canvas.height / 2); //绘制文本
        // x要移动
        x += speed;
        // 如果文本达到画布边缘,改变方向
        if (x > canvas.width - 100 || x < 0) {
          speed = -speed;
        }
      }, 10);
    },
    startRecording() {
      let canvas = document.getElementById("webm-canvas");
      // 实时视频捕获画布,参数为帧率
      const stream = canvas.captureStream(60); // 60 FPS
      // 创建一个对指定的 stream 进行录制的 MediaRecorder 对象
      this.recorder = new MediaRecorder(stream, {
        mimeType: "video/webm;codecs=vp9", // 设置媒体类型
      });
      // 当数据有效时触发的事件,数据有效时可以把数据存储到缓存区里
      this.recorder.ondataavailable = (e) => {
        console.log("TCL: e", e);
        this.allChunks.push(e.data);
      };
      this.recorder.start(10);
    },
    async stopAndblobDownload() {
      // 异步加载 ffmpeg
      await ffmpeg.load();
      // 结束录像
      this.recorder.stop();
      // createElement() 方法通过指定名称创建一个元素
      const link = document.createElement("a");
      link.style.display = "none";
      // 创建一个 Blob 对象,用于存储二进制数据
      const fullBlob = new Blob(this.allChunks);
      let name = "demo";
      // 写文件,fullBlob 存放录制视频流的 blob 数据
      ffmpeg.FS("writeFile", name, await fetchFile(fullBlob));
      // 执行 ffmpeg ('-r', '10' 表示设置视频帧率为 10fps)('output.avi' 表示生成视频的格式为 .avi)
      await ffmpeg.run('-i', name, '-r', '10', 'output.avi');
      // 读取刚刚执行的文件,存放到 data 中
      const data = ffmpeg.FS('readFile', 'output.avi');
      // 把 data 的 buffer 数据传入 blob 实例中,创建一个包含所需数据的 URL
      const downloadUrl = window.URL.createObjectURL(new Blob([data.buffer], {type:'video/avi'}));
      // 获取或设置链接的 URL 属性
      link.href = downloadUrl;
      // 点击链接时,浏览器下载文件
      link.download = `前端杂货铺${Math.random().toFixed(4)}.avi`;
      // 向节点的子节点列表的末尾添加新的子节点
      document.body.appendChild(link);
      // 模拟用户点击链接的操作
      link.click();
      // 删除 HTML 文档中的链接元素
      link.remove();
    },
  },
};
</script>
<style>
canvas {
  box-shadow: 0 0 10px gray;
  display: block;
}

body {
  text-align: center;
}

button {
  margin-top: 20px;
}
</style>

此时,运行我们的项目,点击开始录制后,再点击结束录制

在这里插入图片描述

SharedArrayBuffer 的前世今生

此时,控制台就会暴露出一个问题:SharedArrayBuffer is not defined

在这里插入图片描述

不要慌,我们先来了解一下 SharedArrayBuffer 是什么?

MDN 上是这样解释的:SharedArrayBuffer 对象用来表示一个通用的原始二进制数据缓冲区,类似于 ArrayBuffer 对象,但它可以用来在共享内存上创建视图。与可转移的 ArrayBuffer 不同,SharedArrayBuffer 不是可转移对象。

官方的说明有点抽象。其实 SharedArrayBuffer 就是一种 JavaScript 对象,它允许多个 Web Worker 线程共享相同的内存空间。这就意味着不同的线程可以同时访问和修改相同的数据,而无需复制数据或担心数据同步的问题。它允许以更有效的方式进行多线程编程,从而提高性能。

那么此时为什么会报 SharedArrayBuffer is not defined 的错误呢?

这就要溯源到 2018 年了,其实在 2018 之前,在支持 SharedArrayBuffer 的各大主流浏览器中都是可以正常使用它的。直到 2018 年暴露出了 幽灵漏洞(Spectre漏洞)*,它导致了浏览器性能的严重倒退,各大主流浏览器厂商便都禁用了 SharedArrayBuffer!!

直到 2021 年才正式放开了对 SharedArrayBuffer 的使用。

SharedArrayBuffer 在各大主流浏览器中的支持情况:

在这里插入图片描述

既然都放开了对 SharedArrayBuffer 的使用,那么为什么还会报错呢?

虽然各大主流浏览器都已经陆续开始支持 SharedArrayBuffer ,但它也不是那么随便就能使用的,而是需要我们进行一些配置,开启了跨域隔离 才能正常使用。

在控制台中输入 crossOriginIsolated 回车,为 false,则说明并没有开启跨域隔离

在这里插入图片描述

解决 SharedArrayBuffer 报错问题

在我们的项目中创建并打开 vue.config.js 文件,如果你的项目使用了 webpack 打包工具,那么就打开你的 webpack.config.js 文件,进行如下配置,配置完成之后记得重启一下项目,让我们刚刚的配置生效

在这里插入图片描述

module.exports = {
    devServer: {
        headers: {
            "Cross-Origin-Opener-Policy": "same-origin", // 保护你的源站点免受攻击
            "Cross-Origin-Embedder-Policy": "require-corp", // 保护受害者免受你的源站点的影响
        },
    },
}

我们看一下是否开启了跨域隔离,为 true,确实已开启

在这里插入图片描述

此时,再点击开始录制后点击结束录制,控制台便不会报错了,并且还会生成我们所需要的视频文件,很完美

在这里插入图片描述


FFmpeg 的一些坑

我们解决了 SharedArrayBuffer 问题后,在 ffmpeg 执行的时候也可能遇到执行失败的问题。

比如说,在 webm 转 mp4 格式时当你的分辨率设置成奇数(比如:499x499)时,就可能导致 ffmpeg 转码失败(错误:width not divisible by 2 (499x499)),所以我们尽量控制把分辨率为偶数。

建议大家在开发过程中把 log: true 打开,方便调试开发!!


总结

本文,我们首先讲解了 FFmpeg 在 Vue 项目中的安装与配置,之后在 Vue 项目中编码简单使用了一下 FFmpeg,然后在运行项目时报了错让我们认识到了 SharedArrayBuffer 并成功找到了解决方案,最后我们了解到了 FFmpeg 在使用时的一些坑,明确了要注意查看 log 日志内容…

FFmpeg 无疑是一款非常强大的多媒体处理工具,在 Vue 项目中利用 “纯前端” 就能实现视频的录制和视频编码转码后的输出,这看起来确实是一件非常 cool 的事!

其实坚持学习,热爱生活得你更 cool

好啦,本篇文章到这里就要和大家说再见啦,祝你这篇文章阅读愉快,你下篇文章的阅读愉快留着我下篇文章再祝!


参考资料:

  1. 百度百科 · FFmpeg、幽灵漏洞
  2. MDN官方文档 · SharedArrayBuffer
  3. 由一个报错引发的浏览器跨域隔离探索【作者:网络安全小肖_知乎】
  4. Canvas录制视频【作者:前端杂货铺_CSDN】
  5. FFmpeg——使用Canvas录制视频尚存问题的解决方案 【作者:前端杂货铺_CSDN】

在这里插入图片描述


文章来源:https://blog.csdn.net/qq_45902692/article/details/135032429
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。