最新的ffmepg.js前端VUE3实现视频、音频裁剪上传功能

package.json

复制代码
"dependencies": {
    "@ffmpeg/ffmpeg": "^0.12.10",
    "@ffmpeg/util": "^0.12.1"
}

vue3组件代码

根据需要更改

html 复制代码
<script setup lang="ts">
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { fetchFile, toBlobURL } from '@ffmpeg/util';
import useInject from '@/utils/useInject';

import { reactive, onUnmounted } from 'vue';
const { $global, $fn } = useInject();

const props: any = defineProps<{
    params?: any;
    url?: any;
}>();

let ffmpegObj: any = reactive({
    file: null,
    urlpre: null,
    url: null,
    params: {
        ...(props?.params || {}),
        ext: '',
        starttime: 0,
        endtime: 3,
        maxtime: 0,
    },
});

const ffmpeg = new FFmpeg();

function filechange(e: any) {
    ffmpegObj.file = e.target.files[0];

    // 原视频预览方案一
    const reader: any = new FileReader();
    reader.readAsDataURL(ffmpegObj.file);
    reader.onload = () => {
        ffmpegObj.url = '';
        ffmpegObj.urlpre = reader.result;

        const audioElement: any = new Audio(ffmpegObj.urlpre);
        audioElement.addEventListener('loadedmetadata', () => {
            ffmpegObj.params.maxtime = audioElement.duration;
            ffmpegObj.params.starttime = 0;
            ffmpegObj.params.endtime = audioElement.duration;
        });
    };

    // 原视频预览方案二
    // const reader: any = new FileReader();
    // reader.readAsArrayBuffer(ffmpegObj.file);
    // let blob = null;
    // reader.onload = (e: any) => {
    //     if (typeof e.target.result === 'object') {
    //         blob = new Blob([e.target.result]);
    //     } else {
    //         blob = e.target.result;
    //     }
    //     ffmpegObj.url = '';
    //     if (blob == null) return;
    //     const url = URL.createObjectURL(blob);
    //     ffmpegObj.urlpre = url;
    //     const audioElement = new Audio(url);
    //     audioElement.addEventListener('loadedmetadata', function () {
    //         ffmpegObj.params.maxtime = audioElement.duration;
    //         ffmpegObj.params.starttime = 0;
    //         ffmpegObj.params.endtime = audioElement.duration;
    //     });
    // };
}
async function doffmpeg() {
    try {
        if (!ffmpegObj.file) {
            return;
        }
        ffmpegObj.fileext = ffmpegObj.file.name.split('.').pop();

        const inputName = `input.${ffmpegObj.fileext}`;
        const outputName = `output.${
            ffmpegObj.params.ext ? ffmpegObj.params.ext : ffmpegObj.fileext
        }`;

        console.log(666.001, ffmpeg, ffmpegObj);

        ffmpeg.on('log', ({ message: msg }) => {
            ffmpegObj.msglogger = msg;
        });
        await ffmpeg.load({
            coreURL: await toBlobURL(
                './ffmpeg/ffmpeg-core.js',
                'text/javascript'
            ),
            wasmURL: await toBlobURL(
                './ffmpeg/ffmpeg-core.wasm',
                'application/wasm'
            ),
            workerURL: await toBlobURL(
                `./ffmpeg/ffmpeg-core.worker.js`,
                'text/javascript'
            ),
        });

        await ffmpeg.writeFile(inputName, await fetchFile(ffmpegObj.urlpre));
        await ffmpeg.exec([
            '-ss',
            `${
                ffmpegObj.params.starttime !== undefined
                    ? ffmpegObj.params.starttime
                    : 0
            }`,
            '-t',
            `${
                (ffmpegObj.params.endtime !== undefined
                    ? ffmpegObj.params.endtime
                    : 3) -
                (ffmpegObj.params.starttime !== undefined
                    ? ffmpegObj.params.starttime
                    : 0)
            }`,
            '-i',
            inputName,
            outputName,
        ]);

        ffmpegObj.data = await ffmpeg.readFile(outputName);
        ffmpegObj.blob = new Blob([ffmpegObj.data.buffer], {
            type:
                'video/' + ffmpegObj.params.ext
                    ? ffmpegObj.params.ext
                    : ffmpegObj.fileext,
        });

        ffmpegObj.url = URL.createObjectURL(ffmpegObj.blob);

        ffmpegObj.urlpre = ''; // 清空原预览视频
    } catch (err) {
        alert('出错了');
        throw err;
    }
}
function blobToBase64(blob: any) {
    console.log(666.3002, blob);
    return new Promise((resolve, reject) => {
        const fileReader = new FileReader();
        fileReader.readAsDataURL(blob);
        fileReader.onload = () => {
            resolve(fileReader.result);
        };
        fileReader.onerror = () => {
            reject(new Error('文件流异常'));
        };
    });
}
// function convertBlobToFile(blob, fileName) {
//     blob.lastModifiedDate = new Date();
//     blob.name = fileName;
//     return blob;
// }
async function submit() {
    await doffmpeg();
    const resdata: any = await blobToBase64(ffmpegObj.blob);
    // const resdata: any = convertBlobToFile(ffmpegObj.blob, 'aaa');
    console.log(666.30002, resdata);
    $fn.useApiFiles()
        .request({
            data: props.url
                ? { url: props.url }
                : {
                      dir: 'chat/chat',
                      uid: $fn.As.Uuid('CU'),
                      ext:
                          '.' +
                          (ffmpegObj.params.ext
                              ? ffmpegObj.params.ext
                              : ffmpegObj.fileext),
                      data: resdata,
                  },
        })
        .then((res: any) => {
            console.log(666.789, res);
            props?.params?.fn(res);
        })
        .catch((err: any) => {
            $global.tipsfn({ type: 'err', con: err });
        });
}

