Skip to content

Quick start

This page is a collection of short code snippets that showcase the most common operations you may use this library for.

Read file metadata

ts
import { Input, ALL_FORMATS, BlobSource } from 'mediabunny';

const input = new Input({
	formats: ALL_FORMATS, // Supporting all file formats
	source: new BlobSource(file), // Assuming a File instance
});

const duration = await input.computeDuration(); // in seconds
const allTracks = await input.getTracks(); // List of all tracks

// Extract video metadata
const videoTrack = await input.getPrimaryVideoTrack();
if (videoTrack) {
	videoTrack.displayWidth; // in pixels
	videoTrack.displayHeight; // in pixels
	videoTrack.rotation; // in degrees clockwise

	// Estimate frame rate (FPS)
	const packetStats = await videoTrack.computePacketStats(100);
	const averageFrameRate = packetStats.averagePacketRate;
}

// Extract audio metadata
const audioTrack = await input.getPrimaryAudioTrack();
if (audioTrack) {
	audioTrack.numberOfChannels;
	audioTrack.sampleRate; // in Hz
}

INFO

Read media data

ts
import {
	Input,
	ALL_FORMATS,
	BlobSource,
	VideoSampleSink,
	AudioSampleSink,
} from 'mediabunny';

const input = new Input({
	formats: ALL_FORMATS,
	source: new BlobSource(file),
});

// Read video frames
const videoTrack = await input.getPrimaryVideoTrack();
if (videoTrack) {
	const decodable = await videoTrack.canDecode();
	if (decodable) {
		const sink = new VideoSampleSink(videoTrack);

		// Get the video frame at timestamp 5s
		const videoSample = await sink.getSample(5);
		videoSample.timestamp; // in seconds
		videoSample.duration; // in seconds

		// Draw the frame to a canvas
		videoSample.draw(ctx, 0, 0);

		// Loop over all frames in the first 30s of video
		for await (const sample of sink.samples(0, 30)) {
			// ...
		}
	}
}

// Read audio chunks
const audioTrack = await input.getPrimaryAudioTrack();
if (audioTrack) {
	const decodable = await audioTrack.canDecode();
	if (decodable) {
		const sink = new AudioSampleSink(audioTrack);

		// Get audio chunk at timestamp 5s; a short chunk of audio
		const audioSample = await sink.getSample(5);
		audioSample.timestamp; // in seconds
		audioSample.duration; // in seconds
		audioSample.numberOfFrames;

		// Convert to AudioBuffer for use with the Web Audio API
		const audioBuffer = audioSample.toAudioBuffer();

		// Loop over all samples in the first 30s of audio
		for await (const sample of sink.samples(0, 30)) {
			// ...
		}
	}
}

INFO

Extract video thumbnails

ts
import {
	Input,
	ALL_FORMATS,
	BlobSource,
	CanvasSink,
} from 'mediabunny';

const input = new Input({
	formats: ALL_FORMATS,
	source: new BlobSource(file),
});

const videoTrack = await input.getPrimaryVideoTrack();
if (videoTrack) {
	const decodable = await videoTrack.canDecode();
	if (decodable) {
		const sink = new CanvasSink(videoTrack, {
			width: 320, // Automatically resize the thumbnails
		});

		// Get the thumbnail at timestamp 10s
		const result = await sink.getCanvas(10);
		result.canvas; // HTMLCanvasElement | OffscreenCanvas
		result.timestamp; // in seconds
		result.duration; // in seconds

		// Generate five equally-spaced thumbnails through the video
		const startTimestamp = await videoTrack.getFirstTimestamp();
		const endTimestamp = await videoTrack.computeDuration();
		const timestamps = [0, 0.2, 0.4, 0.6, 0.8].map(
			(t) => startTimestamp + t * (endTimestamp - startTimestamp)
		);

		// Loop over these timestamps
		for await (const result of sink.canvasesAtTimestamps(timestamps)) {
			// ...
		}
	}
}

