
Shipping One Media Toolkit as Both an Android App and a WASM Browser App
A media toolkit that builds from one repo into a Kotlin Android app and a browser app running libwebp in WASM, sharing a hand-written animated-WebP demuxer.
Embeddy is a swiss-army knife for embeddable media. It converts GIFs, video, and animated WebP into WebP or GIF, compresses images with WASM codecs via Squoosh, inspects OG meta tags and EXIF, and uploads files anonymously through 0x0.st. All the processing happens client-side, so your files never leave your device. You can use it in the browser at embeddy.link or install the Android app.
Two targets, one repo
The design goal was one codebase, two shipping surfaces. Embeddy builds from a single repo into a Kotlin/Compose Android app, written against Kotlin 2, Jetpack Compose, and Material 3, and a browser app that runs libwebp compiled to WebAssembly. The browser build offers arm64-v8a, armeabi-v7a, x86_64, and universal APKs on the download page, so the same project covers modern phones, older 32-bit ARM devices, emulators, and Chromebooks.
Shipping twice from one repo is the easy sentence to write. The hard part is the format handling underneath, because animated WebP is genuinely awkward to work with.
The animated-WebP problem
Here is the wrinkle that drove the interesting engineering. Both builds carry a hand-written animated-WebP demuxer, and they have to, because the usual tools cannot do the job. FFmpeg cannot demux the animated-WebP format. Firefox has no ImageDecoder to lean on. So instead of getting frames handed to you, you have to take the animation apart yourself.
The approach Embeddy uses is to re-wrap each frame as a still image. Rather than relying on a decoder that understands the animation as a sequence, it pulls frames out one at a time and presents each as a standalone still, which downstream code already knows how to handle. That single demuxer is shared across the Android and WASM builds, so the trickiest piece of logic is written once and reused on both surfaces.
The result is a tool that does real codec work with no server, no upload, and no install if you do not want one. It is a small demonstration that WASM plus a bit of hand-written format code can put desktop-class media conversion directly in the browser.
Have something that needs building, or stabilizing?
These notes are the work log. The paid work runs through Moonshine Labs, my product and engineering studio. Tell us what you're building, or grab a call.
Work with us →