DEV Community

Mossaddique Ahsan
Mossaddique Ahsan

Posted on

Joining Reddit's split video and audio in the browser with ffmpeg.wasm

I run KlipJar (klipjar.com, made by BeyondITL Inc.), a web tool that saves public Reddit, TikTok and other videos. This post is about one problem we had to solve: Reddit videos that download without sound.

Why Reddit videos are silent

A video on v.redd.it is published as a DASH playlist (DASHPlaylist.mpd) plus an HLS one. Each size is a picture-only MP4 named after its shorter side (CMAF_720.mp4 is 720×1280 for a portrait clip), and the sound is its own file, CMAF_AUDIO_128.mp4. In three posts we checked on 26 October 2026 the picture was H.264 and the sound AAC-LC, 48 kHz stereo, about 130 kbps. Reddit's player plays both together. A downloader that grabs one file gets a silent MP4.

Why do it in the browser

v.redd.it sends access-control-allow-origin: *, so a page can fetch() both files. Joining them on the user's device means our server never touches the bytes, and there is nothing to store.

The join is a remux, not a re-encode

Both streams already fit in MP4, so ffmpeg only rewrites the container:

ffmpeg -i CMAF_720.mp4 -i CMAF_AUDIO_128.mp4 -map 0:v -map 1:a -c copy out.mp4
Enter fullscreen mode Exit fullscreen mode

On a 12-second post that turned 2.47 MB of picture and 0.19 MB of sound into a 2.67 MB MP4.

In the page

We load the single-threaded @ffmpeg/core 0.12 through a dynamic import(), so its roughly 30 MB of wasm never touches first paint. The multi-threaded core needs SharedArrayBuffer, which would force COOP/COEP headers on the whole site and break the cross-origin CDN reads the tool depends on. The core and wasm are self-hosted build assets, wrapped with toBlobURL so they get explicit MIME types:

import { FFmpeg } from '@ffmpeg/ffmpeg';
import { toBlobURL } from '@ffmpeg/util';
import coreURL from '@ffmpeg/core?url';
import wasmURL from '@ffmpeg/core/wasm?url';

const ffmpeg = new FFmpeg();
await ffmpeg.load({
  coreURL: await toBlobURL(coreURL, 'text/javascript'),
  wasmURL: await toBlobURL(wasmURL, 'application/wasm'),
});

const bytes = async (blob) => new Uint8Array(await blob.arrayBuffer());
await ffmpeg.writeFile('v.mp4', await bytes(videoBlob));
await ffmpeg.writeFile('a.m4a', await bytes(audioBlob));
const code = await ffmpeg.exec([
  '-i', 'v.mp4', '-i', 'a.m4a',
  '-c', 'copy', '-map', '0:v:0', '-map', '1:a:0',
  '-movflags', '+faststart',
  'out.mp4',
]);
if (code !== 0) throw new Error(`ffmpeg exited ${code}`);
const out = await ffmpeg.readFile('out.mp4');
const mp4 = new Blob([out.buffer], { type: 'video/mp4' });
Enter fullscreen mode Exit fullscreen mode

Failure cases worth handling

  1. The CDN refuses a cross-origin read. No amount of wasm helps, so say so and offer the tracks separately.
  2. WebAssembly is missing or the core fails to load. Same fallback, different message.
  3. The post was uploaded without sound. There is no audio file in the playlist, so label the sizes "no sound" instead of pretending.
  4. The user closes the tab mid-join. Nothing is saved.

One gotcha

The public playlist doesn't always list every size Reddit stores. In our check, a CMAF_1080.mp4 existed for all three posts, but the playlists stopped at 720 (or 360). If you want the top size, probe for it.

A plain-English version for non-developers: https://klipjar.com/guides/download-reddit-video-with-sound

Top comments (0)