如何在主线程直接运行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
相关产品推荐
相关产品推荐

