<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Fei Y</title>
    <description>The latest articles on DEV Community by Fei Y (@feiy_redpanda).</description>
    <link>https://dev.to/feiy_redpanda</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4129411%2F82ca143d-7ffb-4cad-951d-370251b70c5d.jpeg</url>
      <title>DEV Community: Fei Y</title>
      <link>https://dev.to/feiy_redpanda</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/feiy_redpanda"/>
    <language>en</language>
    <item>
      <title>Best In-Browser Video Compressor: We Tested 4 That Never Upload Your Video</title>
      <dc:creator>Fei Y</dc:creator>
      <pubDate>Fri, 09 Oct 2026 06:14:30 +0000</pubDate>
      <link>https://dev.to/feiy_redpanda/best-in-browser-video-compressor-we-tested-4-that-never-upload-your-video-4d6i</link>
      <guid>https://dev.to/feiy_redpanda/best-in-browser-video-compressor-we-tested-4-that-never-upload-your-video-4d6i</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.redpandacompress.com/blog/best-in-browser-video-compressor/" rel="noopener noreferrer"&gt;RedPanda Compress blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A growing number of video compressors promise that your file never leaves your computer. Instead of uploading the video to a server, they compress it inside the browser tab. That's good for privacy and it means no upload wait, but "runs in your browser" covers some very different technology. We ran the same video through the four in-browser compressors that actually get search traffic and measured what came back.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I build RedPanda Compress, which is one of the four tools tested. It went through the same procedure as everyone else, and I report where it lost.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The short answer
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best quality for a given file size: RedPanda Compress.&lt;/strong&gt; It hit our 50 MB target exactly with a VMAF score of 84.0. The only tool that matched that quality needed a file 77% larger. The catch: it took 3 min 42 s, five to six times longer than the others.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fastest with a size target: VidShift.&lt;/strong&gt; 44 seconds to a 50 MB file, but at that size it scored lowest of the group (VMAF 74.1), a difference you can see.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fastest overall, best looking presets: OpenReplay.&lt;/strong&gt; 35 seconds and good quality, but no size control: its smallest setting still produced an 88 MB file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kommodo&lt;/strong&gt; is just as fast and has resolution options, but it also has no size control, and its presets compressed less than they promise.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Two ways to compress video in a browser
&lt;/h2&gt;

