v1.42.0

Vanilagy/mediabunnyv1.42.0Apr 28, 2026by Vanilagy

AI Summary

This major release introduces full HLS playlist support (read/write), multi-file source/target systems, track querying, live streaming, and pre-processing, while deprecating synchronous track accessors.

Key Highlights

  • Major addition of HLS read/write support
  • Introduction of multi-file Input/Output systems
  • New track query and pairability systems
  • Added live track support and pre-processing transforms
  • Deprecated synchronous track properties in favor of async getters

Breaking Changes

  • Deprecation of synchronous track properties (e.g., `codec`, `codedWidth`, `sampleRate`) in favor of async methods (e.g., `getCodec()`, `getCodedWidth()`, `getSampleRate()`)
  • Event listeners changed from properties (`onread`, `onwrite`) to methods (`on('read', ...)`) on Source and Target

New Features

  • HLS read/write support
  • CMAF output format
  • Multi-file PathedSource and PathedTarget
  • Track query system (InputTrackQuery)
  • Pre-processing transforms for video and audio
  • Live track support with polling
  • EventEmitter base class
  • ConcurrentRunner helper

Full Release Notes

The biggest update yet: this one adds HLS read/write support to Mediabunny, a long-requested feature. HLS playlists consist of many files, each describing a small segment of media data. Mediabunny's HLS API completely abstracts this away: you can read HLS playlists as if they were single media files, meaning they plug into your existing logic without much issue. Same goes for writing!

Still, HLS brought a lot of new challenges to Mediabunny and required a lot of additions across the entire API surface to adequately solve. That's why even beyond HLS, the API is now much more flexible and powerful.

## New features

