【Vue3】前端使用 FFmpeg.wasm 完成用户视频录制,并对视频进行压缩处理
强烈推荐这篇博客!非常全面的一篇文章,本文是对该博客的简要概括和补充,在不同技术栈中提供一种可行思路,可先阅读该篇文章再阅读本篇:
FFmpeg——在Vue项目中使用FFmpeg(安装、配置、使用、SharedArrayBuffer、跨域隔离、避坑…)_vue ffmpeg-CSDN博客文章浏览阅读1.1w次,点赞71次,收藏96次。本文介绍了FFmpeg在Vue项目中从0到1的使用,从安装 => 配置 => 简单使用 => 认识SharedArrayBuffer和跨域隔离 => 避坑…_vue ffmpeg[这里是图片001]https://zahuopu.blog.csdn.net/article/details/135032429?fromshare=blogdetail&sharetype=blogdetail&sharerId=135032429&sharerefer=PC&sharesource=Andye11&sharefrom=from_link
上述博客技术栈: Vue2 + Webpack + JS
本文技术栈:Vue3 + Vite + TS
本文运行环境: windows11 + Edge浏览器 +22.11 node版本
1、安装配置(等同上述博客):
npm i @ffmpeg/ffmpeg@0.9.8
npm i @ffmpeg/core@0.10.0
2、复制下载的核心js文件到 /public 静态文件夹中(基本等同上述博客)