&lt;p&gt;All four tools keep the video on your device, but they encode it in one of two ways, and that choice explains almost every result below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hardware encoding (WebCodecs).&lt;/strong&gt; VidShift, OpenReplay and Kommodo use WebCodecs, a browser feature that hands the video to the hardware encoder built into your computer's graphics chip. Those encoders are designed for real-time jobs like video calls and screen recording. They are very fast, but they make simpler decisions about where to spend bits, so at the same file size they lose more detail. They also depend on what your browser and hardware support.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Software encoding (FFmpeg compiled to WebAssembly).&lt;/strong&gt; RedPanda Compress runs FFmpeg with the x264 encoder inside the page, on your CPU. x264 is the same encoder used by most server-side compressors. It analyses each frame much more thoroughly, which costs time but gets more quality out of every megabyte. On a desktop computer it works the same in any modern browser because it doesn't depend on the graphics chip. We explain the engine in more detail in &lt;a href="https://www.redpandacompress.com/blog/how-in-browser-audio-converter-works/" rel="noopener noreferrer"&gt;how an in-browser converter actually works&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A note on RedPanda Compress on phones.&lt;/strong&gt; Everything in this test ran on a desktop computer, where RedPanda Compress always uses the software encoder described above. On phones and tablets it also uses WebCodecs hardware encoding when the device supports it, because software x264 on a phone's CPU would be slow and hard on the battery. It always does this for large files (200 MB and up) and for many smaller ones too, and if the hardware path fails on a particular file, it falls back to the software encoder. So on mobile, the speed-versus-quality trade-off in this article works in favour of speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  How we tested
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Test file:&lt;/strong&gt; Big Buck Bunny (Blender Foundation, CC-BY), the same file as our &lt;a href="https://www.redpandacompress.com/blog/best-online-video-compressor/" rel="noopener noreferrer"&gt;online video compressor comparison&lt;/a&gt;: 1280×720 at 24 fps, 9 min 56 s, H.264 + AAC, 158.6 MB. It's openly licensed, so you can repeat the test yourself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Goal:&lt;/strong&gt; about 50 MB, roughly a third of the original. If a tool accepts a target size, we typed 50 MB. If it only has presets, we ran its default and the preset closest to 50 MB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tools:&lt;/strong&gt; in-browser compressors with real search traffic (US organic visits per month, October 2026): VidShift (~18.7K), OpenReplay (~12.1K), Kommodo (~3.7K) and RedPanda Compress. The rest of the category gets close to zero visits, so we left it out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time:&lt;/strong&gt; from clicking the compress button to the finished file being ready.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quality:&lt;/strong&gt; VMAF, Netflix's open-source perceptual quality metric (0–100), comparing every frame of the output with the original. Roughly, a 2-point gap is hard to see and 6 or more is clearly visible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upload check:&lt;/strong&gt; we logged the size of every request each page made while compressing. None of the four sent anything over 1 MB, so all four do what they say: the video stays on your device.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Machine:&lt;/strong&gt; Apple M2 MacBook (8 cores, 16 GB), Chrome 154, plugged in, one tool at a time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool and setting&lt;/th&gt;
&lt;th&gt;Engine&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;VMAF&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;RedPanda Compress&lt;/strong&gt; — target 50 MB&lt;/td&gt;
&lt;td&gt;x264 (CPU)&lt;/td&gt;
&lt;td&gt;3 min 42 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;49.9 MB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;84.0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;VidShift&lt;/strong&gt; — custom 50 MB&lt;/td&gt;
&lt;td&gt;WebCodecs (GPU)&lt;/td&gt;
&lt;td&gt;44 s&lt;/td&gt;
&lt;td&gt;50.3 MB&lt;/td&gt;
&lt;td&gt;74.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Kommodo&lt;/strong&gt; — Low, smallest preset&lt;/td&gt;
&lt;td&gt;WebCodecs (GPU)&lt;/td&gt;
&lt;td&gt;35 s&lt;/td&gt;
&lt;td&gt;62.7 MB&lt;/td&gt;
&lt;td&gt;78.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;OpenReplay&lt;/strong&gt; — Low, smallest preset&lt;/td&gt;
&lt;td&gt;WebCodecs (GPU)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;35 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;88.4 MB&lt;/td&gt;
&lt;td&gt;85.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Kommodo&lt;/strong&gt; — Medium, default&lt;/td&gt;
&lt;td&gt;WebCodecs (GPU)&lt;/td&gt;
&lt;td&gt;40 s&lt;/td&gt;
&lt;td&gt;115.4 MB&lt;/td&gt;
&lt;td&gt;90.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;OpenReplay&lt;/strong&gt; — Medium, default&lt;/td&gt;
&lt;td&gt;WebCodecs (GPU)&lt;/td&gt;
&lt;td&gt;35 s&lt;/td&gt;
&lt;td&gt;134.4 MB&lt;/td&gt;
&lt;td&gt;91.7&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All outputs stayed at 1280×720 and 24 fps and played as standard H.264 MP4s, with no watermarks. We ran RedPanda twice in different browser profiles (3 min 42 s and about 3 min 45 s, same size and score) and VidShift twice (42 and 46 s).&lt;/p&gt;

&lt;p&gt;The table reads two ways. Sorted by speed, the hardware encoders win easily: 35–44 seconds against almost four minutes. Sorted by what you get per megabyte, it flips. OpenReplay needed 88 MB to slightly beat the quality RedPanda delivered at 50 MB, and VidShift, at the same 50 MB, was 10 VMAF points behind.&lt;/p&gt;

&lt;p&gt;Averages can hide the bad moments, so we also counted the frames scoring below 70, roughly where damage becomes easy to spot. RedPanda: 8.8% of frames. OpenReplay (Low, 88 MB): 8.5%. Kommodo (Low): 21%. VidShift: 32%, about one frame in three.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff59u8u7zfhbu51lwcccs.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff59u8u7zfhbu51lwcccs.jpg" alt="Zoomed crop of the same Big Buck Bunny frame from RedPanda Compress, VidShift, Kommodo and OpenReplay" width="800" height="689"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The same moment (2:37) from each output, zoomed in. We picked the frame where the RedPanda–VidShift quality gap is at its median, so this is a typical frame, not a worst case.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuwbf5jactmyms1riap7d.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuwbf5jactmyms1riap7d.jpg" alt="Blocky grass and leaves in VidShift output next to RedPanda Compress at the same file size" width="800" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One frame in ten from VidShift looks like this or worse. The same moment (1:42) from RedPanda Compress for comparison; both files are about 50 MB.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the results look like this
&lt;/h2&gt;

