跳到正文
DEV Community· Denny Kot·· 2 小時前AI 評分56

在瀏覽器執行 ffmpeg、Whisper 與背景移除:實作經驗

Running ffmpeg, Whisper and background removal in the browser: lessons learned

AI 導讀

Denny Kot 分享在純靜態網頁執行 ffmpeg.wasm、Whisper、ONNX 分割模型及 Tesseract 的經驗,指出這些引擎無需後端也能運作。他建議按需載入引擎、分開呈現模型下載與處理進度,並以縮小影像、分段處理音訊及 Web Worker 控制記憶體和保持介面流暢。多執行緒 WASM 所需的 COOP/COEP 標頭會影響第三方嵌入,因此可按速度需求採用單執行緒版本。

正文

I ship a few "server-class" engines in plain static web pages: ffmpeg.wasm for video, Whisper for transcription, an ONNX segmentation model for background removal, and Tesseract for OCR. No backend at all. They all work, but each one taught me something. Here are the practical lessons.

1. Never load the engine with the page

A 30 MB WASM binary in the critical path will ruin your Largest Contentful Paint and your search rankings. The page should be fully usable (dropzone, options, explanation) before any engine code arrives.

The pattern I use:

// Import only when needed...
async function getFFmpeg() {
  const { FFmpeg } = await import("@ffmpeg/ffmpeg");
  const ff = new FFmpeg();
  await ff.load({ coreURL, wasmURL });
  return ff;
}

// ...but warm the cache once the page is idle
window.addEventListener("load", () => {
  const conn = (navigator as any).connection;
  if (conn?.saveData) return;            // respect Data Saver
  requestIdleCallback(() => fetch(wasmURL));
});

Most users spend a few seconds reading or picking a file. That's enough time to fetch the engine in the background.

2. Show real progress, not a spinner

A spinner during a 40-second Whisper run looks broken. Every engine I use reports progress in some form:

  • ffmpeg.wasm has a progress event (0–1) and a log stream with time= lines,
  • transformers.js has progress_callback for model downloads,
  • tesseract.js has a logger with status and progress.

Show two phases separately: "Downloading model (first time only) 63%" and then "Processing 21%". Users accept a one-time download much more readily when it's labeled.

3. Memory is the real limit

WASM memory is a single linear buffer, and on mobile the tab gets killed long before you'd expect.

  • Delete files from ffmpeg's virtual FS as soon as you've read the output (ff.deleteFile).
  • Downscale before ML. Segmentation models work at fixed input sizes anyway, so feeding them a 48 MP photo only wastes memory. Run the model on a smaller copy and apply the mask at full resolution.
  • Process audio in chunks for Whisper (30 s windows) and don't decode an hour of audio at once.
  • Release object URLs with URL.revokeObjectURL when you're done.

4. Keep the UI thread free

Anything heavy belongs in a Web Worker. ffmpeg.wasm and transformers.js already run in workers. For my own code (hashing big files, building ZIP/TAR archives) I use fflate's async APIs or a small dedicated worker. A frozen tab during a 2 GB hash makes users think the page has crashed.

5. Cross-origin isolation is a trade-off

Multithreaded WASM needs SharedArrayBuffer, which requires the COOP/COEP headers. Those headers break third-party embeds such as ads and analytics. I use the single-threaded builds wherever the speed difference is acceptable, and keep isolation only on pages that really need it.

6. Cache aggressively

Models and WASM binaries never change for a given version, so serve them with long Cache-Control: immutable lifetimes and versioned URLs. transformers.js also stores models in the Cache API, so the second visit is instant and works offline.

7. Tell users it's local, and let them check

"Your file is never uploaded" is the main selling point, so it should be easy to verify. A one-line note under each tool ("works entirely in your browser") plus the fact that the Network tab stays empty builds more trust than any privacy policy.


You can see these in action, with the Network tab open:

All of them are part of Uttily, a set of free tools that run entirely in the browser. What engines have you got working in the browser? I'm curious what else is possible now.

來源:DEV Community · dev.to