dasha
May 17, 2026 ยท View on GitHub
Library for working with MPEG-DASH (.mpd) manifests and HLS (.m3u8) playlists through a Mediabunny-compatible Input API. Made with the purpose of obtaining a simplified representation convenient for further downloading of segments by URLs and getting basic metadata about the tracks.
Install
npm install dasha
Usage
Reading HLS
In the example below, we read the segment information for a specific video track and save it to a file.
import fs from 'node:fs/promises';
import { desc, HLS_FORMATS, Input, UrlSource } from 'dasha';
async function saveVideo() {
const input = new Input({
source: new UrlSource(
'https://storage.googleapis.com/shaka-demo-assets/angel-one-widevine-hls/hls.m3u8',
{ requestInit: { headers: { Referer: 'https://bitmovin.com/' } } },
),
formats: HLS_FORMATS,
});
const videoTracks = await input.getVideoTracks({
sortBy: async (track) => [
desc(await track.getDisplayHeight()),
// Tracks with matching resolution are sorted by bitrate
desc(await track.getBitrate()),
],
// Filter out #EXT-X-I-FRAME-STREAM-INF tracks
filter: async (track) => !(await track.hasOnlyKeyPackets()),
});
const bestVideoTrack = videoTracks[0];
console.log('Dynamic range:', await bestVideoTrack.getDynamicRange()); // sdr
const segments = await bestVideoTrack.getSegments();
const outputPath = 'output.mp4';
const urls = segments.map((segment) => segment.location.path);
const initSegment = segments[0]?.initSegment;
if (initSegment) urls.unshift(initSegment.location.path);
for (const url of urls) {
const content = await fetch(url).then((res) => res.arrayBuffer());
await fs.appendFile(outputPath, new Uint8Array(content));
}
};
Reading DASH
Everything here is identical to the example above, with the sole exception that an URL to a DASH manifest is used instead of an HLS playlist.
import fs from 'node:fs/promises';
import { DASH_FORMATS, Input, UrlSource, desc } from 'dasha';
async function saveDashVideo() {
const input = new Input({
source: new UrlSource(
'https://dash.akamaized.net/dash264/TestCases/1a/netflix/exMPD_BIP_TC1.mpd',
),
formats: DASH_FORMATS,
});
const videoTracks = await input.getVideoTracks({
sortBy: async (track) => [
desc(await track.getDisplayHeight()),
desc(await track.getBitrate()),
],
});
const bestVideoTrack = videoTracks[0];
const segments = await bestVideoTrack.getSegments();
const outputPath = 'output.m4s';
const urls = segments.map((segment) => segment.location.path);
const initSegment = segments[0]?.initSegment;
if (initSegment) urls.unshift(initSegment.location.path);
for (const url of urls) {
const content = await fetch(url).then((res) => res.arrayBuffer());
await fs.appendFile(outputPath, new Uint8Array(content));
}
}
Adding external subtitle tracks
addSubtitleTrack() is useful when subtitle URLs are provided separately from the HLS/DASH manifest. Added subtitles become part of the same Input, so they can be queried, filtered and downloaded through the regular subtitle track API.
import { DASH_FORMATS, Input, UrlSource } from 'dasha';
async function getEnglishSubtitles() {
const input = new Input({
source: new UrlSource('https://example.com/manifest.mpd'),
formats: DASH_FORMATS,
});
const primaryVideoTrack = await input.getPrimaryVideoTrack();
if (!primaryVideoTrack) {
throw new Error('No video tracks found');
}
input.addSubtitleTrack(new UrlSource('https://cdn.example.com/subtitles/en.vtt'), {
languageCode: 'en',
name: 'English',
pairWith: primaryVideoTrack,
});
const englishSubtitleTracks = await input.getSubtitleTracks({
filter: async (track) => (await track.getLanguageCode()) === 'en',
});
const englishSubtitleTrack = englishSubtitleTracks[0];
if (!englishSubtitleTrack) {
return [];
}
return await englishSubtitleTrack.getSegments();
}
Overriding track metadata
Some services expose track metadata outside the manifest. You can override the parsed language code on any dasha track, and the updated value will be visible through the regular query/filter APIs.
import { DASH_FORMATS, Input, UrlSource } from 'dasha';
async function getFrenchAudioTrack() {
const input = new Input({
source: new UrlSource('https://example.com/manifest.mpd'),
formats: DASH_FORMATS,
});
const audioTracks = await input.getAudioTracks();
const apiLanguageCode = 'fr';
audioTracks[0]?.setLanguageCode(apiLanguageCode);
return await input.getAudioTracks({
filter: async (track) => (await track.getLanguageCode()) === apiLanguageCode,
});
}
Mediabunny with DASH support
Only reading is supported
Similar to downloading an HLS playlist as an MP4 you can do this:
import { Conversion, FilePathTarget, Mp4OutputFormat, Output, Input, UrlSource } from 'mediabunny';
import { DASH_FORMATS } from 'dasha';
const input = new Input({
source: new UrlSource('https://example.com/manifest.mpd'),
formats: DASH_FORMATS,
});
const output = new Output({
format: new Mp4OutputFormat(),
target: new FilePathTarget('output.mp4'),
});
const conversion = await Conversion.init({ input, output });
await conversion.execute();
// Done
See reading HLS guide for more use cases (many things can be used with DASH as well).