INFO

Extract encoded packets

ts
import {
	Input,
	ALL_FORMATS,
	BlobSource,
	EncodedPacketSink,
} from 'mediabunny';

const input = new Input({
	formats: ALL_FORMATS,
	source: new BlobSource(file),
});

const videoTrack = await input.getPrimaryVideoTrack();
if (videoTrack) {
	const sink = new EncodedPacketSink(videoTrack);

	// Get packet for timestamp 10s
	const packet = await sink.getPacket(10);
	packet.data; // Uint8Array
	packet.type; // 'key' | 'delta'
	packet.timestamp; // in seconds
	packet.duration; // in seconds

	// Get the closest key packet to timestamp 10s
	const keyPacket = await sink.getKeyPacket(10);

	// Get the following packet
	const nextPacket = await sink.getNextPacket(keyPacket);

	// Set up a manual decoder
	const decoderConfig = await videoTrack.getDecoderConfig();
	const videoDecoder = new VideoDecoder({
		output: console.log,
		error: console.error,
	});
	videoDecoder.configure(decoderConfig);

	// Loop over all packets in decode order
	for await (const packet of sink.packets()) {
		videoDecoder.decode(packet.toEncodedVideoChunk());
	}

	await videoDecoder.flush();
}

INFO

Check out EncodedPacketSink for the full documentation.

Create new media files

ts
import {
	Output,
	BufferTarget,
	Mp4OutputFormat,
	CanvasSource,
	AudioBufferSource,
	QUALITY_HIGH,
} from 'mediabunny';

// An Output represents a new media file
const output = new Output({
	format: new Mp4OutputFormat(), // The format of the file
	target: new BufferTarget(), // Where to write the file (here, to memory)
});

// Example: add a video track driven by a canvas
const videoSource = new CanvasSource(canvas, {
	codec: 'avc',
	bitrate: QUALITY_HIGH,
});
output.addVideoTrack(videoSource);

// Example: add an audio track driven by AudioBuffers
const audioSource = new AudioBufferSource({
	codec: 'aac',
	bitrate: QUALITY_HIGH,
});
output.addAudioTrack(audioSource);

await output.start();

// Add some video frames
for (let frame = 0; ...) {
	await videoSource.add(frame / 30, 1 / 30);
}

// Add some audio data
await audioSource.add(audioBuffer1);
await audioSource.add(audioBuffer2);

await output.finalize();

const buffer = output.target.buffer; // ArrayBuffer containing the final MP4 file

INFO

Write directly to disk

ts
import {
	Output,
	StreamTarget,
} from 'mediabunny';

// File System API
const handle = await window.showSaveFilePicker();
const writableStream = await handle.createWritable();

const output = new Output({
	// `chunked: true` to batch disk operations
	target: new StreamTarget(writableStream, { chunked: true }),
	// ...
});

// ...

await output.finalize();

// The file has been fully written to disk

Stream over the network

ts
import {
	Output,
	StreamTarget,
	StreamTargetChunk,
	Mp4OutputFormat,
} from 'mediabunny';

const { writable, readable } = new TransformStream<StreamTargetChunk, Uint8Array>({
	transform: (chunk, controller) => controller.enqueue(chunk.data),
});

const output = new Output({
	target: new StreamTarget(writable),
	// We must use an append-only format here, such as fragmented MP4
	format: new Mp4OutputFormat({ fastStart: 'fragmented' }),
});

const uploadComplete = fetch('https://example.com/upload', {
	method: 'POST',
	body: readable,
	headers: {
		'Content-Type': output.format.mimeType,
	},
});

await output.start();

// ...

await output.finalize();
await uploadComplete;

INFO

  • This code automatically handles the backpressure applied by a slow network.
  • Read more on append-only formats, a requirement for this pattern.

Record live media