&lt;p&gt;To understand the gaps, we read each tool's own page code to see what it asks the encoder for, and we looked inside every output file to see how the encoder actually built it. Three things explain almost everything.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;RedPanda&lt;/th&gt;
&lt;th&gt;VidShift&lt;/th&gt;
&lt;th&gt;Kommodo&lt;/th&gt;
&lt;th&gt;OpenReplay&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Encoder&lt;/td&gt;
&lt;td&gt;x264, software (CPU)&lt;/td&gt;
&lt;td&gt;Hardware, via WebCodecs&lt;/td&gt;
&lt;td&gt;Hardware, via WebCodecs&lt;/td&gt;
&lt;td&gt;Hardware, via WebCodecs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How the bitrate is chosen&lt;/td&gt;
&lt;td&gt;From the size you type&lt;/td&gt;
&lt;td&gt;From the size you type&lt;/td&gt;
&lt;td&gt;Fixed per preset and resolution&lt;/td&gt;
&lt;td&gt;Fixed per preset and resolution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B-frames (share of frames)&lt;/td&gt;
&lt;td&gt;67%&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full keyframe every&lt;/td&gt;
&lt;td&gt;~5.6 s, placed at scene cuts&lt;/td&gt;
&lt;td&gt;5 s&lt;/td&gt;
&lt;td&gt;2 s&lt;/td&gt;
&lt;td&gt;2 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frames encoded per second&lt;/td&gt;
&lt;td&gt;~65&lt;/td&gt;
&lt;td&gt;~325&lt;/td&gt;
&lt;td&gt;~410&lt;/td&gt;
&lt;td&gt;~410&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Frame structure measured on the first two minutes of each ~50–90 MB output. Frames per second = 14,315 frames ÷ total time.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The hardware encoders skip B-frames
&lt;/h3&gt;

&lt;p&gt;Most frames in a compressed video are stored as differences from other frames. P-frames borrow only from frames before them; B-frames can borrow from frames before &lt;em&gt;and&lt;/em&gt; after, which usually makes them far cheaper. In RedPanda's file, two out of three frames are B-frames, averaging about 1 KB each. The three hardware-encoded files contain no B-frames at all, so every frame is a P-frame costing 2.6–4.2 KB. With the same 50 MB budget, the software encoder has more bits left over for the frames that matter.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Keyframes are expensive, and two tools insert one every two seconds
&lt;/h3&gt;

&lt;p&gt;A keyframe is a complete picture that doesn't borrow from anything, so it's many times bigger than the frames around it. Kommodo and OpenReplay start a new keyframe every 2 seconds, which takes about 20% of their bit budget. RedPanda's encoder places keyframes mostly where the scene actually changes, about every 5.6 seconds on this film, and spends around 17% on them.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Software encoding plans ahead; hardware encoding works frame by frame
&lt;/h3&gt;

&lt;p&gt;x264 looks 20 frames ahead before deciding how to spend bits, and gives more detail to parts of the picture that later frames will reuse, so the effort is spent once and repaid many times. It also tries several ways of predicting each block before picking one. The video encoders built into graphics chips are designed mainly for real-time jobs like video calls and screen recording, where there's no time to look ahead. That makes them five to six times faster here, but at the same size they keep less detail, and the loss concentrates in busy scenes like grass and leaves.&lt;/p&gt;

&lt;p&gt;The flip side explains RedPanda's speed. All that analysis is real work, and in a browser it runs as WebAssembly on your CPU. That's much faster than it used to be, with multiple threads and SIMD instructions, but it still can't use the hand-tuned assembly code x264 relies on in desktop apps, and decoding the input happens in software too. The hardware tools can hand both decoding and encoding to the chip's dedicated video circuits.&lt;/p&gt;

&lt;h2&gt;
  
  
  How each tool works, and where its problems come from
&lt;/h2&gt;

&lt;h3&gt;
  
  
  RedPanda Compress
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt; on a desktop computer, it runs FFmpeg, compiled to WebAssembly, in your browser tab and encodes with x264. You type a size, and it works out the bitrate that fits: (50 MB − the audio) ÷ the video's length gave 541 kbps here. It then asks x264 for that bitrate at a steady rate, with a few seconds of buffer so busy scenes can borrow from quiet ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strengths:&lt;/strong&gt; the best quality per megabyte in this test, and the size you type is the size you get (49.9 MB for a 50 MB target), which matters when an email, app or upload form has a hard limit. On desktop it doesn't need WebCodecs or a particular graphics chip and accepts files up to 8 GB, and on phones it switches to hardware encoding where the device supports it. The output is always plain H.264 MP4.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Weaknesses, and why:&lt;/strong&gt; it's the slowest by far, 3 min 42 s on this file, about 2.7× faster than real time. That's the cost of software encoding described above: x264's analysis running as WebAssembly on the CPU. It also keeps the CPU busy for the whole job, so a laptop's fans will spin up, and older or lower-power computers will take noticeably longer. (On phones, where this would hurt most, it uses the hardware encoder instead, as described above.)&lt;/p&gt;

