You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在主线程直接运行ffmpeg.wasm?(适用场景:Cloudflare Worker等)

在Cloudflare Worker主线程运行ffmpeg.wasm解决「Worker is not defined」问题

问题描述

我尝试在Cloudflare Worker中运行自定义构建的ffmpeg.wasm,但因为CF Worker环境无法启动Web Worker,出现「Worker is not defined」错误。已通过自定义构建得到体积仅8.4MB(压缩后2.8MB)的ffmpeg.wasm,符合CF Worker的10MB压缩体积限制,想知道能否在主线程直接运行ffmpeg.wasm。

当前Worker代码:

import { FFmpeg } from '@ffmpeg/ffmpeg';
import { fetchFile } from '@ffmpeg/util';

export interface Env {
    // Add any environment variables here
}

export default {
    async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
        if (request.method !== 'POST') {
            return new Response('Send a POST request with a video file', { status: 400 });
        }

        const formData = await request.formData();
        const videoFile = formData.get('video') as File | null;

        if (!videoFile) {
            return new Response('No video file provided', { status: 400 });
        }

        try {
            const trimmedVideo = await trimVideo(videoFile);
            if (!trimmedVideo) {
                return new Response(`Failed to trim video`, { status: 500 });
            }
            return new Response(trimmedVideo, {
                headers: {
                    'Content-Type': 'video/mp4',
                    'Content-Disposition': `attachment; filename="trimmed_${videoFile.name}"`,
                },
            });
        } catch (error) {
            // @ts-ignore
            return new Response(`Error processing video: ${error.message}`, { status: 500 });
        }
    },
};

async function trimVideo(videoFile: File): Promise<Uint8Array | null> {
    const ffmpeg = new FFmpeg();
    try {
        await ffmpeg.load({
            coreURL: 'http://localhost:8787/ffmpeg-core.js',
            wasmURL: 'http://localhost:8787/ffmpeg-core.wasm',
        });
    } catch (error) {
        // @ts-ignore
        console.error(error.message);
        return null;
    }

    const inputFileName = 'input.mp4';
    const outputFileName = 'output.mp4';

    ffmpeg.writeFile(inputFileName, await fetchFile(videoFile));

    await ffmpeg.exec([
        '-i',
        inputFileName,
        '-ss',
        '1', // Start at 1 second
        '-to',
        '-1', // End 1 second before the end
        '-c',
        'copy', // Use the same codec (faster)
        outputFileName,
    ]);

    const data = await ffmpeg.readFile(outputFileName);
    await ffmpeg.deleteFile(inputFileName);
    await ffmpeg.deleteFile(outputFileName);

    return new Uint8Array(data as ArrayBuffer);
}

解决方案

要在Cloudflare Worker主线程运行ffmpeg.wasm,核心是禁用ffmpeg.wasm默认的Web Worker启动逻辑,直接在主线程加载执行Wasm核心,具体步骤如下:

1. 调整ffmpeg.wasm加载配置

创建FFmpeg实例时,通过worker: false参数禁用Web Worker,同时确保核心文件(ffmpeg-core.js、ffmpeg-core.wasm)托管在可被Worker访问的位置(如Cloudflare R2或Pages):

const ffmpeg = new FFmpeg({ log: true });
await ffmpeg.load({
  coreURL: 'https://your-worker-domain/ffmpeg-core.js',
  wasmURL: 'https://your-worker-domain/ffmpeg-core.wasm',
  worker: false // 关键:禁用Web Worker,强制主线程运行
});

2. 适配Cloudflare Worker环境限制

  • 确保自定义构建的ffmpeg.wasm未依赖Worker专属API,编译时需确认参数适配纯主线程环境
  • 优先使用异步API处理文件读写和命令执行,避免同步阻塞主线程
  • 利用-c copy这类快速流拷贝命令,减少CPU占用,避免触发Worker超时限制

3. 修改后的完整代码示例

import { FFmpeg } from '@ffmpeg/ffmpeg';
import { fetchFile } from '@ffmpeg/util';

export interface Env {
    // 可添加R2绑定或其他环境变量
}

export default {
    async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
        if (request.method !== 'POST') {
            return new Response('请发送包含视频文件的POST请求', { status: 400 });
        }

        const formData = await request.formData();
        const videoFile = formData.get('video') as File | null;

        if (!videoFile) {
            return new Response('未提供视频文件', { status: 400 });
        }

        try {
            const trimmedVideo = await trimVideo(videoFile);
            if (!trimmedVideo) {
                return new Response('视频裁剪失败', { status: 500 });
            }
            return new Response(trimmedVideo, {
                headers: {
                    'Content-Type': 'video/mp4',
                    'Content-Disposition': `attachment; filename="trimmed_${videoFile.name}"`,
                },
            });
        } catch (error) {
            return new Response(`视频处理错误: ${(error as Error).message}`, { status: 500 });
        }
    },
};

async function trimVideo(videoFile: File): Promise<Uint8Array | null> {
    const ffmpeg = new FFmpeg({ log: true });
    try {
        // 禁用Worker,直接在主线程加载核心
        await ffmpeg.load({
            coreURL: 'https://your-worker-asset-url/ffmpeg-core.js',
            wasmURL: 'https://your-worker-asset-url/ffmpeg-core.wasm',
            worker: false
        });
    } catch (error) {
        console.error('加载ffmpeg核心失败:', (error as Error).message);
        return null;
    }

    const inputFileName = 'input.mp4';
    const outputFileName = 'output.mp4';

    await ffmpeg.writeFile(inputFileName, await fetchFile(videoFile));

    await ffmpeg.exec([
        '-i',
        inputFileName,
        '-ss',
        '1', // 从第1秒开始
        '-to',
        '-1', // 结束于倒数第1秒
        '-c',
        'copy', // 直接拷贝流,无需重新编码
        outputFileName,
    ]);

    const data = await ffmpeg.readFile(outputFileName);
    await ffmpeg.deleteFile(inputFileName);
    await ffmpeg.deleteFile(outputFileName);
    // 处理完成后释放Wasm资源
    await ffmpeg.terminate();

    return new Uint8Array(data as ArrayBuffer);
}

额外注意事项

  • 资产托管:将ffmpeg核心文件上传到Cloudflare Pages或R2,通过Worker绑定或公开URL访问,禁止使用localhost(仅本地测试有效)
  • 资源清理:使用ffmpeg.terminate()释放Wasm内存,避免内存泄漏
  • 性能优化:复杂转码操作易触发Worker超时,建议优先使用流拷贝,必要时拆分任务

内容的提问来源于stack exchange,提问作者clamentjohn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 12:45:01