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
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' });
Failure cases worth handling
- The CDN refuses a cross-origin read. No amount of wasm helps, so say so and offer the tracks separately.
- WebAssembly is missing or the core fails to load. Same fallback, different message.
- The post was uploaded without sound. There is no audio file in the playlist, so label the sizes "no sound" instead of pretending.
- 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)