【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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/490698.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

聊一下前端常见的图片格式

1. JPEG (JPG) 概述&#xff1a;是一种有损压缩的图像格式&#xff0c;它通过去除图像中一些人类视觉不易察觉的细节来减小文件大小。它支持数百万种颜色&#xff0c;能够很好地呈现照片等色彩丰富的图像内容。优点&#xff1a; 压缩率高&#xff1a;可以在保持相对较好的图像…

【数据结构——内排序】快速排序(头歌实践教学平台习题)【合集】

目录&#x1f60b; 任务描述 测试说明 我的通关代码: 测试结果&#xff1a; 任务描述 本关任务&#xff1a;实现快速排序算法。 测试说明 平台会对你编写的代码进行测试&#xff1a; 测试输入示例&#xff1a; 10 6 8 7 9 0 1 3 2 4 5 (说明&#xff1a;第一行是元素个数&a…

企业级包管理器之 monorepomultirepo (8)

在企业级项目开发中&#xff0c;面对多个项目的管理&#xff0c;monorepo 和 multirepo 是两种常见的代码管理方案&#xff0c;它们各有特点与优劣&#xff0c;下面我们来详细了解一下。 一、基本概念 monorepo&#xff1a;“mono”在英语中有“单一的、单独的”之意&#xf…

【electron】electron forge + vite + vue + electron-release-server 自动更新客户端

基本信息 electron forge vue页面&#xff08;中文&#xff09;&#xff1a;https://forge.electron.js.cn/guides/framework-integration/vue-3 electron forge vue页面&#xff08;英文&#xff0c;中文版下面的tab无法点击&#xff09;&#xff1a;https://www.electronfor…

后端-带有多个动态查询条件的分页查询

page和pagesize是分页插件所带的参数&#xff0c;其他三个是模糊查询的条件字段 因为是路径动态&#xff1f;拼接 的形式&#xff0c;所以不需要注解requestbody&#xff0c;先封装到pageresult中&#xff0c;再把pageresult封装到result中。 后端给前端的返回值封装到Vo中

【机器学习算法】——决策树之集成学习:Bagging、Adaboost、Xgboost、RandomForest、XGBoost

集成学习 **集成学习(Ensemble learning)**是机器学习中近年来的一大热门领域。其中的集成方法是用多种学习方法的组合来获取比原方法更优的结果。 使用于组合的算法是弱学习算法&#xff0c;即分类正确率仅比随机猜测略高的学习算法&#xff0c;但是组合之后的效果仍可能高于…

Java常用 Date 时间格式化、Calender日历、正则表达式的用法

目录 1. SimpleDateFormat 日期格式化类 1.1 Date 类型转 String 1.2 String 类型转 Date 2. Calendar 日历类 3. 正则表达式 3.1 正则表达式的组成部分 3.2 手机号正则表达式 3.3 常用密码校验正则表达式 1. SimpleDateFormat 日期格式化类 SimpleDateFormat 是Java中…

jdk1.8安装及环境配置(最新最详细教学!!!)

jdk1.8安装&#xff1a; 看了网上很多关于jdk1.8的安装&#xff0c;我觉得有时候会让人云里雾里&#xff0c;虽然自己可能配置成功&#xff0c;不过没有一套自己的思路&#xff0c;我结合自己的经验来说一下。 jdk在windows有两种安装方式&#xff0c;一种是解压缩包&#xf…

51c嵌入式~单片机~合集2

我自己的原文哦~ https://blog.51cto.com/whaosoft/12362395 一、不同的电平信号的MCU怎么通信&#xff1f; 下面这个“电平转换”电路&#xff0c;理解后令人心情愉快。电路设计其实也可以很有趣。 先说一说这个电路的用途&#xff1a;当两个MCU在不同的工作电压下工作&a…

httpsok-v1.18.0-SSL证书自动续期

&#x1f525;httpsok-v1.18.0-SSL证书自动续期 介绍 httpsok 是一个便捷的 HTTPS 证书自动续期工具&#xff0c;基于全新的设计理念&#xff0c;专为 Nginx 、OpenResty、Apache 等服务器设计。已服务众多中小企业&#xff0c;稳定、安全、可靠。 一行命令&#xff0c;一分…

