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.
<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:
#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:
#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.
#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:
<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:
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:
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.
<video id="video" controls playsinline preload="metadata"></video>
<script src="/vendor/shaka-player.compiled.js" defer></script>
<script src="/player.js" defer></script>
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.
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:
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:
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
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:
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.
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
#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=2800000,RESOLUTION=1280x720
720p/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=5500000,RESOLUTION=1920x1080
1080p/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.
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.
<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.