Compress videos in the browser before upload
browser-video-compression is an MIT-licensed JavaScript library. When a user picks a video, it downscales the video to a sensible resolution and re-encodes it to H.264 MP4 right in the browser, then hands it to your existing upload flow. Built for course platforms, form builders, recruiting tools and any site where users upload videos.
Faster uploads, fewer failures
A 1080p phone recording compressed to 720p H.264 is typically 60%–90% smaller. Uploads finish sooner and fail less often on mobile networks.
Never blocks an upload
If the browser lacks support, the file cannot be decoded, or the result would not be smaller, the library returns the original file with a reason, and the upload continues as usual.
No server, no ffmpeg.wasm
It uses the browser's built-in WebCodecs hardware encoders: no tens of megabytes of WebAssembly to download, and no cross-origin isolation headers to configure.
Install
npm install github:karuha-m/browser-video-compressionShips as an ES module with TypeScript type definitions and uses mediabunny to read and write media files.
Quick start
Call compressVideo after the user selects a file, then pass result.file to your existing upload code.
import { compressVideo } from 'browser-video-compression';
input.addEventListener('change', async (event) => {
const original = event.target.files[0];
const result = await compressVideo(original, {
maxWidthOrHeight: 1280, // 720p
skipBelowMB: 10, // small files are uploaded as they are
onProgress: (p) => console.log(Math.round(p * 100) + '%'),
});
// result.file is the compressed file, or the original when
// compression was skipped (see result.skipReason)
const body = new FormData();
body.append('video', result.file);
await fetch('/upload', { method: 'POST', body });
});With an upload library
result.file is a standard File object, so it works with tus, Uppy, S3 presigned URLs, and Mux or Cloudflare Stream direct uploads.
import * as tus from 'tus-js-client';
const { file } = await compressVideo(original);
new tus.Upload(file, { endpoint: '/files/' }).start();Preflight checks
inspectVideo reads container metadata without decoding the whole file, so you can check duration, resolution and codecs before uploading.
import { inspectVideo } from 'browser-video-compression';
const info = await inspectVideo(file);
// { duration, width, height, videoCodec, audioCodec, bitrate, canDecode, ... }
if (info.duration > 600) alert('Please keep videos under 10 minutes.');Cancelling
const controller = new AbortController();
cancelButton.onclick = () => controller.abort();
const result = await compressVideo(file, { signal: controller.signal });Options
| Options | Default | Notes |
|---|---|---|
| maxWidthOrHeight | 1280 | Maximum length of the output's longest edge in pixels. Smaller videos keep their original size. |
| skipBelowMB | — | Files at or below this size whose dimensions already fit are returned unchanged. |
| quality | 'medium' | Bitrate preset: low, medium or high, scaled to the output resolution. |
| videoBitrate | — | Video bitrate in bits per second. Overrides quality. |
| audioBitrate | 128000 | Used only when audio must be re-encoded; audio that can be copied is kept untouched. |
| maxFrameRate | — | Output frame rate cap in Hz. Defaults to the source frame rate. |
| format | 'mp4' | mp4 produces H.264 + AAC; webm produces VP9/VP8 + Opus. |
| onProgress | — | Progress callback with values from 0 to 1. |
| signal | — | An AbortSignal used to cancel compression. |
| stallTimeoutMs | 90000 | How long the encoder may make no progress while the page is visible before the original file is returned. |
The result contains file, compressed, skipReason, originalSize, size, width, height and duration. The promise rejects only when the caller aborts; otherwise, when compressed is false, skipReason explains why the original file was returned:
already-smallThe file is already below skipBelowMB and within the size limit.unsupported-browserThis browser has no usable WebCodecs encoder.unsupported-inputThe file has no video track, or this browser cannot decode its video.not-smallerThe compressed result was not smaller than the original.failedCompression failed; see the error field for details.
Browser Support
| Chrome / Edge 94+ | Supported (desktop and Android) |
| Safari 16.4+ | Supported (macOS and iOS) |
| Firefox 130+ | Supported; register the WebAssembly AAC encoder for non-AAC audio |
| Older browsers | Original file returned; upload unaffected |
Use Cases
Online course and training platforms
Students submit assignment videos and instructors upload lecture recordings. Compressed uploads finish faster and lower storage and delivery costs.
Forms and surveys
Let respondents attach video explanations or product demos without hitting upload size limits.
Recruiting and video interviews
Self-introductions recorded on phones are large; compressed, they upload reliably even on mobile networks.
E-commerce and marketplaces
Normalize resolution and codec before sellers upload product videos, so the backend no longer queues every file for transcoding.
To see the compression results first, try VideoKit's online video compressor, which also processes files in the browser.
Frequently Asked Questions
Where does compression happen? Is the video sent to a third party?
Compression runs entirely in the user's browser, and the library itself makes no network requests. Your page uploads the compressed file to your own server or storage, exactly as before.
How is it different from ffmpeg.wasm?
ffmpeg.wasm downloads about 30 MB of WebAssembly, its multithreaded build also requires cross-origin isolation headers, and it encodes in software on the CPU. This library uses the browser's built-in WebCodecs encoders, hardware-accelerated on most devices, with no extra download or header configuration.
What happens when the browser is not supported?
compressVideo returns the original file with compressed set to false and skipReason set to unsupported-browser. Your upload flow needs no extra branch.
What input formats are supported?
Common containers such as MP4, MOV, WebM and MKV are supported. The video codec must be decodable by the current browser: H.264 works everywhere; HEVC, VP9 and AV1 depend on the device.
How large can files be?
The compressed result is held in memory until it is returned. Desktop browsers handle files of several gigabytes; on mobile devices, for files above 2 GB, lower maxWidthOrHeight or upload the original.
Can I use it with React, Vue or in a Web Worker?
Yes. The library is framework-agnostic: input and output are standard File objects, and the APIs it uses are also available in Web Workers.
What is the license?
MIT. It can be used in commercial projects; the only condition is keeping the copyright notice.
Integration help for teams
VideoKit's online tools complete more than 100,000 video jobs in users' browsers every month. If your team plans to add in-browser compression to its upload flow, leave your details: we will learn about your upload volume and use case, and test compression results and device compatibility on sample videos you provide.