Build1 distinct publisher3 min readUpdated
A developer running media conversion entirely in the browser reports that FFmpeg.wasm hung with no error and no log on roughly one heavy job in ten until he added a 180 second idle timer.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
A developer building a client-side media converter has published a post-mortem saying that FFmpeg.wasm, in its multithreaded build, can hang indefinitely with no error, no log output and no recovery path except killing the browser tab [1]. By his account this happened on roughly one in ten heavy jobs before he shipped a watchdog [2], which is a failure rate around 10 percent on exactly the workloads users care about [9].
The mechanism is specific and worth knowing before you commit. With `@ffmpeg/core-mt`, Emscripten creates a pthread pool roughly matching `navigator.hardwareConcurrency`; if the decoder and encoder together ask for more threads than the pool holds, `pthread_create` blocks the runtime permanently [4]. There is no exception to catch and nothing in the log. The user sees a progress bar that has stopped moving [5].
The fix in the post is not a fix, it is a detector. Every log line and every progress event refreshes a `_lastActivity` timestamp, and silence longer than `WATCHDOG_IDLE_MS = 180_000` is treated as a hang rather than slow encoding [6]. The reasoning is that a live encoder prints statistics several times a second even with demanding codecs [7], which makes the three minute threshold more than 180 times the normal gap between log lines [10]. On trip, the code surfaces a hang message and restarts in a safe mode [8]. The supplied text cuts off before that safe mode is defined, so treat the recovery half as unverified.
Be equally careful with the other two ceilings. The author's headline asserts a 2 GB ceiling and a codec that lies to users alongside the deadlocks [11], and his stated agenda includes processing multi-gigabyte files inside a 32-bit heap using WORKERFS [12] and an explanation of why he ships VP8 when the user asks for VP9 [13]. Those are the claims; the numbers behind them are not in the material we have. A VP8-for-VP9 substitution is still the useful signal: a request that returns success is not the same as a file the user can open.
Around that, the build is deliberately thin. No React, Vue or Svelte, just plain ESM JavaScript and Vite, on the grounds that there is no complex reactive state and the real weight sits in the WASM engines, FFmpeg and Pyodide, so a 100-plus KB framework buys nothing [16]. The site is a multi-page app where each tool owns an `index.html` that Vite turns into its own Rollup entry point, so adding a tool means adding a folder [18]. Imports flow one way, pages to ui to core, and `core/`, `engines/` and `utils/` are forbidden from touching the DOM so they can be tested in Node with Vitest; circular imports in an MPA Rollup build are described as undefined behaviour [19]. The commercial argument for the whole exercise is that the server-side path uploads a half-gigabyte file, waits in a queue, then downloads, and the file sits on someone else's machine throughout [14].
Watch the failure mode, not the demo. If you are removing the server, the two launch-blocking items are an activity watchdog and a hard input size gate, because both of the ceilings here fail quietly rather than loudly.
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
In the multithreaded build @ffmpeg/core-mt, Emscripten creates a limited pool of pthreads roughly matching navigator.hardwareConcurrency; if the decoder and encoder request more threads at the same time than the pool holds, pthread_create blocks the runtime forever, with no errors and no logs.
The watchdog refreshes a _lastActivity timestamp on every log message and every progress event; if FFmpeg stays silent for more than 180 seconds (WATCHDOG_IDLE_MS = 180_000) that is treated as a hang rather than slow encoding.
When the watchdog trips, the code rejects with a message reporting a detected hang and says it is restarting in a safe mode; the supplied text ends mid-message.
BrowsersKit is a set of tools (media converter, PDF, math, Python sandbox) that run fully on the client, with no uploads, no queues and no server costs.
FFmpeg.wasm can hang forever with no error, no log and no way out except killing the browser tab.
The author calls the deadlock only the third-worst problem he hit while building a media converter that runs entirely in the browser.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
One first-party post, code shown, nothing independent
The mechanism and mitigation are documented well for a single source — pasted execWatched code, the WATCHDOG_IDLE_MS constant, the MODES ladder, the Vite entry-point crawler — so the engineering claims are internally checkable. But everything rests on one self-published developer account about his own product: no upstream issue, no reproduction case, no telemetry behind the headline hang rate, an internal contradiction about encoder log cadence, and a body that truncates before the 2 GB ceiling and VP9 sections it promised.
One developer's own project
The only adoption signal is the author's first-party statement that the watchdog and fallback ladder are shipped in his BrowsersKit media converter, validated by his own E2E runs on 4K files. There are no users, job volumes, downloads, dependents or third-party deployments, and no evidence that other FFmpeg.wasm consumers use this pattern.
Headline outruns the delivered evidence
The core technical content is sober and largely self-verifying, which keeps the gap small. It is positive rather than zero because the framing leans on claims the text does not support: a memorable 'one in ten heavy jobs' rate with no measurement behind it, a headline promise of a 2 GB ceiling and 'a codec that lies to users' that the supplied body never reaches, a rhetorical one-second abandonment threshold with no analytics, and a stated encoder log cadence the post itself later contradicts.
Author markets the product he is debugging
The post is self-published on a developer blogging platform by the builder of BrowsersKit, and its narrative arc — hundreds of online converters make you hand files to somebody else's machine, so I removed the server — is product positioning as much as engineering writeup. No independent editing, review or disclosure moderates it, and the promotional interest is never stated. Mitigating factor: the author foregrounds his own failures, including false watchdog triggers and shipping VP8 for VP9, which cuts against pure marketing.
Credible practitioner detail, unverified numbers
Confidence is moderate-low: one vendor-authored source, zero corroboration, an unmeasured central statistic and a truncated body, offset by specific, plausible, code-level detail about a documented Emscripten threading constraint and an unusually candid account of the mitigation's own false positives. The mechanism and the watchdog pattern can be relied on directionally; the rates, performance and business claims cannot.
build
Every viewer hits your HLS key endpoint in the same second, and almost nobody tests it1 distinct publisher
build
796 pages of semantic search with no vector database, and what it cost to skip one1 distinct publisher
build
Two timeout numbers, not new code, fixed a nightly job that failed 2-3 mornings a week1 distinct publisher
build
The streaming edit is a software layer: proxies, stubs and an ffmpeg hook on storage you own1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 16, 2026