因为做过的项目中,有涉及到视频上传的模块,常规视频的mp4格式可以直接进行预览和展示,但是部分视频文件无法直接预览,需要进行转码成mp4进行处理,我在GitHub上寻找到了ffmpeg的wasm版本,并成功在项目中实现转码。
这里给出一个将asf格式的视频转换mp4的示例
这里给出解决思路
首先,我们安装 @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>