//下载
function downffmpeg() {
    const oA: any = document.createElement('a');
    oA.download =
        'video_' +
        Date.now() +
        '.' +
        (ffmpegObj.params.ext ? ffmpegObj.params.ext : ffmpegObj.fileext);
    oA.href = ffmpegObj.url;
    document.body.appendChild(oA);
    oA.click();
    // 释放 URL 对象
    URL.revokeObjectURL(oA.href);
    oA.remove();
}
onUnmounted(() => {
    ffmpegObj = null;
});
</script>
<template>
    <div class="as-ff-area">
        <div class="as-show-area">
            <!-- <template v-if="ffmpegObj.urlpre"> -->
            原视频
            <video :src="ffmpegObj.urlpre" controls></video>
            <!-- </template> -->
            <!-- <template v-else> -->
            裁剪后视频
            <video :src="ffmpegObj.url" controls></video>
            <!-- </template> -->
            日志:
            {{ ffmpegObj.params.starttime }}
            -
            {{ ffmpegObj.params.endtime }}
            ({{ ffmpegObj.params.maxtime }})
            <input
                type="range"
                v-model="ffmpegObj.params.starttime"
                min="0"
                :max="ffmpegObj.params.maxtime"
            />
            <input
                type="range"
                v-model="ffmpegObj.params.endtime"
                min="0"
                :max="ffmpegObj.params.maxtime"
            />
            <!-- <input type="number" v-model="ffmpegObj.params.starttime" />
            <input type="number" v-model="ffmpegObj.params.endtime" /> -->
            {{ ffmpegObj.msglogger }}
            {{ ffmpegObj.msgprogress }}
        </div>

        <div class="as-file-area">
            选择图片:
            <input class="file" type="file" @change="filechange" />
        </div>

        <div class="as-btn-area">
            <button @click="doffmpeg()">裁剪</button>
            <button @click="downffmpeg()">下载</button>
            <button @click="submit()">提交</button>
        </div>
    </div>
</template>

<style scoped>
.as-ff-area {
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
}
.as-btn-area {
    text-align: center;
    height: auto;
}
.as-btn-area > button {
    user-select: none;
    padding: 5px 12px;
    margin: 15px 5px 0 5px;
}
.as-file-area {
    padding: 5px 12px;
    text-align: center;
    height: auto;
    width: auto;
    display: inline-block;
    background-color: var(--ch1);
}
.as-show-area {
    flex-grow: 1;
    width: 100%;
    overflow: auto;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
}
</style>
相关推荐
捻tua馔...几秒前
antd3的表单实现(HOC解决方案)
前端·javascript·react.js
支付宝体验科技2 分钟前
支付宝 KJS Compose 动态化方案与架构设计
前端·客户端
AllinLin13 分钟前
JS中的call apply bind全面解析
前端·javascript·vue.js
阿乐去买菜17 分钟前
2025 年末 TypeScript 趋势洞察:AI Agent 与 TS 7.0 的原生化革命
前端
POLITE318 分钟前
Leetcode 438. 找到字符串中所有字母异位词 JavaScript (Day 4)
javascript·算法·leetcode
创思通信19 分钟前
STM32F103C8T6采 DS18B20,通过A7680C 4G模块不断发送短信到手机
javascript·stm32·智能手机
海绵宝龙22 分钟前
Vue 中的 Diff 算法
前端·vue.js·算法
zhougl99623 分钟前
vue中App.vue和index.html冲突问题
javascript·vue.js·html
止观止24 分钟前
告别全局污染:深入理解 ES Modules 模块化与构建工具
javascript·webpack·vite·前端工程化·es modules
沃虎电子27 分钟前
沃虎电子【产品推荐】音频变压器:看不见的声学基石,如何定义专业音频设备的品质?
人工智能·音视频