Web Worker 系统
Web Worker 系统是 ffmpeg.wasm 的关键组件,它使 FFmpeg 的计算密集型操作能够在后台线程中运行,防止阻塞主浏览器线程。这种架构对于在进行视频转码等计算密集型任务时保持 UI 响应性至关重要。
本文档记录了 ffmpeg.wasm 中基于 Web Worker 的执行模型的设计与实现。
架构
Web Worker 系统由三个主要组件组成:
- FFmpeg Class:应用程序交互的主要 JavaScript API。
- Web Worker (
worker.ts):托管 WebAssembly 模块并管理其生命周期的后台线程。 - FFmpeg Core:实际编译为 WebAssembly 的 FFmpeg 库(
@ffmpeg/core或@ffmpeg/core-mt)。
这些组件通过消息传递接口进行交互,以实现 FFmpeg 操作的无阻塞执行。
系统实体映射
下图将高层系统组件映射到代码库中的具体实现实体。
基于消息的通信协议
主线程与 Web Worker 之间的通信通过由 FFMessageType 枚举定义的结构化消息协议进行。
主线程中的 FFmpeg 类在通过 getMessageID() 生成的唯一消息 ID 索引的 #resolves 和 #rejects 映射中维护 promises。
消息生命周期
初始化与加载机制
初始化过程涉及在 Web Worker 中加载 FFmpeg Core 模块。这支持 UMD 和 ESM 两种加载策略,以确保在不同构建工具(如 Vite、Webpack)下的兼容性。
UMD/ESM 加载逻辑
Worker 首先尝试通过 importScripts()(Classic Worker/UMD)加载核心模块。如果失败,则回退到动态 import()(Module Worker/ESM)。
多线程的 "Hash Hack"
一个重要的技术细节是在初始化多线程核心时使用的 mainScriptUrlOrBlob 参数。为了确保 Emscripten 生成的 worker 能够找到正确的 .wasm 和 .worker.js 文件,ffmpeg.wasm 将这些路径编码为 Base64 编码的 JSON 字符串,并将其作为 URL 哈希附加到核心 URL 上。
// worker.ts implementation of the hack
mainScriptUrlOrBlob: `${coreURL}#${btoa(
JSON.stringify({ wasmURL, workerURL })
)}`,事件流式传输 (Log & Progress)
与标准请求 - 响应消息不同,LOG 和 PROGRESS 事件是在执行过程中从 worker 流式传输到主线程的。
- Logger 设置:在
worker.ts中,配置ffmpeg.setLogger以向每一行输出发送类型为FFMessageType.LOG的消息。 - Progress 设置:同样地,
ffmpeg.setProgress发送包含进度小数(0 到 1)和当前时间戳的FFMessageType.PROGRESS消息。 - 主线程处理:
FFmpeg类在其处理器中接收这些消息,并遍历注册的LogEventCallback或ProgressEventCallback函数。
实现细节
Worker 管理
Worker 在 load() 方法内部惰性实例化。它支持用于自定义 worker 实现的 classWorkerURL,但默认使用捆绑的 worker.js。
| Method | Role | Implementation |
|---|---|---|
load() | 初始化 worker 和 core | |
terminate() | 终止 worker 并清除状态 | |
exec() | 向 worker 发送命令 | |
writeFile() | 向 worker VFS 发送数据 |
多线程安全
当使用 @ffmpeg/core-mt 时,浏览器环境必须是“跨域隔离”的,以允许使用 SharedArrayBuffer。这是多线程 worker 系统正常运行的前提条件。
错误处理
Worker 将所有操作包裹在 try...catch 块中。如果发生错误,它会捕获错误消息并向主线程发送类型为 FFMessageType.ERROR 的消息。FFmpeg 类随后在其 #rejects 映射中查找对应的 promise,并使用错误数据将其拒绝。