Skip to main content

Intro

What is a manifest?​

A manifest describes the stream: what is available and where to get it. A segment contains a small piece of the actual audio or video.

Loading the manifest does not download the movie. It tells the player which media is available and where to request it.

What the player learns​

Video quality
Available bitrates, resolutions, and frame rates.
Browser compatibility
The codecs and containers needed to play the media.
Segment locations and timing
Where to download each piece and where it belongs on the timeline.
Audio and subtitles
Available languages, audio tracks, and captions.
Content protection
Encryption and key information for protected media.
Live or finished
Whether the stream is ongoing and which time range is available.

Real manifests: the same demo in two formats​

These are shortened excerpts from Shaka's public Angel One demo. Open the linked files for the complete manifests; the excerpts below are for reading.

DASH: .mpd​

Open the real DASH manifest. This excerpt keeps one video representation and omits the other tracks and some attributes.

dash.mpd — shortened excerpt
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" type="static" mediaPresentationDuration="PT60S" minBufferTime="PT2S">
<Period id="0">
<AdaptationSet id="6" contentType="video">
<Representation id="6" bandwidth="492476" codecs="avc1.4d400d" mimeType="video/mp4" width="300" height="240">
<BaseURL>video_240p_242k_h264.mp4</BaseURL>
<SegmentBase indexRange="860-1071" timescale="12800">
<Initialization range="0-859"/>
</SegmentBase>
</Representation>
</AdaptationSet>
</Period>
</MPD>

Read the opening line: type="static" describes a fixed presentation. PT60S means 60 seconds. The representation gives the bitrate, codec, and dimensions; BaseURL identifies the media file. Here, segments are addressed within an MP4 using byte ranges, rather than separate segment files.

HLS: .m3u8​

Open the real HLS master playlist. It lists qualities and audio/subtitle choices. To see segment durations and whether playback ends, follow a quality's media playlist:

240p media playlist — middle segments omitted
#EXTM3U
#EXT-X-VERSION:6
#EXT-X-TARGETDURATION:5
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-MAP:URI="v-0240p-0400k-libx264-init.mp4"
#EXTINF:4.000,
v-0240p-0400k-libx264-s1.mp4
#EXTINF:4.000,
v-0240p-0400k-libx264-s2.mp4
# ... segments 3 through 14 omitted here ...
#EXTINF:4.000,
v-0240p-0400k-libx264-s15.mp4
#EXT-X-ENDLIST

Read the end marker: EXT-X-ENDLIST says no more segments will be added. EXTINF:4.000 gives the next segment's duration: four seconds. The full playlist has 15 such segments, totaling 60 seconds. EXT-X-MAP points to initialization data.

Multiple qualities, audio languages, and DRM​

This example offers 720p and 1080p video, with English and Hindi audio. Audio and video are separate tracks: the player downloads the chosen video quality and audio language together, then synchronizes playback using their timestamps.

These are illustrative formats. The paths and key IDs are placeholders, and the media must actually be encrypted to match the manifest's DRM metadata.

HLS with FairPlay​

The master playlist connects both video qualities to the same audio group:

master.m3u8
#EXTM3U
#EXT-X-VERSION:7
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="English",LANGUAGE="en",DEFAULT=YES,AUTOSELECT=YES,URI="audio/en/index.m3u8"
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="Hindi",LANGUAGE="hi",DEFAULT=NO,AUTOSELECT=YES,URI="audio/hi/index.m3u8"
#EXT-X-STREAM-INF:BANDWIDTH=3000000,RESOLUTION=1280x720,CODECS="avc1.4d401f,mp4a.40.2",AUDIO="audio"
video/720p/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=6000000,RESOLUTION=1920x1080,CODECS="avc1.4d4028,mp4a.40.2",AUDIO="audio"
video/1080p/index.m3u8

Each of those four media playlists follows this structure. Relative filenames resolve inside that track's directory, so each track has its own media files.

Media playlist template: video or audio
#EXTM3U
#EXT-X-VERSION:7
#EXT-X-TARGETDURATION:6
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="skd://movie-key-001",KEYFORMAT="com.apple.streamingkeydelivery",KEYFORMATVERSIONS="1"
#EXT-X-MAP:URI="init.mp4"
#EXTINF:6.000,
segment-001.m4s
#EXTINF:6.000,
segment-002.m4s
#EXT-X-ENDLIST

The protection applies to audio too. Each encrypted track's playlist includes EXT-X-KEY. The skd:// URI identifies the FairPlay key; it is not a downloadable clear key. This example uses one shared key, but tracks can use different keys.

Apple: FairPlay playlist signaling.

DASH with Widevine​

One MPD contains two video representations and a separate audio AdaptationSet for each language:

