vue+ffmpeg实现前端视频转码

简介: 如何使用ffmpeg的wasm文件在浏览器实现视频转码

因为做过的项目中,有涉及到视频上传的模块,常规视频的mp4格式可以直接进行预览和展示,但是部分视频文件无法直接预览,需要进行转码成mp4进行处理,我在GitHub上寻找到了ffmpeg的wasm版本,并成功在项目中实现转码。
这里给出一个将asf格式的视频转换mp4的示例
转码.png

这里给出解决思路
首先,我们安装 @ffmpeg/core,@ffmpeg/ffmpeg 两个库,网址如下https://ffmpegwasm.netlify.app/#demo,这里写的简单示例是用element-plus的upload组件+video标签实现
代码如下

  
<script setup lang="ts">
import { UploadFilled } from '@element-plus/icons-vue'
import type { UploadProps } from 'element-plus';
import { fetchFile } from '@ffmpeg/ffmpeg';
import { ComponentInternalInstance, getCurrentInstance, ref } from 'vue';
import { createFFmpeg } from '@ffmpeg/ffmpeg';
const {
  appContext: {
    config: { globalProperties }
  }
} = getCurrentInstance() as ComponentInternalInstance;
const videoPlayURL = ref('')
const videoPreview = ref({} as HTMLVideoElement);

const uploadFileChange: UploadProps['onChange'] = async (uploadFile: any) => {
 
  let ffmpegInstance: any
  if (!globalProperties.$ffmpeg) {
    globalProperties.$ffmpeg = createFFmpeg({
      log: false
    });
  }
  let file: File = uploadFile.raw as File;
  ffmpegInstance = globalProperties.$ffmpeg;
  videoTranscodeFunc(ffmpegInstance, file, 'test').then(() => {
    // 转码进度
    let curProgress: number = 0; // 进度取值不稳定,作为对比
    let startTranscode = false;
    let progressCounter = 0;
    ffmpegInstance.setProgress(({ ratio }: any) => {
      console.log('ratio', ratio);
      // 开始转码时跳转到进度页,只执行一次
      if (!startTranscode) {
        startTranscode = true;

      }
      // ratio is a float number between 0 to 1.
      // ratio可能小于0或者大于1,需要处理
      if (ratio > 0) {
        let progress = parseInt(ratio * 100 + ''); //转为两位数
        if (progress < 100) {
          if (progressCounter === 0 && progress > 90) return; // 规避转码异常
          curProgress = Math.max(curProgress, progress); //取相对更大的值,避免进度倒着走
          curProgress = curProgress < 1 ? 1 : curProgress; // 大文件转码初始进度0.00X,出现长时间为0的情况优化
          progressCounter++;
        }
      }
    });
    // 通过文件格式校验的,在解析时也加一层拦截,检查异常文件,报错中断
    ffmpegInstance.setLogger(({ type, message }: any) => {
      let name = 'test';
      // 异常情况判断
      let invalidMessage = ['Conversion failed!', 'pthread sent an error!'];
      let invalid = invalidMessage.some((item) => message.includes(item));
      if (type === 'fferr' && invalid) {
        console.log("转码异常2")
        return;
      }

      // 转码结束处理
      if (type === 'ffout') {
        try {
          const data = ffmpegInstance.FS('readFile', `${name}.mp4`);
          console.log('转码结束');
          console.timeEnd('videoTranscode');
          let videoUrl = '';
          videoUrl = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
          videoPlayURL.value = videoUrl
          var result = new File([data.buffer], `${name}.mp4`, {
            type: 'video/mp4',
            lastModified: Date.now()
          });
          uploadFile.size = result.size;
          uploadFile.raw = result;
          uploadFile.status = 'ready';

          // 防止ffmpeg返回值异常,只在结束时才赋值100
          ffmpegInstance.FS('unlink', `${name}.mp4`); // 释放内存中的文件
        } catch (err) {
          console.log('转码失败', err);
          return;
        }
      }
    });
  });
};


