Skip to content

Commit f424cec

Browse files
committed
Playground survives non-list arguments, self-contained vanilla example, FAQ answers
- The playground's editor preview and Run only use arguments that are a JSON list of strings; anything else crashed the page (#791, #872). - The vanilla example's download script lives in the example (#731). - FAQ: performance tips (#796), license summary (#902), file size and WORKERFS (#623, #757), stderr logs (#877), bit-exact raw frames (#818), WebM alpha (#621).
1 parent 37dced5 commit f424cec

6 files changed

Lines changed: 65 additions & 20 deletions

File tree

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// Downloads the @ffmpeg packages from npm into public/assets.
12
const tar = require("tar");
23
const fs = require("fs");
34

@@ -7,17 +8,14 @@ const ROOT = "public/assets";
78
const FFMPEG_VERSION = "0.12.15";
89
const UTIL_VERSION = "0.12.2";
910
const CORE_VERSION = "0.12.10";
10-
const CORE_MT_VERSION = "0.12.10";
1111

1212
const FFMPEG_TGZ = `ffmpeg-${FFMPEG_VERSION}.tgz`;
1313
const UTIL_TGZ = `util-${UTIL_VERSION}.tgz`;
1414
const CORE_TGZ = `core-${CORE_VERSION}.tgz`;
15-
const CORE_MT_TGZ = `core-mt-${CORE_MT_VERSION}.tgz`;
1615

1716
const FFMPEG_TGZ_URL = `${NPM_URL}/@ffmpeg/ffmpeg/-/${FFMPEG_TGZ}`;
1817
const UTIL_TGZ_URL = `${NPM_URL}/@ffmpeg/util/-/${UTIL_TGZ}`;
1918
const CORE_TGZ_URL = `${NPM_URL}/@ffmpeg/core/-/${CORE_TGZ}`;
20-
const CORE_MT_TGZ_URL = `${NPM_URL}/@ffmpeg/core-mt/-/${CORE_MT_TGZ}`;
2119

2220
const mkdir = (dir) => {
2321
!fs.existsSync(dir) && fs.mkdirSync(dir);
@@ -42,4 +40,3 @@ mkdir(ROOT);
4240
downloadAndUntar(FFMPEG_TGZ_URL, FFMPEG_TGZ, "ffmpeg");
4341
downloadAndUntar(UTIL_TGZ_URL, UTIL_TGZ, "util");
4442
downloadAndUntar(CORE_TGZ_URL, CORE_TGZ, "core");
45-
downloadAndUntar(CORE_MT_TGZ_URL, CORE_MT_TGZ, "core-mt");

‎apps/vanilla-app/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"description": "browser example",
55
"private": true,
66
"scripts": {
7-
"download": "node ../../scripts/download-assets.cjs",
7+
"download": "node download-assets.cjs",
88
"prestart": "pnpm download",
99
"start": "node server.js"
1010
},

‎apps/website/docs/faq.md‎

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,27 @@ A native `ffmpeg` binary is still faster if you can ship one.
2020

2121
### Why ffmpeg.wasm is so slow comparing to ffmpeg?
2222

23-
As of now, WebAssembly is still a lot slower than native, it is possible to further speed up using
24-
WebAssembly intrinsic, which is basically writing assembly code. It is something we are investigating
25-
and hope to introduce in the future.
23+
WebAssembly runs FFmpeg's C code without its hand-written assembly, and
24+
SIMD is limited to 128 bits, so encoding is several times slower than native.
25+
Decoding and remuxing are closer. `pnpm bench` in the repository
26+
measures common jobs against native FFmpeg on your machine.
2627

27-
Since 0.13, @ffmpeg/core runs FFmpeg's multi-threaded pipeline, which is
28-
faster than the single-thread core of 0.12.
28+
To speed things up:
29+
- Use a faster preset: `-preset ultrafast` for x264, `-deadline realtime` for VP9.
30+
- Remux with `-c copy` when you don't need to re-encode.
31+
- Mount large inputs with `WORKERFS` instead of copying them with `writeFile()`.
32+
- On machines with many cores, `load({ threads: 8 })` lets codecs use more threads.
2933

3034
### Is RTSP supported by ffmpeg.wasm?
3135

32-
We are trying to support, but so far WebAssembly itself lack of features like sockets which makes
33-
it hard to implement RTSP protocol. Possible workarounds are still under investigation.
36+
No. Browsers don't give WebAssembly raw TCP/UDP sockets, so ffmpeg.wasm is
37+
built without network protocols (RTSP, RTMP, HTTP). Fetch inputs in
38+
JavaScript and write them to the file system, or use WebRTC/MediaRecorder for
39+
live streams.
3440

3541
### What is the license of ffmpeg.wasm?
3642

37-
There are two components inside ffmpeg.wasm:
43+
The JavaScript packages are MIT; `@ffmpeg/core` is GPL-2.0-or-later. There are two components inside ffmpeg.wasm:
3844

3945
- @ffmpeg/ffmpeg (https://github.com/ffmpegwasm/ffmpeg.wasm/packages/ffmpeg)
4046
- @ffmpeg/core (https://github.com/ffmpegwasm/ffmpeg.wasm/packages/core)
@@ -45,7 +51,30 @@ There are two components inside ffmpeg.wasm:
4551

4652
### What is the maximum size of input file?
4753

48-
2 GB, which is a hard limit in WebAssembly. Might become 4 GB in the future.
54+
Files written with `writeFile()` live in JavaScript memory, outside the
55+
WebAssembly heap, so they can be larger than 2 GB if the browser allows it.
56+
For big inputs, `mount("WORKERFS", { files: [file] }, "/input")` avoids
57+
copying them at all. FFmpeg's own memory can grow to 4 GB, the limit of 32-bit
58+
WebAssembly.
59+
60+
`WORKERFS` mounts are read-only: write outputs outside the mount point.
61+
62+
### Why are all log messages of type `stderr`?
63+
64+
FFmpeg writes its log (banner, progress, warnings) to stderr, like the
65+
native `ffmpeg`. Only a few outputs, such as `ffprobe` results without `-o`,
66+
go to stdout.
67+
68+
### Why do raw frames differ slightly from native FFmpeg?
69+
70+
The WebAssembly build uses swscale's C code paths, while native FFmpeg uses
71+
assembly with slightly different rounding. For output closer to native, add
72+
`-sws_flags accurate_rnd+bitexact`.
73+
74+
### How do I decode a WebM video with transparency?
75+
76+
FFmpeg's built-in VP8/VP9 decoders drop the alpha channel. Ask for the
77+
libvpx decoder before the input: `["-c:v", "libvpx-vp9", "-i", "input.webm", ...]`.
4978

5079
### How can I build my own ffmpeg.wasm?
5180

‎apps/website/src/components/Playground/Workspace/Editor.tsx‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,19 @@ import "ace-builds/src-noconflict/mode-text";
1414
import "ace-builds/src-noconflict/theme-dracula";
1515
import "ace-builds/src-noconflict/theme-github";
1616

17-
const genFFmpegText = (args: string) => {
18-
let data: any = [];
17+
// The arguments as a list of strings, or [] while the JSON is incomplete or not
18+
// such a list (#791, #872).
19+
export const parseArgs = (args: string): string[] => {
1920
try {
20-
data = JSON.parse(args);
21-
} catch (e) {}
21+
const data: unknown = JSON.parse(args);
22+
return Array.isArray(data) && data.every((a) => typeof a === "string") ? data : [];
23+
} catch {
24+
return [];
25+
}
26+
};
27+
28+
const genFFmpegText = (args: string) => {
29+
const data = parseArgs(args);
2230
return `// equivalent ffmpeg.wasm API call
2331
ffmpeg.exec(${JSON.stringify(data)});
2432

‎apps/website/src/components/Playground/Workspace/index.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { downloadFile } from "@site/src/util";
1212
import { Node } from "./types";
1313
import FileSystemManager from "./FileSystemManager";
1414
import { SAMPLE_FILES } from "../const";
15-
import Editor from "./Editor";
15+
import Editor, { parseArgs } from "./Editor";
1616

1717
const defaultArgs = JSON.stringify(["-i", "video.webm", "video.mp4"], null, 2);
1818

@@ -136,7 +136,7 @@ export default function Workspace({ ffmpeg: _ffmpeg }: WorkspaceProps) {
136136
ffmpeg.on("log", logListener);
137137
ffmpeg.on("progress", progListener);
138138
const start = performance.now();
139-
await ffmpeg.exec(JSON.parse(args));
139+
await ffmpeg.exec(parseArgs(args));
140140
setTime(performance.now() - start);
141141
ffmpeg.off("log", logListener);
142142
ffmpeg.off("progress", progListener);

‎tests/e2e/run.mjs‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,17 @@ await test("playground: loads the core and runs the default command", async (pag
107107
await workspace.getByText("video.mp4", { exact: true }).waitFor();
108108
});
109109

110+
await test("playground: survives arguments that aren't a list of strings (#791, #872)", async (page) => {
111+
await page.goto(`${url}/playground`);
112+
const editor = page.getByTestId("playground").locator("#input-args textarea");
113+
for (const args of ["", "{", '["-i", {"toString": "1"}]', "42"]) {
114+
await editor.focus();
115+
await page.keyboard.press("ControlOrMeta+A");
116+
await page.keyboard.insertText(args);
117+
await page.getByTestId("playground").getByText("ffmpeg.exec([]);").waitFor();
118+
}
119+
});
120+
110121
await browser.close();
111122
server.close();
112123
process.exit(failures ? 1 : 0);

0 commit comments

Comments
 (0)