Frontend Development 13 min read

How to Clip Videos in the Browser with WebAssembly and FFmpeg

This article explains how to use WebAssembly and FFmpeg compiled to wasm for client‑side video clipping, covering the basics of WebAssembly, setting up Emscripten, loading ffmpeg.wasm, building a minimal Node server, and adding a slider UI for selecting clip ranges.

WeDoctor Frontend Technology
WeDoctor Frontend Technology
WeDoctor Frontend Technology
How to Clip Videos in the Browser with WebAssembly and FFmpeg

Introduction

Recently I read an article about WebAssembly and became curious about trying it out.

What is WebAssembly?

WebAssembly (wasm) is a portable, small‑size, fast‑loading binary format that runs on the web. C/C++ code can be compiled to a .wasm file, delivered to the browser, and invoked from JavaScript.

Advantages of WebAssembly

WebAssembly offers superior performance compared to JavaScript, making it ideal for compute‑intensive tasks such as image/video decoding, 3D/WebVR/AR, and other high‑performance scenarios. Existing C/C++ libraries can be compiled to wasm and used directly in the browser, reducing server load.

Simple WebAssembly Example

We start with a minimal C file:

<code>int add(int a, int b) {
  return a + b;
}</code>

Compile it with Emscripten:

<code>emcc test.c -Os -s WASM=1 -s SIDE_MODULE=1 -o test.wasm</code>

Then load it in HTML:

<code>fetch('./test.wasm')
  .then(response => response.arrayBuffer())
  .then(bytes => WebAssembly.instantiate(bytes))
  .then(results => {
    const add = results.instance.exports.add;
    console.log(add(11, 33));
  });</code>

The console will show the result, confirming the wasm function works.

Getting Started

Having learned how to call compiled C/C++ code in the browser, we can move toward a practical goal: clipping video on the client side.

Demo Demonstration

The demo repository is https://github.com/Dseekers/clip-video-by-webassembly . Below is a screenshot of the demo.

FFmpeg

FFmpeg is an open‑source suite for handling audio and video formats, providing libraries such as libavcodec and libavformat.

FFmpeg can record, convert, and stream audio/video in many formats.

It is written in C and can be used via command‑line arguments. For example, to cut a segment:

<code>ffmpeg -ss [start] -i [input] -to [end] -c copy [output]</code>

where

start

and

end

define the clip range,

input

is the source video, and

output

is the resulting file.

Obtaining FFmpeg WebAssembly

Compiling FFmpeg to wasm with Emscripten can be problematic, so we use a pre‑built CDN package from ffmpeg.wasm .

The package includes four files:

<code>ffmpeg.min.js
ffmpeg-core.js
ffmpeg-core.wasm
ffmpeg-core.worker.js</code>

Only

ffmpeg.min.js

needs to be imported; the other files are fetched automatically.

Minimal Implementation

Because ffmpeg.wasm requires

SharedArrayBuffer

, we must set the following response headers on a simple Node server:

<code>Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp</code>

Node server example (Koa):

<code>const Koa = require('koa');
const path = require('path');
const fs = require('fs');
const router = require('koa-router')();
const static = require('koa-static');
const staticPath = './static';
const app = new Koa();
app.use(static(path.join(__dirname, staticPath)));
app.use(async (ctx, next) => {
  console.log(`Process ${ctx.request.method} ${ctx.request.url}...`);
  ctx.set('Cross-Origin-Opener-Policy', 'same-origin');
  ctx.set('Cross-Origin-Embedder-Policy', 'require-corp');
  await next();
});
router.get('/', async ctx => { ctx.body = '<h1>Index</h1>'; });
router.get('/:filename', async ctx => {
  const filePath = path.join(__dirname, ctx.request.url);
  const htmlContent = fs.readFileSync(filePath);
  ctx.type = 'html';
  ctx.body = htmlContent;
});
app.use(router.routes());
app.listen(3000);
console.log('app started at port 3000...');</code>

Demo HTML loads the ffmpeg script and provides UI for selecting a video file and starting the clip:

<code>&lt;script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"&gt;&lt;/script&gt;
&lt;script src="./assets/ffmpeg.min.js"&gt;&lt;/script&gt;
<div class="container">
  <div class="operate">
    Select original video file:
    &lt;input type="file" id="select_origin_file"&gt;
    &lt;button id="start_clip"&gt;Start Clip&lt;/button&gt;
  </div>
  <div class="video-container">
    <div class="label">Original Video</div>
    &lt;video class="my-video" id="origin-video" controls&gt;&lt;/video&gt;
  </div>
  <div class="video-container">
    <div class="label">Processed Video</div>
    &lt;video class="my-video" id="handle-video" controls&gt;&lt;/video&gt;
  </div>
</div></code>

JavaScript (jQuery) loads the selected file, creates an FFmpeg instance, runs the clipping command, and displays the result:

<code>let originFile;
$(document).ready(function() {
  $('#select_origin_file').on('change', e => {
    const file = e.target.files[0];
    originFile = file;
    const url = window.webkitURL.createObjectURL(file);
    $('#origin-video').attr('src', url);
  });
  $('#start_clip').on('click', async function() {
    const { fetchFile, createFFmpeg } = FFmpeg;
    const ffmpeg = createFFmpeg({ log: true, corePath: './assets/ffmpeg-core.js' });
    const { name } = originFile;
    if (!ffmpeg.isLoaded()) await ffmpeg.load();
    ffmpeg.FS('writeFile', name, await fetchFile(originFile));
    await ffmpeg.run('-i', name, '-ss', '00:00:00', '-to', '00:00:01', 'output.mp4');
    const data = ffmpeg.FS('readFile', 'output.mp4');
    const tempURL = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
    $('#handle-video').attr('src', tempURL);
  });
});</code>

The core functionality is now complete.

Small Optimizations

To allow users to choose arbitrary start and end times, a slider UI (Vue) is added. Below is the core ClipVideo class (JS) that handles the slider, time conversion, and FFmpeg execution.

<code>class ClipVideo {
  constructor() {
    this.ffmpeg = null;
    this.originFile = null;
    this.handleFile = null;
    this.vueInstance = null;
    this.currentSliderValue = [0, 0];
    this.init();
  }
  init() {
    console.log('init');
    this.initFfmpeg();
    this.bindSelectOriginFile();
    this.bindOriginVideoLoad();
    this.bindClipBtn();
    this.initVueSlider();
  }
  // ... (methods omitted for brevity) ...
}
$(document).ready(function() {
  const instance = new ClipVideo();
});</code>

With these additions, the article demonstrates a functional front‑end video clipping tool powered by WebAssembly.

Conclusion

WebAssembly is still a relatively new technology; this article only scratches its surface by implementing a simple video clipping demo. There is much more to explore.

References

WebAssembly 完全入门——了解 wasm 的前世今生 (https://juejin.cn/post/6844903709806182413)

使用 FFmpeg 与 WebAssembly 实现纯前端视频截帧 (https://toutiao.io/posts/7as4kva/preview)

前端视频帧提取 ffmpeg + Webassembly (https://juejin.cn/post/6854573219454844935)

frontendWasmWebAssemblyFFmpegEmscriptenVideo Clipping
WeDoctor Frontend Technology
Written by

WeDoctor Frontend Technology

Official WeDoctor Group frontend public account, sharing original tech articles, events, job postings, and occasional daily updates from our tech team.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.