ts
import {
	Output,
	BufferTarget,
	WebMOutputFormat,
	MediaStreamVideoTrackSource,
	MediaStreamAudioTrackSource,
	QUALITY_MEDIUM
} from 'mediabunny';

const userMedia = await navigator.mediaDevices.getUserMedia({
	video: true,
	audio: true,
});
const videoTrack = userMedia.getVideoTracks()[0];
const audioTrack = userMedia.getAudioTracks()[0];

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

if (videoTrack) {
	const source = new MediaStreamVideoTrackSource(videoTrack, {
		codec: 'vp9',
		bitrate: QUALITY_MEDIUM,
	});
	output.addVideoTrack(source);
}

if (audioTrack) {
	const source = new MediaStreamAudioTrackSource(audioTrack, {
		codec: 'opus',
		bitrate: QUALITY_MEDIUM,
	});
	output.addAudioTrack(source);
}

await output.start();

// Wait...

await output.finalize();

INFO

Check encoding support

ts
import {
	MovOutputFormat,
	getFirstEncodableVideoCodec,
	getFirstEncodableAudioCodec,
	getEncodableVideoCodecs,
	getEncodableAudioCodecs,
} from 'mediabunny';

const outputFormat = new MovOutputFormat();

// Find the best supported codec for the given container format
const bestVideoCodec = await getFirstEncodableVideoCodec(
	outputFormat.getSupportedVideoCodecs(),
	// Optionally, constrained by these parameters:
	{ width: 1920, height: 1080 },
);
const bestAudioCodec = await getFirstEncodableAudioCodec(
	outputFormat.getSupportedAudioCodecs(),
);

// Find all supported codecs
const supportedVideoCodecs = await getEncodableVideoCodecs();
const supportedAudioCodecs = await getEncodableAudioCodecs();

Convert files

ts
import {
	Input,
	Output,
	Conversion,
	ALL_FORMATS,
	BlobSource,
	Mp4OutputFormat,
} from 'mediabunny';

// Check the above snippets for more examples of Input and Output
const input = new Input({
	formats: ALL_FORMATS,
	source: new BlobSource(file),
});
const output = new Output({
	format: new Mp4OutputFormat(),
	target: new BufferTarget(),
});

const conversion = await Conversion.init({ input, output });
conversion.discardedTracks; // List of tracks that won't make it into the output

conversion.onProgress = (progress) => {
	progress; // Number between 0 and 1, inclusive
};

await conversion.execute();
// Conversion is complete

const buffer = output.target.buffer; // ArrayBuffer containing the final MP4 file

INFO

  • This code will automatically transmux (copy media data) when possible, and transcode (re-encode media data) when necessary.
  • Refer to Converting media files for the full documentation.

Extract audio

ts
import {
	Input,
	Output,
	Conversion,
	WavOutputFormat,
} from 'mediabunny';

const input = new Input(...);
const output = new Output({
	// Write to a .wav file, keeping only the audio track
	format: new WavOutputFormat(),
	// ...
});

const conversion = await Conversion.init({
	input,
	output,
	audio: {
		sampleRate: 16000, // Resample to 16 kHz
	},
});
await conversion.execute();
// Conversion is complete

INFO

  • You can extract to other audio-only formats, such as .mp3, .ogg, or even .m4a. See Output formats.

Compress media

ts
import {
	Input,
	Output,
	Conversion,
	QUALITY_LOW,
} from 'mediabunny';

const input = new Input(...);
const output = new Output(...);

const conversion = await Conversion.init({
	input,
	output,
	video: {
		width: 480,
		bitrate: QUALITY_LOW,
	},
	audio: {
		numberOfChannels: 1,
		bitrate: QUALITY_LOW,
	},
	trim: {
		// Let's keep only the first 60 seconds
		start: 0,
		end: 60,
	},
});

await conversion.execute();
// Conversion is complete

INFO

Released under the Mozilla Public License 2.0.