&lt;h3&gt;
  
  
  VidShift
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt; it uses Mediabunny, an open-source library, to drive the browser's WebCodecs hardware encoder. For a custom size, it computes a bitrate aimed at 97% of your target, as a safety margin, and asks the hardware encoder for a &lt;em&gt;constant&lt;/em&gt; bitrate. Its presets are fractions of the source's own bitrate (70%, 40% or 20%).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strengths:&lt;/strong&gt; the only hardware-based tool here that takes a target size, and it lands close (50.3 MB for 50 MB). It's fast (44 s) and the most-visited tool in this group.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Weaknesses, and why:&lt;/strong&gt; at the same 50 MB it scored lowest (VMAF 74.1), and about one frame in three fell below 70. Constant bitrate is what makes the size predictable, but it also means a busy scene gets no more bits than a still one. Combined with the hardware encoder's lack of B-frames and look-ahead, the busy scenes are where it breaks up (see the second image above). It also came out slightly over target, by about 0.5%, so for a hard limit, type a little less than the limit.&lt;/p&gt;

&lt;h3&gt;
  
  
  OpenReplay
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt; WebCodecs hardware encoding in a background worker. Its quality presets don't look at file size at all. The bitrate is a fixed formula of the output resolution: width × height × 30 × 0.045 for Low, 0.075 for Medium and 0.12 for High, capped at 95% of the source's bitrate. At 720p that's 1.24 Mbps for Low and, after the cap, 2.02 Mbps for Medium. It asks for that as a &lt;em&gt;variable&lt;/em&gt; bitrate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strengths:&lt;/strong&gt; the fastest tool (35 s) and the best-looking files, because its presets are generous. Medium scored 91.7.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Weaknesses, and why:&lt;/strong&gt; since the bitrate comes from the resolution, not from a size you choose, it can't target a size. Low will always be about 1.2 Mbps at 720p, so a 10-minute video can't go below ~88 MB unless you lower the resolution. On our file, Medium asked for 95% of the source's bitrate, so it barely compresses. Nearly all of the 15% it saved came from the hardware encoder delivering about 20% less than it was asked for (Kommodo's files came in 20% under in exactly the same way). The page's size estimate, about 153 MB, matches what Medium requests rather than what the encoder delivered, and it didn't change when we switched quality levels. Separately, the audio came out at about 189 kbps, higher than both the source (~130 kbps) and the 128 kbps its code requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kommodo
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt; it also uses Mediabunny and the WebCodecs hardware encoder (the page shows "Encoder: AVC (Hardware)"). Each preset is a fixed bitrate for 1080p (8, 4 or 2 Mbps for High, Medium and Low), scaled down by pixel count for smaller resolutions: 1.78 Mbps for Medium and 0.89 Mbps for Low at 720p. It's capped just below the source's own bitrate and requested as a variable bitrate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strengths:&lt;/strong&gt; fast (35–40 s), simple, and it lets you pick a lower resolution, the most effective way to shrink a file if you can accept fewer pixels.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Weaknesses, and why:&lt;/strong&gt; no target size, and the labels ("~50%", "~70% compression") are fixed text that doesn't depend on your video. Because the bitrate is fixed per resolution, the saving depends entirely on how big the source already was. Our 720p source was 2.1 Mbps, so Medium's 1.78 Mbps saved only 27%, and Low saved about 60%. On a typical phone video, recorded at a much higher bitrate, the same presets would save more than the labels say. Like OpenReplay, the hardware encoder delivered about 20% under the requested bitrate, and a keyframe every 2 seconds eats into the budget, which is why Low's 62.7 MB looked worse (78.5) than RedPanda's 50 MB. Its FAQ also says it doesn't work on iPhone, because Safari's WebCodecs support is limited.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which one should you use?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;If you need…&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;To get under a hard size limit (email, Discord, a form) with the best quality&lt;/td&gt;
&lt;td&gt;RedPanda Compress&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A size target, and speed matters more than detail&lt;/td&gt;
&lt;td&gt;VidShift&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A modest reduction with almost no visible loss, fast&lt;/td&gt;
&lt;td&gt;OpenReplay (Medium) or Kommodo (Medium)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For short clips the speed gap matters less in absolute terms: RedPanda processed this 720p film at about 2.7× real time, so a one-minute 720p clip takes around 20–25 seconds on a machine like ours, against a few seconds for the hardware tools. It matters most with long recordings. If the file is going somewhere with a size limit, though, quality per megabyte is the number that decides how good it looks when it gets there.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can I compress a video online without uploading it?
&lt;/h3&gt;

&lt;p&gt;Yes. All four tools in this test compress the video inside your browser tab, and we confirmed none of them sent the file anywhere. You can check any tool yourself with your browser's developer tools; we show how in &lt;a href="https://www.redpandacompress.com/blog/are-online-file-converters-safe/" rel="noopener noreferrer"&gt;Are Online File Converters Safe?&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why are in-browser compressors so different in speed?
&lt;/h3&gt;

&lt;p&gt;It comes down to the encoder. Tools built on WebCodecs use the hardware video encoder in your computer's graphics chip, which is fast but less efficient. Tools built on FFmpeg/WebAssembly run a software encoder on your CPU, which is slower but squeezes more quality into each megabyte.&lt;/p&gt;

&lt;h3&gt;
  
  
  Are in-browser compressors worse than server-based ones?
&lt;/h3&gt;

&lt;p&gt;Not necessarily. In our &lt;a href="https://www.redpandacompress.com/blog/best-online-video-compressor/" rel="noopener noreferrer"&gt;comparison of upload-based compressors&lt;/a&gt;, the best server tool scored 85.3 at about 48 MB, close to RedPanda's 84.0 at 50 MB, but it required uploading the whole file first. The hardware-encoding browser tools trade some quality for speed instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Will these results be the same on my computer?
&lt;/h3&gt;

&lt;p&gt;The quality and size numbers should be very close, because they come from the encoders' settings. The times will vary: software encoding scales with your CPU, and hardware encoding depends on your graphics chip and browser. On an older laptop, expect all of them to be slower, and the software encoder more so.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bottom line
&lt;/h2&gt;

&lt;p&gt;Every tool here keeps your video private. The real choice is between speed and quality per megabyte. If you need a file to fit under a limit and still look good, &lt;a href="https://www.redpandacompress.com/" rel="noopener noreferrer"&gt;try RedPanda Compress&lt;/a&gt;: type the size you need and it compresses the video on your own device, with no upload, no watermark, and a standard H.264 MP4 at the end. If you just want a quick, moderate reduction, the hardware-based tools will get you there in under a minute.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>webassembly</category>
      <category>javascript</category>
      <category>performance</category>
    </item>
    <item>
      <title>Handling Files Over 2GB with WebAssembly in the Browser</title>
      <dc:creator>Fei Y</dc:creator>
      <pubDate>Fri, 18 Sep 2026 01:51:05 +0000</pubDate>
      <link>https://dev.to/feiy_redpanda/handling-files-over-2gb-with-webassembly-in-the-browser-1cmm</link>
      <guid>https://dev.to/feiy_redpanda/handling-files-over-2gb-with-webassembly-in-the-browser-1cmm</guid>
      <description>&lt;p&gt;At &lt;a href="https://www.redpandacompress.com" rel="noopener noreferrer"&gt;RedPandaCompress&lt;/a&gt; people routinely drop 4-8GB video files into the browser and expect them to compress or convert without ever touching a server. No upload, no queue, no "your file is processing, check back later" email. Everything happens client-side, in WebAssembly.&lt;/p&gt;

&lt;p&gt;The catch: WebAssembly was never really designed for that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The wall you hit first
&lt;/h2&gt;

&lt;p&gt;A wasm module's linear memory is one contiguous, growable &lt;code&gt;ArrayBuffer&lt;/code&gt;. In practice you hit trouble long before the theoretical 4GB (32-bit) ceiling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every growth step needs a &lt;strong&gt;new&lt;/strong&gt; buffer big enough to hold the old contents &lt;em&gt;plus&lt;/em&gt; the increase, allocated contiguously, before the old one is freed. That doubles your peak requirement right at the moment you're already low on headroom.&lt;/li&gt;
&lt;li&gt;Browsers cap single &lt;code&gt;ArrayBuffer&lt;/code&gt; allocations well under 4GB in practice, and you'll see it as a plain &lt;code&gt;RangeError: Array buffer allocation failed&lt;/code&gt; — not an out-of-memory crash, just a hard refusal.&lt;/li&gt;
&lt;li&gt;None of this is optional if your naive approach is "read the whole file, hand the bytes to the wasm module." A 3GB input file, loaded into a wasm-visible buffer, is already most of the way to that wall — before you've decoded a single frame.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the fix isn't "increase the memory limit." It's making sure the whole file never has to live in one contiguous wasm-addressable buffer in the first place — at any stage: input, processing, or output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Input: don't copy the file in, mount it
&lt;/h2&gt;

&lt;p&gt;The obvious approach — read the &lt;code&gt;File&lt;/code&gt; object into an &lt;code&gt;ArrayBuffer&lt;/code&gt; with &lt;code&gt;file.arrayBuffer()&lt;/code&gt; and write it into the wasm filesystem (MEMFS) — means the entire input now exists twice: once as a JS &lt;code&gt;ArrayBuffer&lt;/code&gt;, once copied into the wasm heap. For an 8GB file that's an 8GB tax before any real work starts.&lt;/p&gt;

&lt;p&gt;ffmpeg.wasm (and Emscripten's FS layer generally) supports mounting a filesystem backed directly by a &lt;code&gt;Blob&lt;/code&gt;/&lt;code&gt;File&lt;/code&gt;, instead of copying bytes into MEMFS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ffmpeg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createDir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ffmpeg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;FFFSType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WORKERFS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;blobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input.mp4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ffmpeg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-i&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/data/input.mp4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-c:v&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;copy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;output.mp4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ffmpeg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unmount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;WORKERFS reads lazily, straight from the browser's own &lt;code&gt;File&lt;/code&gt;/&lt;code&gt;Blob&lt;/code&gt; (which is typically backed by disk, not RAM) whenever ffmpeg actually asks for a byte range. The file is never copied into the wasm heap wholesale. This one change is what makes "8GB input" a non-event instead of an immediate wall.&lt;/p&gt;

&lt;h2&gt;
  
  
  Output: don't collect it in memory either
&lt;/h2&gt;

&lt;p&gt;The other direction has the same trap. If ffmpeg's output lands in wasm's MEMFS and you then read it out as a single &lt;code&gt;Uint8Array&lt;/code&gt; to build a &lt;code&gt;Blob&lt;/code&gt;, you've reintroduced the exact problem you just solved on the input side — the whole output now has to exist as one contiguous buffer before you can hand it back to the page.&lt;/p&gt;

&lt;p&gt;The fix is symmetric: stream it out in chunks instead of pulling one giant buffer at the end. Read the output progressively and assemble it as &lt;code&gt;Blob&lt;/code&gt; parts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;video/mp4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Chrome (and most modern browsers) can spill large &lt;code&gt;Blob&lt;/code&gt;s to disk rather than holding them entirely in RAM, so building the result as many small parts — instead of one &lt;code&gt;ArrayBuffer&lt;/code&gt; the size of the whole output — sidesteps the same allocation ceiling on the way out.&lt;/p&gt;

&lt;h2&gt;
  
  
  For genuinely huge jobs: bound the work, not just the memory
&lt;/h2&gt;

&lt;p&gt;Even with lazy input and streamed output, a single ffmpeg invocation over the &lt;em&gt;entire&lt;/em&gt; file still has to hold its own working state proportional to what it's processing. For very large compress jobs we split the source into bounded segments, run each one through its own short-lived ffmpeg pass, and concatenate the results — rather than asking one wasm instance to eat the whole file in one go. It's the same principle one level up: cap how much any single unit of work has to hold in memory at once, regardless of how large the total job is.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;There's no single trick that makes "&amp;gt;2GB in WebAssembly" work. It's the same discipline applied at every boundary: never let the &lt;em&gt;whole&lt;/em&gt; file — input, intermediate state, or output — become one contiguous thing that has to live entirely inside wasm-addressable memory at once. Mount instead of copy. Stream instead of collect. Segment instead of one giant pass.&lt;/p&gt;

&lt;p&gt;This is the first of what I expect will be a few posts on the weirder corners of shipping a real, no-upload media tool as a client-side wasm app — happy to go deeper on any of this if people are curious.&lt;/p&gt;

</description>
      <category>webassembly</category>
      <category>javascript</category>
      <category>ffmpeg</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
