Mediabunny Documentation

repository·main·Indexed 27 days ago

https://github.com/vanilagy/mediabunny

A high-performance, zero-dependency TypeScript media toolkit for reading, writing, and converting media files (MP4, WebM, MP3, etc.) directly in the browser or server environments like Node, Bun, and Deno. Includes extensions for AAC, AC-3/E-AC-3, FLAC, MP3, and ProRes encoding/decoding via WASM, as well as server-side capabilities via @mediabunny/server.

Tokens
61.9K
Snippets
169
Records
317
Agent score
92%

What's inside mediabunny

  1. Overview of Mediabunny

    main

    Mediabunny is a zero-dependency, tree-shakable TypeScript library designed for reading, writing, and converting media files (such as MP4, WebM, MOV, MKV, etc.) directly in the browser. It functions as a high-performance media toolkit similar to FFmpeg but optimized for web environments using the WebCodecs API.

    Key capabilities include:

    • Metadata & Data Extraction: Read metadata and extract media data from various container formats.
    • File Creation & Conversion: Create new media files and perform transmuxing or transcoding.
    • Hardware Acceleration: Utilizes the WebCodecs API for efficient decoding and encoding.
    • Format Support: Supports many containers (.mp4, .mov, .webm, .mkv, .mp3, .wav, .ogg, .aac, .flac, .ts) and HLS (VOD and live).
    • Media Processing: Utilities for compression, resizing, rotation, cropping, resampling, and trimming.
    • Streaming: Supports input/output streaming and arbitrary file sizes with file location independence (memory, disk, network).
  2. Understand the Mediabunny Codec Registry

    main
    The Mediabunny Codec Registry defines the precise formats for all supported video and audio codecs. It specifies the requirements for EncodedPacket, VideoDecoderConfig, and AudioDecoderConfig. All media packets entering or leaving the Mediabunny ecosystem must adhere to these definitions to ensure compatibility. The registry is an extension of the WebCodecs Codec Registry and maintains parity with WebCodecs for all shared codecs.
  3. Understand @mediabunny/server implementation details

    main

    @mediabunny/server is built on top of NodeAV, which provides N-API C bindings to FFmpeg's C API. It implements custom decoders and encoders by directly utilizing libavcodec APIs.

    Key architectural features include:

    • Encoding Path: Video frames and audio samples are converted to AVFrame instances and passed to the appropriate encoder. Resulting packets are normalized for compatibility with WebCodecs and the Mediabunny Codec Registry.
    • Decoding Path: Packets and decoder metadata are passed to the decoder, and the resulting AVFrame instances are wrapped in VideoSample or AudioSample instances.
    • Transformations: Video frame transformations (such as resize, rotate, and crop) are implemented using the libavfilter API.
    • Zero-copy optimization: Whenever possible, AVFrames are not copied to JavaScript unless explicitly required, enabling zero-copy paths for decode -> transformation -> encode workflows.
    • ProRes Support: ProRes decoding is powered by TurboRes for improved performance over standard FFmpeg decoding.
  4. Understand the difference between Packets and Samples

    main

    Mediabunny handles media data in two primary forms:

    • Packet (EncodedPacket): Encoded media data resulting from an encoding process. These are used for both video and audio.
    • Sample (VideoSample or AudioSample): Raw, uncompressed, presentable media data. VideoSample represents a single video frame, while AudioSample represents a section of audio.

    Workflow:

    • Samples are encoded into Packets.
    • Packets are decoded into Samples.
  5. Explore Mediabunny feature examples

    main

    Mediabunny provides several specialized modules and capabilities for media processing. You can implement the following features using the toolkit:

    • Metadata extraction: Extract various metadata from an input media file.
    • Thumbnail generation: Generate multiple small thumbnails for a video track.
    • Media player (advanced): Implement a full video & audio media player with microsecond playback accuracy.
    • File compression: Convert input files to highly-compressed formats (e.g., MP4).
    • Procedural video generation: Generate video files at hardware-limited speeds.
    • Live recording & streaming: Record video from live sources and stream it to a video element.
    • HLS transcoding: Convert a single video into a full HLS manifest containing multiple video renditions and audio tracks.
  6. Use composable conversions to contribute to a single output

    main

    By default, a Conversion manages the entire lifecycle of an Output. If you want a conversion to be one of several contributors to a single output file (e.g., keeping an input's video track while adding an external audio track), set composable: true.

    When composable is true:

    • The conversion only adds its own tracks and pumps media data during execute().
    • You are responsible for the Output lifecycle: you must call output.start(), output.finalize(), add any additional tracks, and set metadata tags.
    • You can have multiple conversions targeting a single Output.
    import {
    	Input,
    	Output,
    	Mp4OutputFormat,
    	BufferTarget,
    	Conversion,
    	AudioBufferSource,
    } from 'mediabunny';
    
    const input = new Input({ ... });
    const output = new Output({
    	format: new Mp4OutputFormat(),
    	target: new BufferTarget(),
    });
    
    // Use the conversion only to copy over the video
    const conversion = await Conversion.init({
    	input,
    	output,
    	audio: { discard: true },
    	composable: true,
    });
    
    // Add our own audio track directly
    const audioSource = new AudioBufferSource({ codec: 'aac', bitrate: 128e3 });
    output.addAudioTrack(audioSource);
    
    // Start the output
    await output.start();
    
    // Run the conversion concurrently with feeding our own audio
    await Promise.all([
    	conversion.execute(),
    	audioSource.add(myAudioBuffer).then(() => audioSource.close()),
    ]);
    
    // Finalize the output
    await output.finalize();
  7. Configure supported formats in Input

    main

    When instantiating a new Input, you can provide a list of supported container formats via the formats option. You can pass specific singletons or use the ALL_FORMATS constant to support every available format.

    Warning: Using ALL_FORMATS includes demuxers for all formats, which can significantly increase your bundle size.

  8. Use Media Sinks to extract media data

    main

    Media sinks are scoped to a specific InputTrack and provide different levels of abstraction for retrieving media data. Constructing a sink is a lightweight operation and does not trigger any media reads.

    To use a sink, obtain a track from your input and pass it to the sink constructor:

    const track = await input.getPrimaryVideoTrack();
    const sink = new FooSink(track);

    Media sinks are stateless (except for CanvasSink when using a canvas pool), meaning you can call their retrieval methods independently multiple times.

    const track = await input.getPrimaryVideoTrack();
    const sink = new FooSink(track);
  9. Implement a custom encoder

    main

    To polyfill a codec or use Mediabunny in environments without WebCodecs (like Node.js), you can register a custom encoder.

    Steps to implement:

    1. Create a class that extends CustomVideoEncoder or CustomAudioEncoder.
    2. Implement the required static supports(codec, config) method. This method must return true if your encoder can handle the provided codec and configuration; if it returns true, Mediabunny will use your encoder instead of the default.
    3. Implement the lifecycle methods: init(), encode(), flush(), and close().
    4. In the encode() method, you must call the provided onPacket method for every encoded packet created.
    5. Register the class using registerEncoder(YourEncoderClass).

    Important Requirements:

    • Packets passed to onPacket must be in decode order.
    • flush() must resolve only after all samples are encoded and must reset internal state for the next batch.
    • All instance methods can return Promises; Mediabunny will serialize these calls to ensure they do not run concurrently.
  10. Register AC-3 decoder and encoder in Mediabunny

    main

    To enable AC-3 and E-AC-3 support within Mediabunny, you must register the decoder and encoder using the provided registration functions. Once registered, Mediabunny will automatically use these coders when encountering AC-3 or E-AC-3 media.

    Import registerAc3Decoder and registerAc3Encoder from @mediabunny/ac3 and call them during your application setup.

    import { registerAc3Decoder, registerAc3Encoder } from '@mediabunny/ac3';
    
    registerAc3Decoder();
    registerAc3Encoder();
  11. Install @mediabunny/flac-encoder

    main

    The @mediabunny/flac-encoder package is a FLAC encoder polyfill for use in browsers and on the server. It is a peer-dependency of mediabunny.

    Using npm:

    npm install mediabunny @mediabunny/flac-encoder

    Using script tags (Browser): Include the following scripts to expose the global objects Mediabunny and MediabunnyFlacEncoder:

    <script src="mediabunny.js"></script>
    <script src="mediabunny-flac-encoder.js"></script>
  12. Add a video overlay using Canvas

    main

    Apply visual overlays (like watermarks) by using the video.process callback in Conversion.init. This callback receives a sample and allows you to draw onto a CanvasRenderingContext2D. You can use an OffscreenCanvas to composite images or other graphics onto the video frames.

    import {
    	Input,
    	Output,
    	Conversion,
    } from 'mediabunny';
    
    // For example, let's load a watermark image
    const watermark = new Image();
    watermark.src = '/watermark.jpg';
    await new Promise(resolve => watermark.onload = resolve);
    
    const input = new Input(...);
    const output = new Output(...);
    
    let ctx: CanvasRenderingContext2D | null = null;
    const conversion = await Conversion.init({
    	input,
    	output,
    	video: {
    		process: (sample) => {
    			if (!ctx) {
    				// Create a canvas for image compositing
    				const canvas = new OffscreenCanvas(
    					sample.displayWidth,
    					sample.displayHeight,
    				);
    				ctx = canvas.getContext('2d')!;
    			}
    
    			ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    			sample.draw(ctx, 0, 0);
    			ctx.drawImage(watermark, 32, 32);
    
    			return ctx.canvas;
    		},
    	},
    });
    
    await conversion.execute();
    // Conversion is complete