Compress Video in Your Browser
Build a browser-based video compressor that keeps files on your device.
Introduction
30 Second Summary
A video that looks fine on your phone can still be too large to email. Many compression sites make you hand over the file before they shrink it.
In this project, you will build a browser-based video compressor powered by FFmpeg running through WebAssembly. You will prove its privacy in the browser's network panel before deploying it for future clips.
What You'll Build
Choose a phone recording on your deployed page, watch the compression progress inside the tab, then play the smaller result beside the original with both file sizes visible.
By the end of this project, you'll have:
- A private compression flow that turns a large video into a smaller file without uploading it to a server.
- A side-by-side comparison where you can play the original video beside the compressed result and compare their file sizes.
- A quality control that lets you encode the same clip at different settings and see how each setting changes the output size.
- A network panel privacy check that proves no request leaves your machine while the video is being compressed.
- Secret Mission: An optional challenge to push your skills further.
Are there any prerequisites?
You need a modern browser, a text editor, and a short video clip. Basic JavaScript experience with async and await helps with the browser-based encoding steps.
Before We Start
Browser-based video compression depends on a few local tools working together. A missing runtime or hidden browser panel can stop the project before the first clip loads.
This step prepares the browser that runs the encoder. It also prepares the editor that holds your project. A local development server will come from Node.js later.
In this step, get ready to:
- Confirm your browser can open its Developer Tools.
- Prepare Visual Studio Code for editing the project.
- Verify that Node.js and npm are available.
Check your browser and editor
This project uses Google Chrome because its Developer Tools include the network panel you will use later. Visual Studio Code gives every project file one visible workspace.
- Press Cmd+Space on macOS or the Windows key on Windows to open system search.
- Type Google Chrome into the search field.
- Press Enter to open Google Chrome.
- Press Cmd+Option+I on macOS or Ctrl+Shift+I on Windows to open Developer Tools.
You should see Developer Tools beside or below the browser page. Its tabs include Elements.
Developer Tools Not Opening?
Click inside the browser page before trying the shortcut again. The shortcut only works when the browser viewport has focus.
If Google Chrome is missing, install it from the official Google Chrome download page. Help me open Developer Tools in Google Chrome.
- Press Cmd+Space on macOS or the Windows key on Windows to return to system search.
- Type Visual Studio Code into the search field.
- Press Enter to test whether the editor opens.
- Match the result on your screen to one of the tabs below.
✔️ Visual Studio Code opens
Your editor is ready for the project.
- Keep Visual Studio Code open for the next substep.
ⓧ Visual Studio Code is not installed
- Open the official Visual Studio Code download page.
- Download the installer for your operating system.
- Run the downloaded installer.
- Follow the installer prompts with their default options.
- Press Cmd+Space on macOS or the Windows key on Windows after installation finishes.
- Type Visual Studio Code into the search field.
- Press Enter to open the installed editor.
Editor Still Not Opening?
Confirm that the installer finished before searching again. On Windows, the User Installer is suitable for an individual account.
On macOS, confirm that the application is inside the Applications folder. Help me troubleshoot my Visual Studio Code installation.
Open an empty project folder
A folder gives Visual Studio Code a boundary for the project. Starting with an empty folder also makes every file you add easy to spot.
- Click File in the Visual Studio Code menu bar.
- Select Open Folder....
- Select your Desktop in the folder picker.
- Click New Folder.
- Enter video-compressor as the folder name.
- Select the new video-compressor folder.
- Click Select Folder on Windows or Open on macOS.
- Select Yes, I trust the authors if the Workspace Trust dialog appears.
The Explorer sidebar should show video-compressor with no project files underneath it. That empty workspace is the starting point for your page.
Folder Not Showing in the Explorer?
Confirm that the Visual Studio Code window title includes video-compressor. Reopen the folder from the File menu if another location appears.
Check that you selected the folder itself before confirming the folder picker. Help me open my empty project folder in Visual Studio Code.
Verify Node.js and npm
Node.js provides the local JavaScript runtime used by development tools. npm provides the package commands that will run your local static development server.
- Click Terminal in the Visual Studio Code menu bar.
- Select New Terminal.
- Check whether Node.js and npm are available by running these commands:
node -v
npm -v
What Do These Commands Check?
- The first command asks Node.js to print its installed version.
- The second command asks npm to print its installed version.
- Two version numbers confirm that both commands are available in your terminal.
- Match the terminal output to one of the tabs below.
✔️ I see two version numbers
That confirms your local JavaScript tools are ready. The terminal can now use Node.js plus npm.
ⓧ One command is not found
- Open the official Node.js site.
- Download the LTS installer for your operating system.
- Run the downloaded installer.
- Follow the installer prompts with their default options.
- Close Visual Studio Code after the installation finishes.
- Press Cmd+Space on macOS or the Windows key on Windows.
- Type Visual Studio Code into the search field.
- Press Enter to reopen the editor.
Command Still Missing After Installation?
Restarting Visual Studio Code lets its terminal pick up the updated command path. Confirm that the Node.js installer completed before reopening the editor.
If the folder did not restore automatically, reopen video-compressor from the File menu. Help me troubleshoot my Node.js installation.
Before you check again, do you expect both commands to succeed?
- Return to the integrated terminal in the video-compressor workspace.
- Confirm the final setup by running both version checks again:
node -v
npm -v
What Does This Final Check Prove?
- The first output proves the Node.js runtime is available.
- The second output proves the npm package manager is available.
- The terminal runs inside your open project workspace.
You should see one version number on each line. Both commands should return without an error.
- Select the Explorer icon in the Visual Studio Code Activity Bar.
You should see the empty video-compressor folder beside the successful terminal output. Your browser-based development workspace is ready.
Your local tools are ready. Next, you will build the file picker. You will also preview a video directly from your machine.
Build the Video Preview Page
Video compression starts with a trustworthy view of the source clip. This step gives you a baseline to compare with the compressed result later.
An object URL lets the video element read a file selected from your machine. JavaScript also reads the file size for display beneath the preview.
In this step, get ready to:
- Build the original-video interface.
- Connect a selected clip through an object URL.
- Verify the playable preview with its file size visible.
Create the preview structure
The page needs a clear place to choose the source clip. HTML provides the file picker plus the video player that receives it.
- In the Explorer sidebar from earlier, click the New File icon.
- Enter index.html as the file name.
- Add the original-video interface to index.html by pasting this code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Local Video Compressor</title>
</head>
<body>
<!-- Keep this first workflow focused on the original local clip. -->
<main>
<h1>Compress a Video Locally</h1>
<label for="video-input">Choose a video</label>
<input id="video-input" type="file" accept="video/*">
<section>
<h2>Original video</h2>
<video id="original-video" controls width="640"></video>
<p id="original-size">Choose a video to see its size.</p>
</section>
</main>
<!-- Load the behavior that connects the chosen file to the preview. -->
<script src="app.js"></script>
</body>
</html>
What does this structure do?
- The video-input control limits the file picker to video files.
- The original-video element provides the browser's playback controls.
- The original-size paragraph reserves a visible place for the selected file size.
- The final script tag loads the preview behavior from app.js.
- Save index.html.
- Confirm index.html appears in the Explorer sidebar.
Can't find the page file?
Check that index.html sits at the top level of the open project folder. A file inside another folder will not appear at the page address used later.
Confirm that the file name ends with .html. Help me check where index.html belongs in my open project folder.
✔️ Awesome, I've got everything!
Great. Double-check that you saved index.html.
ⓧ I'd like to double check the full code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Local Video Compressor</title>
</head>
<body>
<!-- Keep this first workflow focused on the original local clip. -->
<main>
<h1>Compress a Video Locally</h1>
<label for="video-input">Choose a video</label>
<input id="video-input" type="file" accept="video/*">
<section>
<h2>Original video</h2>
<video id="original-video" controls width="640"></video>
<p id="original-size">Choose a video to see its size.</p>
</section>
</main>
<!-- Load the behavior that connects the chosen file to the preview. -->
<script src="app.js"></script>
</body>
</html>
Connect the selected video
The browser represents the selected clip as a File object. URL.createObjectURL() turns that object into a temporary address that the video player can use.
- In the Explorer sidebar, click the New File icon.
- Enter app.js as the file name.
- Add the preview behavior to app.js by pasting this code:
// Cache the controls so a file selection can update the page.
const videoInput = document.querySelector("#video-input");
const originalVideo = document.querySelector("#original-video");
const originalSize = document.querySelector("#original-size");
let originalVideoUrl = null;
// Convert the file's byte count into a readable megabyte value.
function formatFileSize(bytes) {
const megabytes = bytes / (1024 * 1024);
return `${megabytes.toFixed(2)} MB`;
}
// Replace the preview whenever the learner chooses a video.
videoInput.addEventListener("change", () => {
const file = videoInput.files.item(0);
if (!file) return;
if (originalVideoUrl) {
URL.revokeObjectURL(originalVideoUrl);
}
originalVideoUrl = URL.createObjectURL(file);
originalVideo.src = originalVideoUrl;
originalSize.textContent = `Original size: ${formatFileSize(file.size)}`;
});
What does this code do?
- The first three constants connect the script to the picker plus the two original-video elements.
- The formatFileSize() function converts bytes into megabytes with two decimal places.
- The change listener reads the video selected through the file picker.
- The existing object URL is released before a replacement is created. This keeps repeated selections from holding unnecessary browser memory.
- The new object URL becomes the video source. The selected file's size becomes visible underneath.
- Save app.js.
- Confirm app.js appears beside index.html in the Explorer sidebar.
Is the script file missing?
Check that the file is named app.js. Its name must match the script reference inside index.html.
Place both files at the same level in the open project folder. Help me compare my app.js file with the script reference in index.html.
✔️ Awesome, I've got everything!
Great. Double-check that you saved app.js.
ⓧ I'd like to double check the full code
// Cache the controls so a file selection can update the page.
const videoInput = document.querySelector("#video-input");
const originalVideo = document.querySelector("#original-video");
const originalSize = document.querySelector("#original-size");
let originalVideoUrl = null;
// Convert the file's byte count into a readable megabyte value.
function formatFileSize(bytes) {
const megabytes = bytes / (1024 * 1024);
return `${megabytes.toFixed(2)} MB`;
}
// Replace the preview whenever the learner chooses a video.
videoInput.addEventListener("change", () => {
const file = videoInput.files.item(0);
if (!file) return;
if (originalVideoUrl) {
URL.revokeObjectURL(originalVideoUrl);
}
originalVideoUrl = URL.createObjectURL(file);
originalVideo.src = originalVideoUrl;
originalSize.textContent = `Original size: ${formatFileSize(file.size)}`;
});
Run and verify the preview
A local development server gives the browser an HTTP address for your files. The page can then load its script through the same path that a deployed site uses.
What happens on the first run?
The first run can take about a minute while the static server package downloads. The terminal stays busy while the page is available.
- Approve the package download if your terminal asks for confirmation.
- Start the local development server inside the integrated terminal by running this command:
npx serve
What does this command do?
The command serves the current project folder as a static website. It prints a local address that stays available while the terminal process is running.
- Copy the local address shown in the terminal.
- Paste the address into your browser's address bar.
- Press Enter to load the page.
You'll see the page heading plus the video file picker. The original-video area shows playback controls with the file-size prompt underneath.
Before you choose a clip, which parts of the page do you expect to update?
- Click the video file picker.
- Select a short video file from your machine.
- Press the video player's play control.
That first local loop is working. You'll see the chosen clip play in the original-video player.
The text underneath shows the clip's original size in megabytes. This gives you the baseline for the compressed result later.
Video preview not updating?
Confirm that the local server is still running in the integrated terminal. Reload the page after saving both files.
Choose a video format that your browser can already play. Help me diagnose why my selected local video is not appearing in the preview.
Your local preview now plays a selected clip with its original size visible. Next, you'll connect the in-browser encoder and show when it is ready.
Load FFmpeg in the Browser
That first win is in place. A local clip now plays with its file size visible beneath the preview.
The page still lacks the engine that compresses the clip. In this step, you will load FFmpeg compiled to WebAssembly inside the browser.
In this step, get ready to:
- Install the FFmpeg browser packages.
- Add a status that reports FFmpeg initialization.
- Confirm the ready message in the browser log.
Install the FFmpeg packages
The browser loader uses one package to control FFmpeg. A utility package prepares the core files for browser loading.
- Use your code editor's terminal control to create a second terminal inside the project folder.
- Install the browser packages by running this command:
npm install @ffmpeg/ffmpeg @ffmpeg/util
What does this command install?
- The @ffmpeg/ffmpeg package provides the browser controller for loading FFmpeg.
- The @ffmpeg/util package provides helpers for loading the core files.
- npm records both packages as project dependencies.
Good progress. The terminal finishes with an installation summary once both browser packages are available.
- Select package.json in the code editor's file sidebar.
- Confirm @ffmpeg/ffmpeg appears in the dependencies.
- Confirm @ffmpeg/util appears in the dependencies.
Packages not installing?
- Confirm the terminal path points to the folder used by the local development server.
- Check that the terminal can access the internet before retrying the installation.
- Use this installation troubleshooting prompt if the installation still fails.
Create the browser loader
A dedicated loader keeps one FFmpeg instance available for the encoding step. It also controls the visible status while the core initializes.
- Select the project folder in your code editor's file sidebar.
- Click the file creation control in the sidebar toolbar.
- Enter ffmpeg-loader.js as the file name.
- Build the browser loader by pasting this code into ffmpeg-loader.js:
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { toBlobURL } from '@ffmpeg/util';
// Keep one encoder instance ready for later processing.
export const ffmpeg = new FFmpeg();
const ffmpegStatus = document.querySelector('#ffmpeg-status');
const baseURL = 'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.10/dist/umd';
// Download the browser core before any video is processed.
export async function loadFFmpeg() {
ffmpegStatus.textContent = 'Loading FFmpeg...';
await ffmpeg.load({
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),
});
ffmpegStatus.textContent = 'FFmpeg is ready';
console.log('FFmpeg is ready');
}
// Start initialization when this module loads.
loadFFmpeg();
What does this loader do?
- The FFmpeg instance becomes the controller that later encoding steps can reuse.
- The baseURL points to the single-threaded browser core.
- The toBlobURL() helper prepares the JavaScript core and WebAssembly file for the browser.
- The loadFFmpeg() function waits for initialization before changing the page status and writing the ready message to the log.
- The single-threaded core avoids the cross-origin isolation requirement used by builds that depend on SharedArrayBuffer.
- Save ffmpeg-loader.js.
- Confirm ffmpeg-loader.js appears in the project folder in the file sidebar.
Loader file missing or incomplete?
- Confirm the file name is exactly ffmpeg-loader.js.
- Check that the two import paths match the installed package names.
- Use this loader debugging prompt if the file still looks incomplete.
✔️ Awesome, I've got everything!
Great. Double-check that you saved ffmpeg-loader.js in the project folder.
ⓧ I'd like to double check the full code
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { toBlobURL } from '@ffmpeg/util';
// Keep one encoder instance ready for later processing.
export const ffmpeg = new FFmpeg();
const ffmpegStatus = document.querySelector('#ffmpeg-status');
const baseURL = 'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.10/dist/umd';
// Download the browser core before any video is processed.
export async function loadFFmpeg() {
ffmpegStatus.textContent = 'Loading FFmpeg...';
await ffmpeg.load({
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),
});
ffmpegStatus.textContent = 'FFmpeg is ready';
console.log('FFmpeg is ready');
}
// Start initialization when this module loads.
loadFFmpeg();
Connect and verify the loading state
The page needs a visible status before it starts the loader. The module script begins initialization as soon as the browser reaches it.
- Select index.html in your code editor's file sidebar.
- Find the markup for the original file-size display.
- Add the status and loader directly after that markup by pasting this code:
<!-- Show FFmpeg initialization beside the existing preview. -->
<p id="ffmpeg-status" role="status">Loading FFmpeg...</p>
<script type="module" src="./ffmpeg-loader.js"></script>
How does the page start FFmpeg?
- The ffmpeg-status element gives the loader a visible place to report initialization.
- The module script runs ffmpeg-loader.js after the status element exists.
- The browser downloads FFmpeg's core files during initialization. Your selected video is not part of that download.
- Save index.html.
The first initialization downloads around 31 MB of core files. A slower connection can make this load feel lengthy.
Before you reload, predict which status message appears first.
- Switch back to the locally served page from earlier.
- Use your browser menu to open Developer Tools.
- Select the panel that displays JavaScript logs.
- Reload the page using the browser's refresh control.
- Watch the page status while FFmpeg initializes.
You will first see Loading FFmpeg... on the page. The status changes to FFmpeg is ready after initialization.
The browser log also shows FFmpeg is ready. That matching message proves the loader finished before you selected a file.
- Choose the same short clip with the existing file picker.
- Play the selected clip using its video controls.
The original preview still plays with its file size visible. The FFmpeg status remains ready beside it.
Status stuck on loading?
- Confirm the local development server from earlier is still running.
- Check that ffmpeg-loader.js sits beside index.html.
- Confirm the script source is exactly ./ffmpeg-loader.js.
- Use this initialization debugging prompt if the status still does not change.
FFmpeg now initializes inside your browser before a clip is selected. Next, you will feed the clip into the encoder and watch its progress.
Encode a Video with FFmpeg
Your page already previews a clip from your machine. The next gap is turning that selected file into encoded bytes with FFmpeg.
A browser virtual file system lets JavaScript hand the local clip to WebAssembly without a server. This step uses one fixed quality setting to produce an MP4 file.
In this step, get ready to:
- Add an encode control with a visible status area.
- Move the selected clip into FFmpeg's browser file system.
- Run one fixed-quality MP4 encode with visible output.
Add the encode controls
Encoding takes longer than previewing a local file. A dedicated status area gives the page somewhere to display each message from the encoder.
- Switch back to the page markup file from earlier.
- Find the original file-size display.
- Add the encode controls immediately after that display by pasting this code:
<!-- Keep the encoding action beside the original preview -->
<div class="encode-controls">
<button id="encode-button" type="button" disabled>Encode video</button>
<pre id="encode-log" aria-live="polite">Choose a video to begin.</pre>
</div>
What do these controls do?
- The button gives the selected clip a clear encoding action.
- The disabled state prevents an encode before a clip is available.
- The status area preserves FFmpeg's changing output while the encode runs.
- Save the page markup file.
- Return to the browser page from earlier.
- Refresh the page.
You'll see a disabled Encode video button near the original preview. The status area asks you to choose a video.
Don't see the encode controls?
Confirm that the new markup sits outside the original video element. Content placed inside a video element may remain hidden.
Check that you saved the same markup file served by the local development server. Help me find why my new encode controls are missing from the local page.
Track the selected clip
The preview already reads the chosen file. The encoder also needs a stable reference to that same file after the selection event finishes.
- Switch back to the browser script from earlier.
- Add the selection state below the existing FFmpeg setup by pasting this code:
// Connect the existing file picker to the new encode controls
const videoInput = document.querySelector('input[type="file"]');
const encodeButton = document.querySelector("#encode-button");
const encodeLog = document.querySelector("#encode-log");
let selectedFile = null;
let encodedData = null;
// Keep the encoder tied to the clip currently selected
videoInput.addEventListener("change", () => {
const [file] = videoInput.files;
selectedFile = file ?? null;
encodeButton.disabled = !selectedFile;
});
What does this code do?
- The element references connect the existing file picker to the new button and status area.
- The selectedFile value holds the local clip that FFmpeg receives.
- The encodedData value reserves a place for the output bytes returned after encoding.
- The change listener enables the button only when the file picker contains a clip.
- Save the browser script.
- Return to the browser page from earlier.
- Refresh the page.
- Choose the same short video clip you previewed earlier.
The original preview still plays your clip. The Encode video button is now enabled.
Is the encode button still disabled?
Confirm that the selector in the script matches the page's existing file input. Check that the new script block appears after the FFmpeg instance is created.
Refresh the page before selecting the clip again. Help me debug why my encode button stays disabled after I choose a video.
Encode the clip and watch FFmpeg
The encode follows three stages inside the tab. FFmpeg writes the input into its virtual file system before encoding it at a fixed quality.
JavaScript then reads the completed MP4 back into browser memory. The log listener makes the work visible while those stages run.
- Add the log listener and fixed-quality encode below the selection listener by pasting this code:
// Stream FFmpeg messages into the page during each encode
ffmpeg.on("log", ({ message }) => encodeLog.textContent = message);
// Run one fixed-quality encode inside the browser
encodeButton.addEventListener("click", async () => {
if (!selectedFile) return;
encodeButton.disabled = true;
encodeLog.textContent = "Preparing video...";
await ffmpeg.writeFile(selectedFile.name, await fetchFile(selectedFile));
await ffmpeg.exec([
"-i", selectedFile.name,
"-c:v", "libx264",
"-preset", "veryfast",
"-crf", "28",
"output.mp4"
]);
// Keep the output bytes ready for the result preview
encodedData = await ffmpeg.readFile("output.mp4");
encodeLog.textContent = "Encoding complete. The MP4 is ready in memory.";
encodeButton.disabled = false;
});
How does the encode work?
- The ffmpeg.on() listener copies encoder messages into the page as they arrive.
- The ffmpeg.writeFile() call writes the selected clip into FFmpeg's in-browser file system.
- The ffmpeg.exec() call encodes the video with libx264. The fixed -crf value controls this run's quality.
- The ffmpeg.readFile() call retrieves output.mp4 as browser-readable data.
- Save the browser script.
- Return to the browser page from earlier.
- Refresh the page.
- Choose the same short video clip again.
A short clip can still keep the tab busy because the encoder is using your computer's CPU.
Before you start the encode, do you expect the status area to change once or keep updating during the run?
- Click Encode video.
You'll see FFmpeg messages replace one another while the clip is being encoded. The button stays disabled during this work.
The status finishes with Encoding complete. The MP4 is ready in memory. Your first browser-side encode has completed.
Does the encode stop before completion?
Try a shorter clip if the browser runs low on memory. Keep the page open while FFmpeg is processing the file.
Confirm that the existing FFmpeg ready message appears before you start encoding. Help me diagnose why my browser-side FFmpeg encode does not complete.
Your clip now travels through FFmpeg and returns as MP4 bytes without leaving the browser. Next, you'll turn those bytes into a second playable preview.
Display the Compressed Video
Your FFmpeg encoder now turns a chosen clip into output bytes inside the browser. The page still gives you no way to inspect those bytes as a video.
A browser object URL makes the generated MP4 playable without an upload. The finished page places both videos side by side.
In this step, get ready to:
- Show both videos side by side.
- Connect the encoded output to a playable browser object URL.
- Compare both video results.
Build the second result panel
Matching result panels make the comparison easy to scan. Each panel gives one video its own preview area plus its own file-size display.
- Switch back to the web page markup file from earlier.
- Add a compressed-result panel containing a video preview plus a file-size area.
- Save the web page file.
- Refresh the locally running page.
You'll see a second result panel for the compressed video. Its preview remains empty until an encode finishes.
- Update the page's existing styles to place the original result beside the compressed result.
- Save the page styles.
- Refresh the locally running page.
Good progress. Your page now has a clear comparison area with one panel for each result.
Compressed Panel Missing or Stacked?
Confirm that the compressed-result panel sits inside the same results container as the original panel. Check that the page styles target that shared container.
Make sure the latest markup plus styles are saved before refreshing. Help me fix the side-by-side video result layout.
Connect the encoded output
FFmpeg returns the compressed video as bytes from its in-browser file system. The browser needs a local address before its video player can load those bytes.
How Does Local Playback Work?
A Blob packages the encoded bytes as a browser-readable video object. The MP4 media type describes the format stored inside it.
An object URL gives that Blob a temporary local address. The compressed video element can load this address directly.
- Return to the script file containing the encode logic from earlier.
- Locate the completion logic immediately after the encoded output is read from FFmpeg.
- Extend the completion logic to create the compressed preview's object URL from an MP4 Blob.
- Save the script file.
- Click the page's encode button.
When encoding completes, you'll see the compressed clip inside the new video preview. Its playback controls are ready to use.
Compressed Preview Still Empty?
Confirm that the new preview receives the object URL after FFmpeg finishes reading the encoded output. Check that the Blob describes MP4 video data.
Make sure the result update runs after a successful encode. Help me diagnose the empty compressed preview.
- Extend the same completion logic to render the compressed Blob's size using the original size format.
- Save the script file.
- Click the page's encode button again.
You'll see a formatted file size beneath the compressed preview. The original panel keeps the selected clip's size visible for comparison.
Compressed Size Missing?
Confirm that the displayed value comes from the compressed Blob created after the encode. Check that the result uses the same size-formatting pattern as the original file.
Re-run the encode after saving the script. Help me fix the compressed file-size display.
Verify the compressed result
A successful encode needs more than a finished progress log. The generated video must play with acceptable visual quality.
Before you press play, do you expect the compressed preview to preserve enough detail for the clip to remain useful?
- Press the playback control in the compressed preview.
- Watch a detailed section of the compressed clip.
- Play the matching section in the original preview.
- Compare the visible clarity between the two previews.
- Read the original file-size display.
- Read the compressed file-size display.
You'll see the compressed preview play beside the original. For your short test clip, the compressed result should remain watchable with a smaller displayed file size.
Output Larger Than the Original?
Confirm that the compressed label shows the Blob created by the latest encode. A stale value can make the comparison misleading.
Try another short clip if your source was already heavily compressed. Help me understand this file-size result.
Your page now turns FFmpeg's encoded bytes into a playable result with a visible size comparison. Next, you'll make the quality adjustable.
Add a Quality Control
Your compressor already shows the original clip beside its compressed result. The fixed setting keeps every run on a single quality trade-off.
A quality control turns that trade-off into a choice before each FFmpeg encode. Using the same clip twice reveals how the selected quality affects the output size.
In this step, get ready to:
- Add a quality control to the existing page.
- Connect the selected value to the FFmpeg encode.
- Compare two compressed sizes from the same clip.
Add the quality control
The control gives each encode a visible quality choice. Its initial choice preserves the behavior that already works.
- Switch back to the code editor from earlier.
- Add a labeled quality control beside the existing encode button with the current fixed quality as its initial choice.
- Save the page file that contains your existing controls.
- Return to the locally served page from earlier.
- Refresh the browser.
That is the visible layer done. You can now choose a quality before the encoder runs.
Can't see the quality control?
Check that you added the control to the same page markup that contains the existing encode button. Confirm that the page file was saved before you refreshed.
If the old layout remains visible, check that the browser is showing the local development page from earlier. Help me find why my new quality control is missing from my locally served page.
Use the selected quality
The encode handler currently sends one fixed quality value into every run. Reading the control at encode time lets each selection drive the next result.
- Switch back to the code editor from earlier.
- Find the fixed quality value inside the existing FFmpeg encode command.
- Replace the fixed quality value with a read from the quality control's current selection.
- Save the script file that contains the existing encode handler.
- Return to the locally served page from earlier.
- Refresh the browser.
- Select the same short test clip you used earlier.
- Choose the first value you want to test with the quality control.
Before you start the encode, do you expect the selected value to produce a playable result?
- Start the first controlled encode with the existing encode button.
You'll see the FFmpeg progress or log output move during the run. The compressed preview updates when the encode finishes.
How Does the Selection Reach FFmpeg?
- The encode handler reads the control when you start an encode.
- That selected value replaces the previous fixed quality value.
- The existing virtual file system flow produces the new compressed MP4.
- Record the first quality setting as first quality value.
- Record the first result as first compressed file size.
Your first controlled encode is complete. Its quality value plus output size form the baseline for your comparison.
Encode Still Using the Fixed Quality?
Check that the encode handler reads the control after you start each run. A value captured only during page loading stays unchanged when you move the control.
Confirm that the selected value replaces the original fixed value in the existing FFmpeg encode command. Help me trace the quality value into my FFmpeg encode.
Compare two quality settings
A fair comparison keeps the source clip unchanged. Only the quality selection changes between runs.
- Keep the same clip selected in the file input.
- Choose a distinctly different value with the quality control.
Before you encode again, do you expect the second compressed size to match your first result?
- Start the second encode with the existing encode button.
You'll see the progress or log output move again. The compressed preview updates with the result from the second quality setting.
- Record the second quality setting as second quality value.
- Record the second result as second compressed file size.
- Compare second compressed file size with first compressed file size.
- Play the latest compressed preview from start to finish.
You should have two measured sizes tied to two different quality choices. The latest compressed result should remain playable.
Both Runs Look Identical?
Confirm that the second quality value differs clearly from the first one. Check that each run reads the control's current selection.
Make sure you recorded the displayed compressed size after each encode completed. Help me compare two FFmpeg quality runs.
You have turned a fixed compressor into one you can tune for each clip. Next, you'll prove the encode stays on your machine before publishing the page.
Verify Privacy and Deploy the Page
Your two quality runs proved that the compressor can trade file size against visual quality. Now you need evidence that the selected video stays on your machine.
The browser's Network panel makes that privacy claim testable. A public deployment then turns your local tool into a page you can return to whenever a clip is too large.
In this step, get ready to:
- Prove that encoding creates no remote upload request.
- Publish the project folder as a static web page.
- Run the compressor from its public URL.
Prove the clip stays local
The Network panel records requests between your browser and remote servers. Clearing it after the encoder is ready isolates the requests made during compression.
- Switch back to the local compressor page from earlier.
- Wait for the page's ready message.
- Open Developer Tools from your browser's main menu.
- Select the Network panel.
- Use the panel's clear control to remove the existing requests.
Why clear the request list now?
The FFmpeg WebAssembly files arrive while the page loads. Clearing the list after the ready message separates those expected downloads from the encode itself.
- Choose the same short clip with the page's file picker.
Before you start the encode, do you expect the Network panel to show a request carrying your video to a remote server?
- Click the page's encode button.
You'll see the progress output move while the request list stays free of any new remote upload containing your clip. The encode is running inside the browser tab.
What proves the video stayed local?
The selected clip is written into FFmpeg's in-browser virtual file system. The absence of a remote upload request during encoding shows that the media bytes never went to a compression server.
Seeing requests during the encode?
- Check whether the requests appeared before the ready message.
- Repeat the test after clearing the Network panel once the encoder is ready.
- Inspect the destination of each new request to distinguish a remote URL from a browser object URL.
- Ask for help with the evidence you see.
Deploy the static page
A static deployment publishes your page files without adding a compression server. Netlify Drop accepts the project folder from your computer and provides a public URL.
Signing in keeps the deployment attached to your account. Your chosen video stays on your machine because test clips are selected at runtime.
- Open the official Netlify Drop page in your browser.
- Create a free Netlify account from the sign-up link if you do not have one.
- Sign in to Netlify.
- Return to the Netlify Drop page after signing in.
- Drag the project folder from earlier into the Drop zone.
- Keep the Netlify page open until the deployment finishes.
Netlify shows a Preview URL when the deployment is live. Good progress. Your compressor now has an address on the web.
Some Netlify teams make new projects private by default. Public visibility lets the finished page open without a Netlify login.
- Select Project configuration for the deployed project.
- Select General.
- Open Visitor access.
- Select Project visibility.
- Click Edit visibility.
- Choose Public.
- Click Save.
The production deployment is now available to anyone who has its address.
- Copy the Preview URL ending in netlify.app.
- Record the public address here: your Netlify public URL.
Deployment not completing?
- Confirm that you dropped the project folder containing the locally runnable page.
- Check the Netlify deployment log for the first failed build or publishing task.
- Retry the deployment after resolving the reported task.
- Get help interpreting the deployment log.
Test the public URL
A private browser window tests the page without relying on your active Netlify session. This confirms that the address is genuinely public.
- Open a private window from your browser's main menu.
- Paste your Netlify public URL into the address bar.
- Press Enter to load the page.
The first public load can spend a few seconds fetching the FFmpeg core. Keep the tab open while the loading status is visible.
- Wait for the deployed page to show its ready message.
The ready message confirms that the browser loaded the deployed encoder before any video was selected.
- Choose the same short clip with the deployed page's file picker.
Before you run it on the deployed page, do you expect the complete compression flow to work from this public URL?
- Click the deployed page's encode button.
You'll see progress move during the encode. The original and compressed previews appear side by side with their file sizes.
- Play the compressed preview.
The compressed clip plays from the deployed page with the quality setting you selected. Your public compressor is ready for future clips.
Public page not reaching ready?
- Return to the project's Project visibility setting if the private window asks for a Netlify login.
- Confirm that Public is selected.
- Open Developer Tools on the deployed page if the ready status stalls.
- Ask for help identifying a failed FFmpeg asset or worker request.
That completes the full loop. Your public compressor can now shrink future clips inside the browser without uploading them to a compression server.
Secret mission
Prove Compression Works Offline
Load the deployed compressor until FFmpeg is ready. Disconnect your computer. Complete an encode while the Network panel records the attempt.
Clean Up Your Resources
Clean Up Your Resources
Every encode runs locally in your browser. Keep the public page available, pause your local work, or remove the project entirely without ongoing video-processing costs.
Resources you used:
- A deployed static video-compressor page at the public URL you opened earlier.
- A local project folder containing the page code plus its FFmpeg integration through WebAssembly.
- Temporary browser data containing the virtual files plus object URLs for your selected clips.
Keep everything running
No action is needed while you want the compressor ready for future clips. The deployed page continues processing each selected video inside your browser.
- Leave the public deployment available.
- Keep the local project folder from earlier.
- Use the public URL whenever you need to compress another short clip.
- Close the compressor tab after each session to release its temporary browser data.
Pause - I'll come back to this later
Pausing local development frees terminal resources plus browser memory. Your deployed page remains available for later.
- Return to the terminal running the local static development server if it is still active.
- Stop the local static development server with your terminal's interrupt control.
- Close every browser tab running the video compressor.
- Leave the public deployment in place.
- Keep the local project folder from earlier.
That clears the local runtime while preserving everything you need to return.
Delete - I don't want to use this again
Complete removal clears the deployed page plus its local source files. There is no account to close or key to revoke.
Deleting the deployment and folder is final. Your original video files stay untouched.
- Close every browser tab running the video compressor.
- Return to the hosting service from the deployment step.
- Use that provider's documented site deletion control for the deployed static video-compressor page.
- Open the public URL from earlier.
- Confirm that the public URL no longer loads the compressor.
- Choose your operating system below to remove the local project folder.
macOS
- Use Finder to locate the project folder from earlier.
- Move the project folder to Trash.
- Empty Trash if you want to remove the files immediately.
Windows
- Use File Explorer to locate the project folder from earlier.
- Move the project folder to the Recycle Bin.
- Empty the Recycle Bin if you want to remove the files immediately.
- Confirm that the project folder no longer appears in its previous location.
Your deployment, local source files, plus temporary browser data are now removed.
Nice Work!
Nice Work!
You did it! Your deployed video compressor now shrinks clips inside the browser using FFmpeg compiled to WebAssembly. You also proved that each selected video stays on your machine during encoding.
You've learned how to:
- Run browser-side video compression with visible ready status. Watch the encoding progress as FFmpeg processes a selected clip.
- Compare compression quality settings through side-by-side playable previews. Use the displayed file sizes to judge each result.
- Verify local processing in the browser Network panel. Use the deployed compressor from its public URL.
- Secret Mission: Take on an optional challenge to push your browser-side video skills further.
Ready to quiz yourself?