- Added support for reading HLS playlists, facilitated by `HlsInputFormat` and the `HLS` singleton. Support for an arbitrary amount video and audio tracks, I-frame only tracks, VOD playlists, live playlists, encrypted playlists, DATE-TIME-tagged segments, BYTERANGE-addressed segments, and more. All segment file formats are supported (MPEG-TS, fMP4, CMAF, ADTS, MP3, ...). For a detailed guide, see the [Reading HLS](https://mediabunny.dev/guide/reading-hls) guide.
- Added support for writing HLS playlists via `HlsOutputFormat`. Supports arbitrary video/audio track configurations, master playlist configuration, both VOD and live writing, with fully configurable output file structure. All segment file formats are supported (MPEG-TS, fMP4, CMAF, ADTS, MP3, ...). For a detailed guide, see the [Writing HLS](https://mediabunny.dev/guide/writing-hls) guide.
- Added `CmafOutputFormat`, a way of writing Segmented MP4 files which are split into a segment file and a separate init file.
- Added multi-file `Inputs` via `PathedSource`; sources that can return other sources for a given file path. `UrlSource` and `FilePathSource` are pathed out of the gate; custom multi-file sources can be constructed using `CustomPathedSource`. Required for reading HLS as it is multi-file.
- Added multi-file `Outputs` via `PathedTarget`, which return a new target for a given file path. Fully customizable. Required for writing HLS as it is multi-file.
- Added async getters for most data on `InputTrack`, such as `.getCodec()`, `.getDisplayWidth()`, etc. Required due to lazy loading requirements added by HLS.
- Added live tracks: tracks that are currently still generating media data. You can probe liveness with `InputTrack.isLive()` and obtain a refresh interval via `InputTrack.getLiveRefreshInterval()`, which you can use to poll the live edge for updates.
- Added `Input.getDurationFromMetadata()` and `InputTrack.getDurationFromMetadata()`, a new way to get a file's duration efficiently without needing to find the last packet. Instead, it extracts the duration from metadata in the file. If there is no such metadata, `null` is returned.
- Added an input track query system via `InputTrackQuery`, allowing for expressing filtering and sorting of tracks. Useful for finding tracks in many-track inputs. Track queries are accepted by `Input.getTracks()`, `Input.getVideoTracks()`, `Input.getAudioTracks()`, `Input.getPrimaryVideoTrack()`, and `Input.getPrimaryAudioTrack()`. Added `asc`, `desc` and `prefer` helpers to help write sorters.
- Added input track pairability: a metric that describes if two tracks can be presented together. Can be checked via `a.canBePairedWith(b)`. Additional helper methods exist on `InputTrack`: `.getPairableTracks()`, `.getPairableVideoTracks()`, `.getPairableAudioTracks()`, `.getPrimaryPairableVideoTrack()`, `.getPrimaryPairableAudioTrack()`, `.hasPairableTrack()`, `.hasPairableVideoTrack()`, `.hasPairableAudioTrack()`.
- Added a video pre-processing system via `VideoEncodingConfig.transform`, which allows you to transform (resize/rotate/etc.) frames right before they get passed to the encoder. Useful for generating multiple HLS variants.
- Added an audio pre-processing system via `AudioEncodingConfig.transform`, which allows you to transform (resample/remix) audio data right before they get passed to the encoder. Useful for generating multiple HLS variants.
- Added output track fan-out to the Conversion API: you can now create multiple output tracks from a single input track. Great for creating multiple renditions of a video track, for example.
- Added `OutputTrack`, `OutputVideoTrack`, `OutputAudioTrack` and `OutputSubtitleTrack`, returned by the `Output.add*Track` methods
- Added an output track grouping system, used to define track pairings for HLS outputs. Create `OutputTrackGroup`s, then assign tracks to them via `BaseTrackMetadata.group`.
- Added `TrackDisposition.primary` to indicate that a track is the primary, main track. Corresponds to HLS's `DEFAULT` attribute.
- Added Unix epoch-relative tracks where timestamps are relative to Jan 1 1970. Useful for syncing data to a real-time clock. Exposed via `InputTrack.isRelativeToUnixEpoch()`. Such tracks can also be written via `BaseTrackMetadata.isRelativeToUnixEpoch`.
- Added `InputOptions.formatOptions`, a way to provide input format-specific configuration. For now, this can be used to provide decryption keys for DRM-protected content.
- Added `Input.canRead()`, resolving to `true` iff Mediabunny can read the file.
- `Input.getFirstTimestamp()` now takes an optional `InputTrack[]` as first argument; if provided, the first timestamp is only computed for the given tracks.
- `Input.computeDuration()` now takes two optional arguments: `InputTrack[]` and `PacketRetrievalOptions`. When tracks are provided, the duration is only computed for the given tracks. The packet retrieval options allow you to configure behavior for live streams using `skipLiveWait`.
- Added `InputOptions.initInput`, a way to specify a separate init file that carries track initialization data. Can be used with segmented MP4 and MPEG-TS.
- Added `OutputOptions.initTarget`, which is used to write a separate init file for the formats that require it, such as CMAF.
- Added `InputTrack.hasOnlyKeyPackets()`, returning `true` for tracks that only contain key packets, such as audio tracks or #EXT-X-I-FRAME-STREAM-INF HLS tracks.
- Added `InputTrack.getBitrate()` and `InputTrack.getAverageBitrate()` for retrieving a track's peak and average bitrate respectively, as specified in file metadata.
- `InputTrack.computeDuration()` now accepts an optional `PacketRetrievalOptions` argument that can be used to configure behavior for live streams using `skipLiveWait`.
- Added `PacketRetrievalOptions.skipLiveWait`, can be used to disable waiting for the live stream to catch up when requesting media data beyond the live edge.
- Added `OutputOptions.onFinalize`, a callback which is called right before Output finalization completes.
- `BufferTarget` now takes an optional first argument `BufferTargetOptions`, which can be used to set the `onFinalize` callback which allows you to do work as soon as the buffer has been finalized.
- Added `ConversionOptions.tracks` which can be `'all'` or `'primary'`; used to control the tracks that are considered for conversion.
- More media sinks now accept `PacketRetrievalOptions` in their methods: `AudioBufferSink`, `AudioSampleSink`, `CanvasSink`, `VideoSampleSink`. Required for accessing `skipLiveWait`.
- Added global decodability checker helper functions: `canDecode`, `canDecodeAudio`, `canDecodeVideo`, `getDecodableCodecs`, `getDecodableAudioCodecs`, `getDecodableVideoCodecs`,
- Added `Source`/`Target` ranging; a new `.slice()` method returns a `RangedSource`/`RangedTarget` which maps to a subset of the original source/target.
- Added a new reference counting system for sources via `SourceRef`. A ref can be acquired via `source.ref()`, and only when the last ref has been `.free()`d will the source be disposed.
- Added `AppendOnlyStreamTarget`, which is like `StreamTarget` except it accepts `WritableStream<Uint8Array>`, meaning it models purely sequential writes.
- Added `EventEmitter`, a new base class for listening to events via an `.on` method. Subclassed by `Input`, `Output`, `Source` and `Target`.
- Added a `source` event for `Input`, emitted every time a new source is acquired
- Added a `target` event for `Output`, emitted every time a new target is acquired
- Added a `finalized` event to `Target`, emitted after the target has been finalized
- Added `ConcurrentRunner`, a helper class for orchestrating parallel async tasks; useful for coordinating parallel HTTP uploads.
- MPEG-TS files no longer need to begin with a key frame, as long as the codec parameters are found somewhere in the remaining packets. Zero key frames can also be present, but then an `initInput` is necessary.
- `UrlSource` now supports responses with unknown length (no `Content-Length` or `Content-Range` headers present)
- `Conversion.onProgress` now receives a second `processedTime` argument
- Added `timestampBase` option for `MediaStreamVideoTrackSource` and `MediaStreamAudioTrackSource`, allowing you to define the zero point for the media sample timestamps

## Changes

- `getTracks()` now always returns tracks in the order in which they appear in the input file
- `VideoEncodingConfig.keyFrameInterval` now defaults to 2 seconds (was 5 previously)
- `ConversionOptions.trim.start` now defaults to the start timestamp of all non-user-discarded tracks, instead of all tracks.

## Deprecations

- `InputTrack.codec`. Prefer `await inputTrack.getCodec()` instead.
- `InputTrack.internalCodecId`. Prefer `await inputTrack.getInternalCodecId()` instead.
- `InputTrack.languageCode`. Prefer `await inputTrack.getLanguageCode()` instead.
- `InputTrack.name`. Prefer `await inputTrack.getName()` instead.
- `InputTrack.timeResolution`. Prefer `await inputTrack.getTimeResolution()` instead.
- `InputTrack.disposition`. Prefer `await inputTrack.getDisposition()` instead.
- `InputVideoTrack.codedWidth`. Prefer `await inputVideoTrack.getCodedWidth()` instead.
- `InputVideoTrack.codedHeight`. Prefer `await inputVideoTrack.getCodedHeight()` instead.
- `InputVideoTrack.displayWidth`. Prefer `await inputVideoTrack.getDisplayWidth()` instead.
- `InputVideoTrack.displayHeight`. Prefer `await inputVideoTrack.getDisplayHeight()` instead.
- `InputVideoTrack.squarePixelWidth`. Prefer `await inputVideoTrack.getSquarePixelWidth()` instead.
- `InputVideoTrack.squarePixelHeight`. Prefer `await inputVideoTrack.getSquarePixelHeight()` instead.
- `InputVideoTrack.pixelAspectRatio`. Prefer `await inputVideoTrack.getPixelAspectRatio()` instead.
- `InputVideoTrack.rotation`. Prefer `await inputVideoTrack.getRotation()` instead.
- `InputAudioTrack.numberOfChannels`. Prefer `await inputAudioTrack.getNumberOfChannels()` instead.
- `InputAudioTrack.sampleRate`. Prefer `await inputAudioTrack.getSampleRate()` instead.
- `Source.onread`. Prefer `.on('read', ...)` instead.
- `Target.onwrite`. Prefer `.on('write', ...)` instead.

## Fixes
- Fixed rare track closing race conditions in multiple muxers
- Fixed off-by-one reorder buffer error in MPEG-TS demuxer
- Fixed ISOBMFF muxer duration calculation
- Fixed multiple ISOBMFF trun boxes not being supported
- Fixed incorrect MP3 Xing TOC frame locations
- Codec string `'mp4a.40.34'` is now correctly identified as MP3
- `UrlSource` no longer always requests byte 0 to determine the file size, it can do that from any starting offset now
- `ReadableStreamSource`'s reader is now properly canceled on dispose
- `UrlSource.parallelism` actually now controlls the amount of parallel requests, guaranteed
- Fixed missing null terminator at the end of TXXX ID3v2 frame
- Fixed being unable to access the file system in server-side environments when using the CommonJS build
- Fixed encoder/decoder extension packages struggling with huge timestamps beyond the int32 limit

## Full list of API additions

### New types/classes/functions

* [`HLS`](https://mediabunny.dev/api/HLS)
* [`HLS_FORMATS`](https://mediabunny.dev/api/HLS_FORMATS)
* [`HlsInputFormat`](https://mediabunny.dev/api/HlsInputFormat)
* [`HlsOutputFormat`](https://mediabunny.dev/api/HlsOutputFormat)
* [`HlsOutputFormatOptions`](https://mediabunny.dev/api/HlsOutputFormatOptions)
* [`HlsOutputPlaylistInfo`](https://mediabunny.dev/api/HlsOutputPlaylistInfo)
* [`HlsOutputSegmentInfo`](https://mediabunny.dev/api/HlsOutputSegmentInfo)
* [`CmafOutputFormat`](https://mediabunny.dev/api/CmafOutputFormat)
* [`CmafOutputFormatOptions`](https://mediabunny.dev/api/CmafOutputFormatOptions)
* [`FilePath`](https://mediabunny.dev/api/FilePath)
* [`PathedSource`](https://mediabunny.dev/api/PathedSource)
* [`CustomPathedSource`](https://mediabunny.dev/api/CustomPathedSource)
* [`PathedTarget`](https://mediabunny.dev/api/PathedTarget)
* [`SourceRequest`](https://mediabunny.dev/api/SourceRequest)
* [`TargetRequest`](https://mediabunny.dev/api/TargetRequest)
* [`RangedSource`](https://mediabunny.dev/api/RangedSource)
* [`RangedTarget`](https://mediabunny.dev/api/RangedTarget)
* [`AppendOnlyStreamTarget`](https://mediabunny.dev/api/AppendOnlyStreamTarget)
* [`SourceRef`](https://mediabunny.dev/api/SourceRef)
* [`EventEmitter`](https://mediabunny.dev/api/EventEmitter)
* [`EventListenerOptions`](https://mediabunny.dev/api/EventListenerOptions)
* [`SourceEvents`](https://mediabunny.dev/api/SourceEvents)
* [`TargetEvents`](https://mediabunny.dev/api/TargetEvents)
* [`InputEvents`](https://mediabunny.dev/api/InputEvents)
* [`OutputEvents`](https://mediabunny.dev/api/OutputEvents)
* [`OutputTrack`](https://mediabunny.dev/api/OutputTrack)
* [`OutputVideoTrack`](https://mediabunny.dev/api/OutputVideoTrack)
* [`OutputAudioTrack`](https://mediabunny.dev/api/OutputAudioTrack)
* [`OutputSubtitleTrack`](https://mediabunny.dev/api/OutputSubtitleTrack)
* [`OutputTrackGroup`](https://mediabunny.dev/api/OutputTrackGroup)
* [`InputTrackQuery`](https://mediabunny.dev/api/InputTrackQuery)
* [`asc`](https://mediabunny.dev/api/asc)
* [`desc`](https://mediabunny.dev/api/desc)
* [`prefer`](https://mediabunny.dev/api/prefer)
* [`canDecode`](https://mediabunny.dev/api/canDecode)
* [`canDecodeAudio`](https://mediabunny.dev/api/canDecodeAudio)
* [`canDecodeVideo`](https://mediabunny.dev/api/canDecodeVideo)
* [`getDecodableCodecs`](https://mediabunny.dev/api/getDecodableCodecs)
* [`getDecodableAudioCodecs`](https://mediabunny.dev/api/getDecodableAudioCodecs)
* [`getDecodableVideoCodecs`](https://mediabunny.dev/api/getDecodableVideoCodecs)
* [`AudioTransformOptions`](https://mediabunny.dev/api/AudioTransformOptions)
* [`VideoTransformOptions`](https://mediabunny.dev/api/VideoTransformOptions)
* [`UnsupportedInputFormatError`](https://mediabunny.dev/api/UnsupportedInputFormatError)
* [`ConcurrentRunner`](https://mediabunny.dev/api/ConcurrentRunner)
* [`DurationMetadataRequestOptions`](https://mediabunny.dev/api/DurationMetadataRequestOptions)
* [`SetOptional`](https://mediabunny.dev/api/SetOptional)
* [`IsobmffInputFormatOptions`](https://mediabunny.dev/api/IsobmffInputFormatOptions)
* [`InputFormatOptions`](https://mediabunny.dev/api/InputFormatOptions)
* [`BufferTargetOptions`](https://mediabunny.dev/api/BufferTargetOptions)
* [`PsshBox`](https://mediabunny.dev/api/PsshBox)
* [`MediaStreamAudioTrackSourceOptions`](https://mediabunny.dev/api/MediaStreamAudioTrackSourceOptions)

### New fields

* [`InputTrack.getCodec`](https://mediabunny.dev/api/InputTrack#getcodec)
* [`InputTrack.getInternalCodecId`](https://mediabunny.dev/api/InputTrack#getinternalcodecid)
* [`InputTrack.getLanguageCode`](https://mediabunny.dev/api/InputTrack#getlanguagecode)
* [`InputTrack.getName`](https://mediabunny.dev/api/InputTrack#getname)
* [`InputTrack.getTimeResolution`](https://mediabunny.dev/api/InputTrack#gettimeresolution)
* [`InputTrack.getDisposition`](https://mediabunny.dev/api/InputTrack#getdisposition)
* [`InputTrack.hasOnlyKeyPackets`](https://mediabunny.dev/api/InputTrack#hasonlykeypackets)
* [`InputTrack.isRelativeToUnixEpoch`](https://mediabunny.dev/api/InputTrack#isrelativetounixepoch)
* [`InputTrack.isLive`](https://mediabunny.dev/api/InputTrack#islive)
* [`InputTrack.getLiveRefreshInterval`](https://mediabunny.dev/api/InputTrack#getliverefreshinterval)
* [`InputTrack.getBitrate`](https://mediabunny.dev/api/InputTrack#getbitrate)
* [`InputTrack.getAverageBitrate`](https://mediabunny.dev/api/InputTrack#getaveragebitrate)
* [`InputTrack.getDurationFromMetadata`](https://mediabunny.dev/api/InputTrack#getdurationfrommetadata)
* [`InputTrack.canBePairedWith`](https://mediabunny.dev/api/InputTrack#canbepairedwith)
* [`InputTrack.getPairableTracks`](https://mediabunny.dev/api/InputTrack#getpairabletracks)
* [`InputTrack.getPairableVideoTracks`](https://mediabunny.dev/api/InputTrack#getpairablevideotracks)
* [`InputTrack.getPairableAudioTracks`](https://mediabunny.dev/api/InputTrack#getpairableaudiotracks)
* [`InputTrack.getPrimaryPairableVideoTrack`](https://mediabunny.dev/api/InputTrack#getprimarypairablevideotrack)
* [`InputTrack.getPrimaryPairableAudioTrack`](https://mediabunny.dev/api/InputTrack#getprimarypairableaudiotrack)
* [`InputTrack.hasPairableTrack`](https://mediabunny.dev/api/InputTrack#haspairabletrack)
* [`InputTrack.hasPairableVideoTrack`](https://mediabunny.dev/api/InputTrack#haspairablevideotrack)
* [`InputTrack.hasPairableAudioTrack`](https://mediabunny.dev/api/InputTrack#haspairableaudiotrack)
* [`InputVideoTrack.getCodedWidth`](https://mediabunny.dev/api/InputVideoTrack#getcodedwidth)
* [`InputVideoTrack.getCodedHeight`](https://mediabunny.dev/api/InputVideoTrack#getcodedheight)
* [`InputVideoTrack.getRotation`](https://mediabunny.dev/api/InputVideoTrack#getrotation)
* [`InputVideoTrack.getSquarePixelWidth`](https://mediabunny.dev/api/InputVideoTrack#getsquarepixelwidth)
* [`InputVideoTrack.getSquarePixelHeight`](https://mediabunny.dev/api/InputVideoTrack#getsquarepixelheight)
* [`InputVideoTrack.getPixelAspectRatio`](https://mediabunny.dev/api/InputVideoTrack#getpixelaspectratio)
* [`InputVideoTrack.getDisplayWidth`](https://mediabunny.dev/api/InputVideoTrack#getdisplaywidth)
* [`InputVideoTrack.getDisplayHeight`](https://mediabunny.dev/api/InputVideoTrack#getdisplayheight)
* [`InputAudioTrack.getNumberOfChannels`](https://mediabunny.dev/api/InputAudioTrack#getnumberofchannels)
* [`InputAudioTrack.getSampleRate`](https://mediabunny.dev/api/InputAudioTrack#getsamplerate)
* [`Input.canRead`](https://mediabunny.dev/api/Input#canread)
* [`Input.getDurationFromMetadata`](https://mediabunny.dev/api/Input#getdurationfrommetadata)
* [`Source.slice`](https://mediabunny.dev/api/Source#slice)
* [`Source.ref`](https://mediabunny.dev/api/Source#ref)
* [`Target.slice`](https://mediabunny.dev/api/Target#slice)
* [`InputOptions.initInput`](https://mediabunny.dev/api/InputOptions#initinput)
* [`InputOptions.formatOptions`](https://mediabunny.dev/api/InputOptions#formatoptions)
* [`OutputOptions.initTarget`](https://mediabunny.dev/api/OutputOptions#inittarget)
* [`OutputOptions.onFinalize`](https://mediabunny.dev/api/OutputOptions#onfinalize)
* [`Output.defaultTrackGroup`](https://mediabunny.dev/api/Output#defaulttrackgroup)
* [`BaseTrackMetadata.isRelativeToUnixEpoch`](https://mediabunny.dev/api/BaseTrackMetadata#isrelativetounixepoch)
* [`BaseTrackMetadata.group`](https://mediabunny.dev/api/BaseTrackMetadata#group)
* [`VideoTrackMetadata.hasOnlyKeyPackets`](https://mediabunny.dev/api/VideoTrackMetadata#hasonlykeypackets)
* [`AudioEncodingConfig.transform`](https://mediabunny.dev/api/AudioEncodingConfig#transform)
* [`VideoEncodingConfig.transform`](https://mediabunny.dev/api/VideoEncodingConfig#transform)
* [`PacketRetrievalOptions.skipLiveWait`](https://mediabunny.dev/api/PacketRetrievalOptions#skiplivewait)
* [`TrackDisposition.primary`](https://mediabunny.dev/api/TrackDisposition#primary)
* [`ConversionOptions.tracks`](https://mediabunny.dev/api/ConversionOptions#tracks)
* [`ConversionVideoOptions.group`](https://mediabunny.dev/api/ConversionVideoOptions#group)
* [`ConversionAudioOptions.group`](https://mediabunny.dev/api/ConversionAudioOptions#group)
* [`DiscardedTrack.trackOptions`](https://mediabunny.dev/api/DiscardedTrack#trackoptions)
* [`MediaStreamVideoTrackSourceOptions.timestampBase`](https://mediabunny.dev/api/MediaStreamVideoTrackSourceOptions#timestampbase)