3、配置Vite配置项(和webpack在代码位置上有细微区别 内容完全一致)
server: {......headers: { // 目的是为了消除浏览器报错: SharedArrayBufferis not defined'Cross-Origin-Opener-Policy': 'same-origin','Cross-Origin-Embedder-Policy': 'require-corp'},......},
特别注意!
1、配置该配置项之后, 本地运行时 请使用localhost 服务进行开发,因为 ffmpeg仅支持 本地 和 https 环境运行,不支持 http 环境;
2、如果需要打包到线上环境运行,还需要 后端同学对nginx 进行同样头部配置,仅有前端自己配置只能在自己本地服务中运行。
4、完成配置,代码相关内容如下:
预览效果图:

可以看到,压缩率还是很高的,时间性能上大概需要原视频长度的 50% (也就是 10分钟的视频大概需要接近5分钟去压缩(我的设备信息:笔记本电脑CPU - 英特尔 i5-12500H)
在此过程中 页面内存消耗巨大,基本无法与用户进行其他互动),同时牺牲了一定清晰度,但最终可以缩小90%左右的大小。
也就是说:如果你的目标是 使用时间和性能来换取极致的大小压缩,这个库是可用的。
代码原文:
<template><div class="test-page" v-loading="isLoading_fullScreen"><div class="flx-center" style="flex-direction: column"><videoref="videoElement"webkit-playsinlineplaysinlinemutedautoplaystyle="object-fit: cover"width="200px"height="150px"></video><div style="margin-top: 10px; column-gap: 30px" class="flx-center"><h1 style="display: inline-block">{{ page }}</h1><el-button type="primary" @click="nextPage">翻页</el-button></div></div><div class="card"><div style="margin-bottom: 10px; column-gap: 30px" class="flx-center"><el-button @click="getMedia" v-if="!isRecording" style="margin-right: 10px">开始录制</el-button><el-text type="primary">{{ timer_min }}</el-text><el-text v-if="isRecording">{{ old_timer_min }}</el-text></div><el-table :data="tableData" border style="width: 100%; min-height: 250px" max-height="260"><el-table-column prop="type" label="类型"><template #default="scope">{{ scope.row.type == 1 ? '原文件' : '压缩后' }}</template></el-table-column><el-table-column prop="size" label="大小" /><el-table-column prop="spendTime" label="用时" /><el-table-column prop="page" label="页码" /></el-table></div></div>
</template><script setup lang="ts" name="test">
import { ref, nextTick, computed } from 'vue'
import FFmpeg from '@ffmpeg/ffmpeg/dist/ffmpeg.min.js'const page = ref(1)// ============================================= 时间相关 =============================================
const timer_sec = ref(0)
const timer_min = computed(() => {let mm = Math.floor(timer_sec.value / 60)let ss = Math.floor(timer_sec.value % 60)return (mm < 10 ? '0' + mm : mm) + ':' + (ss < 10 ? '0' + ss : ss)
})
const old_timer_min = ref('00:00')// ============================================= 表格相关 =============================================
type TableDataItem = {type: number // 1代表原文件 2代表压缩文件size: string | numberpage: numbertime: numberspendTime: string
}
const tableData = ref<TableDataItem[]>([])const addTableData = (type: number, blobSize: number) => {if (type == 1) {let spendTime = ''let lastItem = tableData.value.findLast(item => item.type == 1)if (lastItem) {spendTime = secondToMinute((Date.now() - lastItem.time) / 1000)} else {spendTime = secondToMinute(timer_sec.value)}tableData.value.push({type: 1,size: (blobSize / 1024 / 1024).toFixed(2),time: Date.now(),spendTime: spendTime,page: page.value})} else {let spendTime2 = secondToMinute((Date.now() - tableData.value[tableData.value.length - 1].time) / 1000)tableData.value.push({type: 2,page: page.value,time: Date.now(),spendTime: spendTime2,size: (blobSize / 1024 / 1024).toFixed(2)})}
}// ===================================== 视频录制相关 =====================================
const videoElement = ref<HTMLVideoElement | null>(null) // 用于播放的 VideoElement 对象
let mediaRecorder: MediaRecorder | null = null // 用于录制的 MediaRecorder 对象
const isRecording = ref(false) // 是否正在录制
const recordBlob = ref<Blob[]>([]) // 记录视频Blob数据
const isLoading_fullScreen = ref(false)
const timeSlice = 10 * 1000// 获取摄像头和麦克风访问权限
async function getMedia() {isLoading_fullScreen.value = trueawait ffmpeg.load()// 配置视频录制分辨率为480p 帧率为24,但是在实际测试中发现各大浏览器基本都不支持此配置,设置与否不影响视频大小let mediaStreamConstraints: MediaStreamConstraints = {video: {width: { ideal: 640 },height: { ideal: 480 },frameRate: { ideal: 24, max: 24 }},audio: true}navigator.mediaDevices.getUserMedia(mediaStreamConstraints).then(stream => {isRecording.value = true// 1、记录视频数据mediaRecorder = new MediaRecorder(stream)// 2、开始录制mediaRecorder.start(timeSlice)// 处理录制数据mediaRecorder.ondataavailable = (e: BlobEvent) => {if (e.data.size > 0) {recordBlob.value.push(e.data)} else {console.error('最近10秒内数据异常')}}mediaRecorder.onstop = async () => {if (recordBlob.value.length > 0) {let fullBlob = new Blob(recordBlob.value, { type: 'video/mp4' })// 处理原文件数据到表格中addTableData(1, fullBlob.size)let compressedBlob = await compressVideoBlob(fullBlob)// 处理压缩文件数据到表格中addTableData(2, compressedBlob.size)page.value += 1 // 页数+1recordBlob.value = [] // 清空数组mediaRecorder!.start(timeSlice) // 重新开始录制} else {console.error('录制异常,请重新录制本页,可尝试缩短录制时长')mediaRecorder!.start(timeSlice)}}mediaRecorder.onerror = (e: Event) => {console.error('触发了 error 事件' + mediaRecorder?.state)}// 3、实时显示摄像画面nextTick(() => {videoElement.value!.srcObject = streamvideoElement.value!.play()setInterval(() => {timer_sec.value += 1}, 1000)})isLoading_fullScreen.value = false}).catch(err => {console.error('错误:', err)alert('无法访问摄像头或麦克风')})
}const nextPage = async () => {if (mediaRecorder) {mediaRecorder.stop()old_timer_min.value = timer_min.value}
}const { createFFmpeg, fetchFile } = FFmpeg
const ffmpeg = createFFmpeg({corePath: '/FFMPEG/ffmpeg-core.js', // 核心文件的路径log: true // 是否在控制台打印日志,true => 打印
})// 压缩视频Blob数据
const compressVideoBlob = async (blob: Blob) => {const data = await blob.arrayBuffer()const inputName = 'input.mp4'ffmpeg.FS('writeFile', inputName, new Uint8Array(data))await ffmpeg.run('-i', inputName, '-vcodec', 'libx264', '-crf', '28', '-acodec', 'copy', 'output.mp4')const outputData = ffmpeg.FS('readFile', 'output.mp4')const compressedBlob = new Blob([outputData.buffer], { type: 'video/mp4' })return compressedBlob
}// 处理时间 把秒数处理为分钟
const secondToMinute = (second: number, pad: string = '') => {second = Math.floor(second)return Math.floor(second / 60) + '分' + pad + (second % 60) + '秒'
}
</script><style lang="scss" scoped>
.test-page {padding: 20px;
}
.flx-center {display: flex;align-items: center;justify-content: center;
}
.card {box-sizing: border-box;padding: 20px;overflow-x: hidden;background-color: #ffffff;border: 1px solid #e4e7ed;border-radius: 6px;box-shadow: 0 0 12px rgb(0 0 0 / 5%);
}</style>
注意:请自行删除 HTML中element-plus UI库的相关内容,使用它们仅为了样式美观,不影响实际代码运行
额外注意事项:
1、import 引入 FFmpeg时 需要指定完整路径:即使用
import FFmpeg from '@ffmpeg/ffmpeg/dist/ffmpeg.min.js'
// 虽然这样会损失代码提示,但起码能在Vite环境跑起来 这点和博主原文不同
2、在 createFFmpeg 函数中, 需要额外注意路径一定要使用绝对路径
// 我把那3个文件复制到 /public/FFMPEG下面,但是这里不要带 /public ,主要是为了防止线上环境路径失效, 具体为什么可查询我的【Vite项目中静态资源路径处理】一文
const { createFFmpeg, fetchFile } = FFmpegconst ffmpeg = createFFmpeg({corePath: '/FFMPEG/ffmpeg-core.js',
})
3、最后,我在0.9.8版本的 ffmpeg.wasm 中发现:
微信内置浏览器乃至移动设备大部分浏览器都不兼容 SharedArrayBuffer,我在win11的 edge和chrome浏览器中可以非常非常顺利地运行代码,哪怕是打包到线上环境也是可以正常运行,
但手机端无论是 微信浏览器还是 夸克浏览器、safari浏览器 均会报 SharedArrayBuffer is not defined 错误,IOS手机还会报出 Range Error 错误。
听说最新0.12版本已经可以修复这个问题了,如果有机会,我也会补充到这里来。
Q:0.12版本ffmpeg.wasm可以在移动设备上正常使用了吗?
A:十分抱歉,笔者在使用0.12版本的库时遇到了 ffmpeg.load 不执行也不报错的未知情况,至今仍未能成功运行。
但笔者发现canvas也可以间接完成视频压缩的功能,大概可以把原视频压缩到原本的25%~35%大小,链接放在这里:
【Vue3】使用canvas来实现H5页面摄像头录制的【视频压缩】功能-CSDN博客文章浏览阅读2次。用户边说话边进行摄像头录制,同时要把用户说的话转为字幕显示在评测结果中,但iPhone手机10秒钟基本就能录制一个大约12MB大小的视频,如果考试时间达到30分钟,那么消耗的流量将十分恐怖,而且极其容易出现网络问题,导致视频录制失败。,效果虽然不算非常好,但是没有引入任何第三方库,代码量非常少,所以也算有它的价值所在。之前尝试使用 FFmege.wasm失败,一是性能比较差,需要大概原视频50%的时间来进行压缩,二是移动设备兼容性比较差,没办法成功跑起来。代码效果图:(大小为 PC端未被压缩视频大小)[这里是图片005]https://blog.csdn.net/Andye11/article/details/144084235?fromshare=blogdetail&sharetype=blogdetail&sharerId=144084235&sharerefer=PC&sharesource=Andye11&sharefrom=from_link
相关文章:
【Vue3】前端使用 FFmpeg.wasm 完成用户视频录制,并对视频进行压缩处理
强烈推荐这篇博客!非常全面的一篇文章,本文是对该博客的简要概括和补充,在不同技术栈中提供一种可行思路,可先阅读该篇文章再阅读本篇: FFmpeg——在Vue项目中使用FFmpeg(安装、配置、使用、SharedArrayBu…...
基础算法——前缀和
由于比赛基本都是采用Dev-C所以,算法篇基本都是采用Dev-C来解释(版本5.11,c11) 首先介绍一下前缀和算法 给定一个数组,有q次询问,每次询问: 两个整数l,r,求出数组 l 到 r的结果 遇…...
spring实例化对象的几种方式(使用XML配置文件)
前言 Spring框架作为一个轻量级的控制反转(IoC)容器,为开发者提供了多种对象实例化的策略。通过这些策略,开发者可以更加灵活地控制对象的生命周期和依赖关系。无论是通过XML配置、注解配置还是Java配置,Spring都能…...
【二叉树】力扣 129.求根节点到叶子节点数字之和
一、题目 二、思路 每找到一个非空节点,之前路径上的所有节点的数量级都要增加1个单位。例如,当前节点为3,之前的节点路径为1 -> 2,presum 1 * 10 2 12,现在路径变为了 1 -> 2 -> 3,sum pres…...
深度学习物体检测之YOLOV5源码解读
V5比前面版本偏工程化,项目化,更贴合实战 一.V5版本项目配置 (1)整体项目概述 首先github直接查找yolov5,下载下来即可。在训练时,数据是怎么处理的?网络模型架构是怎么设计的(如各层的设计)?yolov5要求是大于python3.8与大于等…...
音频数据采样入门详解 - 给Python初学者的简单解释
音频数据采样入门详解 - 给Python初学者的简单解释 声音是如何变成数字的?什么是采样率?为什么要懂这个?Python小例子总结 大家好!今天我们来聊一个有趣的话题:音频数据是如何在计算机中处理的。让我用最简单的方式来解…...
Unity类银河战士恶魔城学习总结(P179 Enemy Archer 弓箭手)
教程源地址:https://www.udemy.com/course/2d-rpg-alexdev/ 本章节实现了敌人弓箭手的制作 Enemy_Archer.cs 核心功能 状态机管理敌人的行为 定义了多个状态对象(如 idleState、moveState、attackState 等),通过状态机管理敌人的…...
SpringCloud集成sleuth和zipkin实现微服务链路追踪
文章目录 前言技术积累spring cloud sleuth介绍zipkin介绍Zipkin与Sleuth的协作 SpringCloud多模块搭建Zipkin Server部署docker pull 镜像启动zipkin server SpringCloud 接入 Sleuth 与 Zipkinpom引入依赖 (springboot2.6)appilication.yml配置修改增加测试链路代码 调用微服…...
Python随机抽取Excel数据并在处理后整合为一个文件
本文介绍基于Python语言,针对一个文件夹下大量的Excel表格文件,基于其中每一个文件,随机从其中选取一部分数据,并将全部文件中随机获取的数据合并为一个新的Excel表格文件的方法。 首先,我们来明确一下本文的具体需求。…...
Linux+Docker onlyoffice 启用 HTTPS 端口支持
文章目录 一、需求二、配置2.1 创建容器2.2 进入容器2.3 生成私钥和证书 2.4 测试访问 一、需求 上篇文章介绍了如何搭建一个 onlyoffice 在线预览服务,但是我们实际场景调用该服务的网站是协议是 https 的 ,但是 onlyoffice 服务还没做配置,…...
在 Visual Studio Code 中编译、调试和执行 Makefile 工程 llama2.c
在 Visual Studio Code 中编译、调试和执行 Makefile 工程 llama2.c 1. Installing the extension (在 Visual Studio Code 中安装插件)1.1. Extensions for Visual Studio Code1.2. C/C1.2.1. Pre-requisites 1.3. Makefile Tools 2. Configuring your project (配置项目)2.1.…...
python中math模块常用函数
文章目录 math模块简介各种三角函数反三角函数取整函数欧几里得距离绝对值最大公约数开根号幂阶乘函数 math模块简介 math模块是python标准库的一部分,提供了对于浮点数相关的数学运算,下面是常用的一些function 各种三角函数反三角函数 math.cos、ma…...
优化 Vue 3 开发体验:配置 Vite 使用 WebStorm 作为 Vue DevTools 的默认编辑器
优化 Vue 3 开发体验:配置 Vite 使用 WebStorm 替代 VS Code 作为 Vue DevTools 的默认编辑器 在 Vue 3 项目开发中,合理配置开发工具可以大大提升我们的工作效率。本文将介绍如何配置 Vite,使其在使用 Vue DevTools 时将默认编辑器从 VS Co…...
【C语言练习(9)—有一个正整数,求是几位数然后逆序打印】
C语言练习(9) 文章目录 C语言练习(9)前言题目题目解析结果总结 前言 主要到整数的取余(%)和整数的取商(/),判断语句if…else if …else的使用 题目 给一个不多于3位的正整数,要求:一、求它是几位数&…...
热敏打印机的控制
首次接触热敏打印机,本来没有特别之处,花了大概十天时间完成一款猫学王热敏打印机,给到客户体验后,客户反馈说打字看起来不明显,打印照片有条纹,所以引起了我对于他的关注,几点不足之处需要优化…...
【closerAI ComfyUI】电商赋能,AI模特套图生产,各种姿势自定义,高度保持人物服饰场景一致性,摆拍街拍专用
closerAIGCcloserAI,一个深入探索前沿人工智能与AIGC领域的资讯平台,我们旨在让AIGC渗入我们的工作与生活中,让我们一起探索AIGC的无限可能性!aigc.douyoubuy.cn 【closerAI ComfyUI】电商赋能,AI模特套图生产,各种姿势自定义,高度保持人物服饰场景一致性,摆拍街拍专用…...
ARM学习(36)静态扫描规则学习以及工具使用
笔者来学习了解一下静态扫描以及其规则,并且亲身是实践一下对arm 架构的代码进行扫描。 1、静态扫描认识 静态扫描:对代码源文件按照一定的规则进行扫描,来发现一些潜在的问题或者风险,因为不涉及代码运行,所以其一般只是发现一些规范或则一些质量问题,当然这些可能存在潜…...
使用 Docker Compose 部署 Redis 主从与 Sentinel 高可用集群
文章目录 使用 Docker Compose 部署 Redis 主从与 Sentinel 高可用集群Redis 主从架构简介Redis Sentinel 简介配置文件1. 主节点配置 (redis-master.conf)2. 从节点配置 (redis-slave1.conf 和 redis-slave2.conf)redis-slave1.confredis-slave2.conf3. Sentinel 配置 (sentin…...
警惕!手动调整服务器时间可能引发的系统灾难
警惕!手动调整服务器时间可能引发的系统灾难 1. 鉴权机制1.1 基于时间戳的签名验证1.2 基于会话的认证机制(JWT、TOTP) 2. 雪花算法生成 ID 的影响2.1 时间戳回拨导致 ID 冲突2.2 ID 顺序被打乱 3. 日志记录与审计3.1 日志顺序错误3.2 审计日…...
MySQL追梦旅途之性能优化
1、索引优化 索引可以显著加速查询操作,但过多或不适当的索引也会带来负面影响(如增加写入开销)。因此,选择合适的索引至关重要。 创建索引: 为经常用于WHERE子句、JOIN条件和ORDER BY排序的列创建索引。 CREATE I…...
wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...
简易版抽奖活动的设计技术方案
1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...
ElasticSearch搜索引擎之倒排索引及其底层算法
文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...
让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...
大数据学习(132)-HIve数据分析
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言Ǵ…...
Docker 本地安装 mysql 数据库
Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker ;并安装。 基础操作不再赘述。 打开 macOS 终端,开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...
【笔记】WSL 中 Rust 安装与测试完整记录
#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统:Ubuntu 24.04 LTS (WSL2)架构:x86_64 (GNU/Linux)Rust 版本:rustc 1.87.0 (2025-05-09)Cargo 版本:cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...
