libav.js and FFmpeg compiled to WebAssembly for the browser
This is a compilation of the libraries associated with handling audio and video in ffmpeg—libavformat, libavcodec, libavfilter, libavutil, libswresample, and libswscale—for emscripten, and thus the web.
At a glance
- What is it?
- libav.js is a WebAssembly and asm.js build of the FFmpeg libraries, libavformat, libavcodec, libavfilter, libavutil and libswresample, along with the ffmpeg and ffprobe command line tools, and it is distributed as npm packages with per-codec variants. The interesting constraint is the licence, since FFmpeg is LGPL and the compiled sources are shipped with the library, and the interesting engineering is the emscripten build configuration that makes a blocking C library usable from a web worker.
- Who is it for?
- libav.js is a good fit for a browser application that has to decode or transcode media without a server, because it gives you the real FFmpeg libraries rather than a JavaScript reimplementation, and the per-codec variants let you ship only the codec you need.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 53 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What libav.js actually is
libav.js is a compilation of the libraries associated with handling audio and video in FFmpeg, libavformat, libavcodec, libavfilter, libavutil and libswresample, for WebAssembly and asm.js and thus the web, as well as the ffmpeg and ffprobe command line tools themselves. It is compiled via emscripten, is highly customizable, and has what the readme calls a ruthless commitment to correct licensing.
The short summary the readme gives is that this is a pure JavaScript and WebAssembly system for low-level audio and video encoding, decoding, muxing, demuxing and filtering.
The scope is worth stating precisely, because it is narrower than FFmpeg itself. This is not a JavaScript port of FFmpeg; it is the actual C libraries compiled to WebAssembly, with a thin JavaScript wrapper around them. That distinction matters for everything from performance to licensing, because you are running the same code that a native ffmpeg build runs, and you inherit the same licence obligations.
The project is a fork of the FamiTracker-style naming convention, not related; the author is Yahweasel, and the repository lives under that name. The primary language is JavaScript, the licence field is a custom one GitHub cannot classify with a pointer to the LICENSE file, and the recent releases are v6.10.9.0 from 2026-08-18, v6.9.8.1 from 2026-06-29 and v6.8.8.0 from 2025-09-02. The last push was on 2026-08-31 and the default branch is master.
The audience is a web developer who needs media processing in the browser and has concluded that a native ffmpeg.wasm is not a starting point, either because of the feature set or because of the licensing.
The licence, and why sources ship with the build
The licensing section is short and it is the first thing to understand about the project.
FFmpeg is released under the LGPL. Therefore, if you distribute this library, you must provide sources. The sources are included in the sources/ directory of the compiled version of libav.js.
That is the whole compliance story, and it is unusually clean. Rather than making you chase the corresponding FFmpeg release and patch set, the build ships the sources in the package. For a web project that means your deployed bundle carries the C source of the codecs you use, which is a size cost, and for a commercial project it means your obligation is discharged by a directory rather than by a process.
There is a second licence wrinkle, and it is about the wrapper rather than the libraries. To reduce license-header hell, the small amount of wrapper functions provided by libav.js are all released under the so-called 0-clause BSD license, which does not require that the license text itself appear in derivative works. Built libraries have their correct license headers.
So the design is deliberate: the wrapper code is permissively licensed so that using it does not drag notices into your own source, and the compiled libraries keep their own headers so that the LGPL obligations are visible where they apply.
The readme calls this a ruthless commitment to correct licensing, and for a project that puts an LGPL library into a browser, where static distribution is the norm, that is the right thing to worry about. It is also the thing that distinguishes libav.js from a project that simply compiles FFmpeg to WebAssembly and says nothing.
Loading it in a page, and the worker problem
The usage documentation opens with the practical question of which files to include, and the answer is more involved than a single script tag.
libav.js builds are available on GitHub releases and in NPM, and you include dist/libav-version-variant.js. The simplest way to use it is to include it from a CDN, and the example is a complete page that fetches an Opus file, writes it into the virtual filesystem, opens a demuxer, initialises a decoder, reads packets and decodes frames:
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@libav.js/[email protected]/dist/libav-6.10.9.0-default.js"></script>
<script type="text/javascript">(async function() {
const libav = await LibAV.LibAV({noworker: true});
await libav.writeFile("tmp.opus", new Uint8Array(
await (await fetch("exa.opus")).arrayBuffer()
));
const [fmt_ctx, [stream]] = await libav.ff_init_demuxer_file("tmp.opus");The noworker: true option in that snippet is the whole CDN caveat in one argument. libav.js uses Web Workers by default, and Web Workers cannot be loaded from a different origin, so if you load it from a CDN you must disable its own loading of workers. Which means the readme only recommends the CDN route if you are already in a worker and so do not need sub-workers.
The API shape is low-level and FFmpeg-shaped, which is the point. ff_init_demuxer_file returns a format context and the stream list, ff_init_decoder takes a codec id and the codec parameters, ff_read_multi reads packets and ff_decode_multi decodes them. There is no friendly layer over this; API.md describes the libav.js-specific parts of the API and the rest is FFmpeg's own naming.
The factory function LibAV.LibAV returns a promise which resolves to a ready instance, and it is exposed as a global variable named LibAV for all API access, with LibAV as the default export if you import it as a module. For unusual loading situations you can set the LibAV global before importing, and in particular, if the base directory cannot be detected you must set LibAV.base, which does not need to be a full URL but should be if you are loading from another origin.
Which files you need, and the three build targets
The file selection section is the part that trips people up, because the number of files is larger than expected and each has a distinct role.
You need the main entry file and at least one target, for a minimum of three files, though the readme says you should probably include several others. The main entry file is named libav-version-variant.js and loads a target based on the environment it is loaded in and the options used to load it. The debug version is libav-version-variant.dbg.js, which can be used in place of the original but is not required, and if you are using ES6 modules you use mjs in place of js.
There are three targets.
The asm.js target is named libav-version-variant.asm.js, and the note is that no modern browser excludes support for WebAssembly, so this is probably not necessary.
Plain WebAssembly is named libav-version-variant.wasm.js and libav-version-variant.wasm.wasm, and is used in most situations.
Threaded WebAssembly is named libav-version-variant.thr.js, .thr.wasm and .thr.worker.js, and is used only when threading is supported by the browser and yesthreads is set.
At a minimum it is usually sufficient to include only the .js, .wasm.js and .wasm.wasm files, and to include threads you must also include .thr.js and .thr.wasm. It is harmless to include all of them, because users will not download all of them, only the ones they use.
The Node.js situation is worth being blunt about. It is also possible to use libav.js from Node.js, though this is not a good idea, since you can presumably use a native version of FFmpeg's libraries. The Node interface is only provided for internal testing.
That sentence is the whole recommendation. If you are on Node, you want the native libraries, and libav.js is not for you.
The npm package variants and the emscripten build
The package.json shows a design decision that is unusual and useful: the package exposes per-codec entry points rather than one bundle with everything in it.
There is the root export, the default export, a default-cli export for the command line tools, and then codec-specific exports: opus, opus-af, flac, flac-af and wav, and the list continues past what the excerpt shows. Each export maps to an import path of the .mjs form, a default of the .js form and a types entry of libav.types.d.ts, so the same package gives you a tree-shakeable choice of which codecs you bundle.
The af suffix appears to be the audio filter variant, and the -cli suffix is the command line build. The split is what makes it possible to ship a small bundle: an application that only decodes Opus does not have to carry the FLAC, WAV or the CLI code.
The Makefile shows how the builds are produced, and it is worth noting that the file itself warns that it is generated by m4, so you should be editing the .m4 version rather than the generated Makefile. The FFmpeg version is pinned as major 9 and minor revision 0, giving FFMPEG_VERSION 9.0, and the list of previously tested versions runs back through 8.1, 7.1.5, 6.1.6, 5.1.10 and 4.4.8. The library version is composed from the libav.js base of 6.10 and the FFmpeg version, with a short version used for filenames.
The emscripten flags are the interesting part of the build. The optimisation is -Oz, there is a one megabyte stack, ASYNCIFY is enabled with an explicit list of the four functions that need it, and initial memory is 25165824 bytes with memory growth allowed. The ASYNCIFY list is the mechanism that makes a blocking C library callable from JavaScript, since the wasm call has to yield back to the event loop at points where the C code would otherwise block, and those four functions are where that happens.
There is also a set of exported runtime methods that differ between the threaded and non-threaded builds, which is how the same source can be built either way.
Editorial conclusion
libav.js is a good fit for a browser application that has to decode or transcode media without a server, because it gives you the real FFmpeg libraries rather than a JavaScript reimplementation, and the per-codec variants let you ship only the codec you need. It is a poor fit if you need a full ffmpeg command line in the browser, since the CLI builds are present but the documented API is the low-level one, and a poor fit for Node.js, where the readme says plainly that the Node interface exists only for internal testing. Before adopting, read CONFIG.md to understand how the variants are built, and check the licence terms, because the FFmpeg libraries are LGPL and you must provide sources if you distribute the library.
Frequently asked questions
What is libav.js?
It is a compilation of the FFmpeg libraries, libavformat, libavcodec, libavfilter, libavutil and libswresample, for WebAssembly and asm.js and thus the web, along with the ffmpeg and ffprobe command line tools. It is compiled via emscripten and provides low-level encoding, decoding, muxing, demuxing and filtering in the browser.
What licence does libav.js use?
The wrapper functions are released under the 0-clause BSD licence so the licence text does not need to appear in derivative works, while the built FFmpeg libraries keep their own headers. Because FFmpeg is LGPL, distributing the library requires providing the sources, and those are included in the sources/ directory of the compiled version.
Which files do I need to include to use libav.js?
You need the main entry file libav-version-variant.js and at least one target, so three files at minimum, and usually the .js, .wasm.js and .wasm.wasm files are enough. Adding threads requires the .thr.js and .thr.wasm files, and .dbg.js is only needed for the debug build.
Why does the CDN example pass noworker to LibAV?
libav.js uses Web Workers by default, and Web Workers cannot be loaded from a different origin, so loading from a CDN requires disabling its own worker loading. The readme only recommends the CDN route if you are already inside a worker and so do not need sub-workers.
Can I use libav.js from Node.js?
It is possible, but the readme says it is not a good idea since you can presumably use a native version of FFmpeg's libraries, and that the Node interface is only provided for internal testing.
What are the npm package variants for?
The package exposes per-codec entry points such as opus, opus-af, flac, flac-af and wav, alongside the default and default-cli exports, each with ES module, default and types paths, so an application can bundle only the codecs and the command line tools it actually uses.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/yahweasel-libav-js)Was this useful?