网盘项目总结:vue+vite+FFmpeg库实现分割上传,合并大视频文件(分割的视频可单独播放,下载时后端添加水印)

315 阅读2分钟

tips:

本地开发的时候需要在 ​​vue.config.js​​ 中添加

devServer: {
    headers: {
      "Cross-Origin-Opener-Policy": "same-origin",
      "Cross-Origin-Embedder-Policy": "require-corp",
    },
}

操作的文件名称中不能出现中文,否则会操作失败。。。

部署的时候需要配置 ​​nginx​​ 或者在后端配置。(配置在访问路径的静态资源请求中)

add_header Cross-Origin-Opener-Policy same-origin;
add_header Cross-Origin-Embedder-Policy require-corp;

1.下载引入FFmpeg

下载指定版本(最新版本使用方式不同)

npm install @ffmpeg/ffmpeg@0.10.1

npm install @ffmpeg/core@0.10.0

引入

import { createFFmpeg, fetchFile } from '@ffmpeg/ffmpeg'

2.使用

分割

<script setup>
let mp4Duration = ref(0)

const ffmpeg = createFFmpeg()
if (!ffmpeg.isLoaded()) {
  ffmpeg.load().catch(err => {
    console.log(err)
  })
}

// 获取上传的视频文件时长
const getMp4Time = file => {
  return new Promise(async (resolve, reject) => {
    let url = URL.createObjectURL(file)
    let audioElement = new Audio(url)
    let durtaion = 0
    // 下面需要注意的是在监听loadedmetadata绑定的事件中对duration直接进行赋值是无效的,需要在fun回调函数中进行赋值
    audioElement.addEventListener('loadedmetadata', function () {
      //音频/视频的元数据已加载时,会发生 loadedmetadata 事件
      durtaion = audioElement.duration //时长以秒作为单位
      fun(durtaion)
    })
    let fun = s => {
      durtaion = s
      resolve(durtaion)
    }
  })
}


// 分割上传视频文件
const sliceVideoFile = async file => {
  let startTime = 0 // 截取开始时间 单位s
  let step = 25 // 截取视频长度 单位s
  let filName = file.name // 上传的文件名
  let newName = 'newFile.' + file.name.split('.')[1] // 分割后的文件名 newFile.[视频格式]
  try {
  // fetchFile获取文件并返回一个 Uint8Array 变量供 ffmpeg.wasm 使用
    await ffmpeg.FS('writeFile', filName, await fetchFile(file.raw)) // 先写入上传的视频文件
    // 分割上传的视频文件,如果分割的视频小于了1s,可能没有关键帧,分割出来的视频无法播放,但是不影响合并
    await ffmpeg.run('-ss', `${startTime}`, '-t', `${step}`, '-i', filName, '-vcodec', 'copy', '-acodec', 'copy', newName)
    // 通过newName读取分割后的视频文件 格式为Uint8Array
    let arrayBuffer = ffmpeg.FS('readFile', newName).buffer
    let blob = new Blob([arrayBuffer])
    // let newVideoUrl = URL.createObjectURL(blob)
    // 将blob对象转换为file对象
    let file11 = new File([blob], filName, { type: file.raw.type })
  } catch (err) {
    throw err
  }
}
<script>

合并

// 将文件写入内存
await ffmpeg.FS('writeFile', 'mp4Name1.mp4', await fetchFile(file11))
await ffmpeg.FS('writeFile', 'mp4Name2.mp4', await fetchFile(file12))
// 将文件转为ts格式
await ffmpeg.run('-i', 'mp4Name1.mp4', '-c', 'copy', '-bsf:v', 'h264_mp4toannexb', '-f', 'mpegts', 'newTdsName1.ts')
await ffmpeg.run('-i', 'mp4Name2.mp4', '-c', 'copy', '-bsf:v', 'h264_mp4toannexb', '-f', 'mpegts', 'newTdsName2.ts')
// 清除缓存
await ffmpeg.FS('unlink', 'mp4Name1.mp4');
await ffmpeg.FS('unlink', 'mp4Name2.mp4');
// 合并视频
str = 'concat:newTdsName1.ts|newTdsName2.ts'
await ffmpeg.run('-i', `${str}`, '-c', 'copy', '-bsf:a', 'aac_adtstoasc', '-movflags','+faststart', 'outPut.mp4')
// 获取合并后的文件二进制流
let arrayBuffer = ffmpeg.FS('readFile', 'outPut.mp4').buffer
onUnmounted(() => { ffmpeg.exit(); })

一般运行内存为16g的电脑,合并时视频文件最大不能超过500M,否则浏览器内存将超出导致页面崩掉

参考:blog.51cto.com/u_15344825/… www.zhangxinxu.com/wordpress/2…