Dynamics 365 CRM- 后端

Dynamics 365 CRM 后端插件语法示例 public IPluginExecutionContext context null;//上下文 public IOrganizationServiceFactory serviceFactory null;//组织服务工厂对象 public IOrganizationService service null;//Org服务对象//创建执行上下文 context (IPluginExe…

Linux驱动开发(12):中断子系统–按键中断实验

本章我们以按键为例讲解在驱动程序中如何使用中断&#xff0c; 在学习本章之前建议先回顾一下关于中断相关的裸机部分相关章节&#xff0c; 这里主要介绍在驱动中如何使用中断&#xff0c;对于中断的概念及GIC中断控制器相关内容不再进行讲解。 本章配套源码和设备树插件位于“…

Grafana配置告警规则推送企微机器人服务器资源告警

前提 已经部署Grafana&#xff0c;并且dashboard接入数据 大屏编号地址&#xff1a;Node Exporter Full | Grafana Labs 创建企微机器人 备注&#xff1a;群里若有第三方外部人员不能创建 机器人创建完成&#xff0c;记录下来Webhook地址 Grafana配置告警消息模板 {{ define &…

[WiFi] WiFi安全加密WEP Vs WPA Vs WPA2 Vs WPA3整理

WiFi安全标准时间线 WEP&#xff08;Wired Equivalent Privacy&#xff09; WEP最早于1997年推出&#xff0c;是为了保护无线网络上的数据通信而设计的。当时&#xff0c;Wi-Fi技术还处于起步阶段&#xff0c;人们开始意识到需要一种安全协议来防止未经授权的访问和窃听。WEP被…

使用Keil V6编译 FreeRTOS CMSIS V2版本 ETH + Lwip 编译报错问题解决方式

网上其他人写的都解决不了&#xff0c;要不用的是CMSIS V1版本&#xff0c;根据他们的方式搞完还是报错&#xff0c;今天花点时间自己搞一下。 不想自己动手&#xff1f;没问题&#xff0c;模版已上传Gitee https://gitee.com/maybe_404/stm32-f4xx_-free-rtos_-lwip_-templa…

群控系统服务端开发模式-应用开发-操作记录功能开发

一、开放路由 在根目录下route文件夹下修改app.php文件&#xff0c;代码如下&#xff1a; // 操作日志Route::get(token/get_list,permission.Token/getList);// 获取操作日志列表Route::post(token/get_all,permission.Token/getAll);// 获取操作日志所有数据Route::post(toke…

大模型闯关学习——L1G6000 OpenCompass 评测书生大模型实践

任务要求&#xff1a; 基础任务&#xff1a; 首先创建开发机 进入开发机&#xff1a; 创建测评所需要的环境 conda create -n opencompass python3.10 conda activate opencompass cd /root git clone -b 0.3.3 https://github.com/open-compass/opencompass cd opencompas…

C# 探险之旅:第三十节 - 类型class(继承Inheritance) —— 当“儿子”继承“老爸”的遗产

嘿&#xff0c;探险家们&#xff01;欢迎再次踏上我们的C#奇幻旅程。今天&#xff0c;我们要聊一个既有趣又实用的话题——继承&#xff08;Inheritance&#xff09;&#xff01;想象一下&#xff0c;如果你的“儿子”能够继承“老爸”的遗产&#xff0c;那编程世界里的对象们也…

排序算法(2)——快速排序

目录 1. 实现方式 1.1 霍尔法 ​ 1.2 挖坑法 1.3 前后指针法 2. 复杂度分析 3. 快速排序优化 3.1 三数取中 3.2 小区间使用插入排序 3.3 非递归实现 快速排序是英国计算机科学家托尼・霍尔&#xff08;C. A. R. Hoare&#xff09;在 1960 年年提出的一种二叉树结构的交…

【python】最新版抖音js逆向拿到数据,非常详细教程(附完整代码)

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全…