movie.mpd: two qualities and two audio languages
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:cenc="urn:mpeg:cenc:2013" type="static" mediaPresentationDuration="PT12S" minBufferTime="PT2S" profiles="urn:mpeg:dash:profile:isoff-live:2011">
<Period start="PT0S">
<AdaptationSet contentType="video" mimeType="video/mp4" segmentAlignment="true">
<ContentProtection schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc" cenc:default_KID="11111111-2222-3333-4444-555555555555"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"/>
<SegmentTemplate timescale="1" duration="6" startNumber="1" initialization="$RepresentationID$/init.mp4" media="$RepresentationID$/segment-$Number$.m4s"/>
<Representation id="video-720p" bandwidth="2800000" width="1280" height="720" codecs="avc1.4d401f"/>
<Representation id="video-1080p" bandwidth="5800000" width="1920" height="1080" codecs="avc1.4d4028"/>
</AdaptationSet>
<AdaptationSet contentType="audio" mimeType="audio/mp4" lang="en">
<ContentProtection schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc" cenc:default_KID="11111111-2222-3333-4444-555555555555"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"/>
<SegmentTemplate timescale="1" duration="6" startNumber="1" initialization="$RepresentationID$/init.mp4" media="$RepresentationID$/segment-$Number$.m4s"/>
<Representation id="audio-en" bandwidth="128000" codecs="mp4a.40.2" audioSamplingRate="48000"/>
</AdaptationSet>
<AdaptationSet contentType="audio" mimeType="audio/mp4" lang="hi">
<ContentProtection schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc" cenc:default_KID="11111111-2222-3333-4444-555555555555"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"/>
<SegmentTemplate timescale="1" duration="6" startNumber="1" initialization="$RepresentationID$/init.mp4" media="$RepresentationID$/segment-$Number$.m4s"/>
<Representation id="audio-hi" bandwidth="128000" codecs="mp4a.40.2" audioSamplingRate="48000"/>
</AdaptationSet>
</Period>
</MPD>

A key ID is not the key. default_KID identifies the encryption key; the UUID in the second ContentProtection element identifies Widevine. This example assumes DRM initialization data (pssh) is in the initialization segments and the license server is configured in the player.

The isoff-live profile name describes the segment format; type="static" still makes this a fixed, 12-second presentation. DASH-IF: DRM signaling and initialization data.

One movie, several qualities​

The same content can be encoded at different qualities:

Each row follows the same playback timeline
360p  -> segment 1 -> segment 2 -> segment 3
720p -> segment 1 -> segment 2 -> segment 3
1080p -> segment 1 -> segment 2 -> segment 3

Each row is a representation: another encoded version of the same content. The player chooses which version to download.

Load a stream with Shaka​

Shaka attaches to a video element, applies configuration, then loads a manifest:

The setup order
const player = new shaka.Player();
await player.attach(video);
player.configure({ abr: { enabled: true } });
await player.load(manifestUrl);

The video element still owns play, pause, volume, and playback position. Shaka manages the streaming decisions.

Complete example: video element and player setup

Serve the compiled bundle from your pinned shaka-player package at the /vendor/ path below. Put the JavaScript in player.js.

index.html
<video id="video" controls playsinline preload="metadata"></video>
<script src="/vendor/shaka-player.compiled.js" defer></script>
<script src="/player.js" defer></script>
player.js
async function createPlayer() {
shaka.polyfill.installAll();
if (!shaka.Player.isBrowserSupported()) {
throw new Error('Required playback APIs are unavailable');
}

const video = document.querySelector('#video');
const player = new shaka.Player();
player.addEventListener('error', (event) => {
console.error('Playback error:', event.detail);
});

try {
await player.attach(video);
player.configure({ abr: { enabled: true } });

await player.load(
'https://storage.googleapis.com/shaka-demo-assets/angel-one/dash.mpd',
);
return player;
} catch (error) {
await player.destroy();
throw error;
}
}

createPlayer()
.then((player) => { window.player = player; })
.catch(console.error);

The URL is Shaka's public DASH demo. Use the native Play control after loading. The examples below use this player instance; apply startup settings before load(). When the view is removed, call await player.destroy().

See Shaka's setup guide and MDN: video element.

Loaded does not mean playing. player.load() prepares the stream. An application call to video.play() can still be rejected by autoplay policy. Handle its Promise: MDN: play().

Choose a quality​

Adaptive bitrate (ABR) means changing quality as network conditions change. Starting too high risks an early pause; starting low sacrifices initial sharpness.

An explicit startup estimate
player.configure({
abr: {
enabled: true,
useNetworkInformation: false,
defaultBandwidthEstimate: 1_000_000, // => 1 Mbps, in bits/second
},
});

This is an initial estimate, not a forced video bitrate. Shaka updates its estimate from transfers. Here, useNetworkInformation: false makes the example use our estimate instead of an available browser network estimate.

These are example settings, not universal defaults. Inspect your installed version with player.getConfiguration(). Shaka ABR reference.

Download segments​

Some formats require initialization data before the media segments:

GET /720p/init.mp4         -> codec and track setup
GET /720p/segment-001.m4s -> first piece of media
GET /720p/segment-002.m4s -> next piece of media

In the Media Source Extensions (MSE) path, Shaka appends downloaded media to browser buffers. The browser decodes and presents it at the correct time. MDN: MSE.