const videoTranscodeFunc = async (ffmpeg: any, file: any, name: string) => {

  try {
    if (!ffmpeg.isLoaded()) await ffmpeg.load(); // load过的不需要重复load,否则会转码失败
    // console.log('1', name, `${name}.mp4`);
    ffmpeg.FS('writeFile', name, await fetchFile(file));
    // console.log('2', name, `${name}.mp4`);
    ffmpeg.run('-i', name, `${name}.mp4`);
  } catch (err) {
    console.log('ffmpeg run error', err);
  }
};
</script>


<template>
  <el-upload class="upload-demo" :on-change="uploadFileChange" drag :auto-upload="false">
    <el-icon class="el-icon--upload"><upload-filled /></el-icon>
    <div class="el-upload__text">
      Drop file here or <em>click to upload</em>
    </div>

  </el-upload>

  <div class="videoContainer">
    <video :src="videoPlayURL" :controls="true" ref="videoPreview" crossorigin="Anonymous" class="videoDom"></video>
  </div>
</template>



<style scoped>
.videoDom {
  width: 800px;
  height: 400px;
  border: solid 1px red;
}
</style>
  
相关文章
|
6天前
|
JavaScript 前端开发 API
Vue.js:现代前端开发的强大框架
【10月更文挑战第11天】Vue.js:现代前端开发的强大框架
58 41
|
4天前
|
前端开发 JavaScript 安全
在vue前端开发中基于refreshToken和axios拦截器实现token的无感刷新
在vue前端开发中基于refreshToken和axios拦截器实现token的无感刷新
28 4
|
3天前
|
Java 数据安全/隐私保护
Java ffmpeg 实现视频加文字/图片水印功能
【10月更文挑战第22天】在 Java 中使用 FFmpeg 实现视频加文字或图片水印功能,需先安装 FFmpeg 并添加依赖(如 JavaCV)。通过构建 FFmpeg 命令行参数,使用 `drawtext` 滤镜添加文字水印,或使用 `overlay` 滤镜添加图片水印。示例代码展示了如何使用 JavaCV 实现文字水印。
|
8天前
|
JavaScript 前端开发 API
Vue.js:打造高效前端应用的最佳选择
【10月更文挑战第9天】Vue.js:打造高效前端应用的最佳选择
11 2
|
8天前
|
计算机视觉 Python
FFMPEG学习笔记(一): 提取视频的纯音频及无声视频
本文介绍了如何使用FFmpeg工具从视频中提取纯音频和无声视频。提供了具体的命令行操作,例如使用`ffmpeg -i input.mp4 -vn -c:a libmp3lame output.mp3`来提取音频,以及`ffmpeg -i input.mp4 -c:v copy -an output.mp4`来提取无声视频。此外,还包含了一个Python脚本,用于批量处理视频文件,自动提取音频和生成无声视频。
15 1
|
9天前
|
JavaScript 前端开发 Java
VUE学习四:前端模块化,ES6和ES5如何实现模块化
这篇文章介绍了前端模块化的概念,以及如何在ES6和ES5中实现模块化,包括ES6模块化的基本用法、默认导出与混合导出、重命名export和import,以及ES6之前如何通过函数闭包和CommonJS规范实现模块化。
31 0
VUE学习四:前端模块化,ES6和ES5如何实现模块化
|
5天前
|
JSON 前端开发 JavaScript
Vue微前端新探:iframe优雅升级,扬长避短,重获新生
Vue微前端新探:iframe优雅升级,扬长避短,重获新生
27 0
|
5天前
|
JavaScript 前端开发 应用服务中间件
vue前端开发中,通过vue.config.js配置和nginx配置,实现多个入口文件的实现方法
vue前端开发中,通过vue.config.js配置和nginx配置,实现多个入口文件的实现方法
38 0
|
5天前
|
JavaScript 前端开发 应用服务中间件
Vue开发中,在实现单页面应用(SPA)前端路由时的hash模式和history模式的区别及详细介绍
Vue开发中,在实现单页面应用(SPA)前端路由时的hash模式和history模式的区别及详细介绍
13 0
|
6天前
|
监控 JavaScript 前端开发
深入了解Vue.js:构建现代前端应用的利器
【10月更文挑战第11天】深入了解Vue.js:构建现代前端应用的利器
10 0