Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Vanilagy/mediabunny/llms.txt

Use this file to discover all available pages before exploring further.

Mediabunny enables you to create media files with fine-grained control. You can add multiple video, audio, and subtitle tracks to a media file and precisely control the timing of media data. Using output targets, you can decide whether to build the entire file in memory or stream it out in chunks as it’s being created, allowing you to create very large files efficiently.

Creating an output

Media file creation in Mediabunny revolves around the Output class. One instance of Output represents one media file you want to create.
1

Import the required classes

import { Output, Mp4OutputFormat, BufferTarget } from 'mediabunny';
2

Create a new output

const output = new Output({
  format: new Mp4OutputFormat(),
  target: new BufferTarget(),
});
The format determines the container format of the output file (MP4, WebM, etc.).The target determines where the data will be written (memory, disk, stream, etc.). See the Streaming guide for available targets.

Adding tracks

Before starting an output, you need to add tracks to it. Each track requires a media source that provides the media data.

Adding a video track

import { CanvasSource } from 'mediabunny';

// Create a video source from a canvas element
const videoSource = new CanvasSource(canvasElement, {
  codec: 'avc',
  bitrate: 1e6, // 1 Mbps
});

output.addVideoTrack(videoSource, {
  frameRate: 30,
  rotation: 0,
  language: 'eng',
  name: 'Main video',
});

Adding an audio track

import { MediaStreamAudioTrackSource } from 'mediabunny';

// Create an audio source from microphone
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const audioStreamTrack = stream.getAudioTracks()[0];
const audioSource = new MediaStreamAudioTrackSource(audioStreamTrack, {
  codec: 'aac',
  bitrate: 128e3, // 128 kbps
});

output.addAudioTrack(audioSource, {
  language: 'eng',
  name: 'Microphone',
});

Track metadata options

output.addVideoTrack(videoSource, {
  // Clockwise rotation in degrees
  rotation: 90,
  // Expected frame rate in hertz
  frameRate: 30,
  // ISO 639-2/T language code
  language: 'eng',
  // User-defined track name
  name: 'Main camera',
  // Track disposition flags
  disposition: { default: true },
});
The frameRate option snaps all timestamps and durations to the specified frame rate. To achieve fractional frame rates precisely, use their exact fractional forms:
  • 23.976 → 24000/1001
  • 29.97 → 30000/1001
  • 59.94 → 60000/1001

Setting metadata tags

You can write descriptive metadata tags to the output file:
output.setMetadataTags({
  title: 'Big Buck Bunny',
  artist: 'Blender Foundation',
  date: new Date('2008-05-20'),
  images: [{
    data: coverImageBytes,
    mimeType: 'image/jpeg',
    kind: 'coverFront',
  }],
});
Metadata tags must be set before calling output.start().
See the Metadata guide for all available metadata fields.

Starting an output

After adding all tracks, you need to start the output:
await output.start();
This spins up the writing process and prevents adding new tracks. After this, you can start sending media data to the output file.

Adding media data

After starting an output, use the media sources to pipe data to the output file:
// For a CanvasSource, capture frames at regular intervals
let framesAdded = 0;
const intervalId = setInterval(() => {
  const timestamp = framesAdded / 30;
  const duration = 1 / 30;
  
  // Captures the canvas state at the time of calling add
  videoSource.add(timestamp, duration);
  framesAdded++;
}, 1000 / 30);

// Audio from MediaStreamAudioTrackSource is automatically piped
// after calling start()
The API differs for each media source type - check the media sources documentation for details.

Finalizing an output

Once all media data has been added, finalize the output:
clearInterval(intervalId);  // Stop capturing
audioStreamTrack.stop();    // Stop microphone

await output.finalize();

const file = output.target.buffer; // => ArrayBuffer
After calling finalize(), adding more media data will result in an error.

Output state

You can check the current state of an output:
output.state; // => 'pending' | 'started' | 'canceled' | 'finalizing' | 'finalized'
  • 'pending' - Not started yet; tracks can be added
  • 'started' - Ready to receive media data; no more tracks can be added
  • 'finalizing' - finalize() has been called but hasn’t completed
  • 'finalized' - Output is complete
  • 'canceled' - Output was canceled

Canceling an output

To cancel an ongoing output:
await output.cancel();
This frees up resources like encoders and prevents adding more data.

Example: Record canvas and microphone

import {
  Output,
  Mp4OutputFormat,
  BufferTarget,
  CanvasSource,
  MediaStreamAudioTrackSource,
} from 'mediabunny';

const output = new Output({
  format: new Mp4OutputFormat(),
  target: new BufferTarget(),
});

// Set up video from canvas
const videoSource = new CanvasSource(canvas, {
  codec: 'avc',
  bitrate: 2e6,
});
output.addVideoTrack(videoSource, { frameRate: 30 });

// Set up audio from microphone
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const audioTrack = stream.getAudioTracks()[0];
const audioSource = new MediaStreamAudioTrackSource(audioTrack, {
  codec: 'aac',
  bitrate: 128e3,
});
output.addAudioTrack(audioSource);

await output.start();

// Capture video frames
const intervalId = setInterval(() => {
  videoSource.add(framesAdded / 30, 1 / 30);
  framesAdded++;
}, 1000 / 30);

// Stop after 10 seconds
setTimeout(async () => {
  clearInterval(intervalId);
  audioTrack.stop();
  
  await output.finalize();
  
  // Download the file
  const blob = new Blob([output.target.buffer], { 
    type: output.format.mimeType 
  });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = 'recording.mp4';
  a.click();
}, 10000);

Getting the MIME type

To retrieve the full MIME type of the output file (including codec strings):
const mimeType = await output.getMimeType();
// => 'video/mp4; codecs="avc1.42c032, mp4a.40.2"'
This promise only resolves once codec strings for all tracks are known, which requires encoders to be initialized. Don’t await this before adding media data or you’ll create a deadlock.

Packet buffering

Some output formats require packet buffering for multi-track outputs. The output must wait for data from all tracks for a given timestamp before writing.
To minimize memory usage, add media data in an interleaved way. For example, add 10 seconds of video, then 10 seconds of audio, then repeat - instead of adding all video first, then all audio.

Build docs developers (and LLMs) love