Manifest and segment requests have separate retry settings:

Example retry policy
const retry = {
timeout: 30_000, // Milliseconds per attempt
maxAttempts: 3, // Initial attempt plus up to two retries
baseDelay: 1_000,
backoffFactor: 2,
fuzzFactor: 0.5,
};

player.configure({
manifest: { retryParameters: { ...retry } },
streaming: { retryParameters: { ...retry } },
});

A retry is only useful if it completes before the buffer runs out. Tune these limits against your network and startup budget. If requests fail because of CORS, fix the server's permissions; retries cannot grant access. MDN: CORS.

Keep enough media buffered​

Buffer ahead is the playable time already downloaded beyond the current position.

Playback position: 20 seconds
Buffered until: 35 seconds
Buffer ahead: 15 seconds

A useful VOD starting point is:

Buffer targets, in seconds
player.configure({
streaming: {
bufferingGoal: 30, // Aim to download 30 seconds ahead
rebufferingGoal: 2, // Leave buffering once 2 seconds are available
bufferBehind: 30, // Retain recent media behind the playhead
},
});

Keep rebufferingGoal below bufferingGoal. These are targets, not exact memory limits; segment boundaries affect how much media is retained. Shaka buffering and retries.

More buffer has a cost. It protects against short network interruptions, but downloads more media the viewer may never watch. It can also delay a visible quality change because Shaka normally plays the old buffered media first.

Measure buffer ahead without counting gaps
Read the range containing the playhead
function bufferAhead(video) {
const time = video.currentTime;
const ranges = video.buffered;
for (let i = 0; i < ranges.length; i += 1) {
if (ranges.start(i) <= time && time < ranges.end(i)) {
return ranges.end(i) - time;
}
}
return 0;
}

console.log(bufferAhead(document.querySelector('#video')));
// => Playable seconds ahead in the current range

After seeks, buffered can contain separate ranges. Using the last range's end would count gaps as playable media. MDN: buffered.

Switch quality while playing​

When bandwidth drops, the player can request a lower-quality future segment:

720p segment 1 -> 720p segment 2 -> 360p segment 3

The timeline continues across representations. Already displayed frames are unchanged, and a selected quality may take time to appear because older media is still buffered.

VOD and live playback​

Video on demand (VOD) has a fixed timeline and a final segment. Live playback follows a timeline that grows as new media becomes available.

For an HLS live playlist, the available window might move like this:

10:00:00 -> segments 101, 102, 103
10:00:06 -> segments 102, 103, 104
10:00:12 -> segments 103, 104, 105

For a stream packaged for low latency:

Apply before loading a compatible stream
player.configure({ streaming: { lowLatencyMode: true } });

This setting cannot make an ordinary stream low latency by itself. The packaging and delivery path must support it. Shaka low-latency configuration.

Recognize HLS and DASH​

Both describe segmented media that the player can request over HTTP. Neither format automatically gives better picture quality.

HLS uses playlists​

HTTP Live Streaming (HLS) uses .m3u8 playlists. A master playlist can list qualities; each media playlist lists that quality's segments.

HLS structure
master.m3u8
-> 720p/index.m3u8 -> segment-001.ts
-> 1080p/index.m3u8 -> segment-001.ts

Common media files are .ts or fragmented MP4 segments such as .m4s; subtitles may use .vtt.

Example HLS playlists
master.m3u8
#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=2800000,RESOLUTION=1280x720
720p/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=5500000,RESOLUTION=1920x1080
1080p/index.m3u8
720p/index.m3u8
#EXTM3U
#EXT-X-TARGETDURATION:6
#EXTINF:6.0,
segment-001.ts
#EXTINF:6.0,
segment-002.ts

The listed segment paths are relative to their playlist's location.

DASH uses an XML manifest​

Dynamic Adaptive Streaming over HTTP (DASH) uses an .mpd file. Its hierarchy separates time periods, media groups, and representations.

DASH structure
movie.mpd
-> Period
-> Video AdaptationSet
-> 360p Representation
-> 720p Representation
-> Audio AdaptationSet
-> English Representation

Common media files include .m4s segments and MP4 initialization data. DASH can also use WebM media.

Example DASH manifest fragment

This fragment illustrates the hierarchy and URL template; it is not a complete playable manifest.

Representation inside a Period
<AdaptationSet contentType="video">
<Representation id="720p" bandwidth="2800000" width="1280" height="720">
<SegmentTemplate
media="720p-$Number$.m4s"
initialization="720p-init.mp4"
/>
</Representation>
</AdaptationSet>

The mistakes to remember​

  • Manifest loaded, segments fail: check the segment URLs and their access permissions.
  • Quality changed, picture looks the same: old media may still be buffered.
  • Large buffer, slow startup: check the recovery threshold, not just the forward target.
  • Shaka settings have no effect: native HLS playback is browser-managed; these examples configure Shaka's MSE path.
  • A config example looks like a standard: inspect player.getConfiguration() for your pinned version's actual settings.

Resources​