Video Streaming: HLS, DASH and CMAF
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. See the section bibliography for the reference material consulted while preparing these pages. |
Baseline date: 2026-10-07. Once a video has been encoded into a ladder of renditions, a player has to be able to choose between them while the video plays. That is what adaptive bitrate (ABR) streaming does, and it is done by splitting each rendition into short segments and publishing a playlist that lists them.
Progressive download versus adaptive streaming
- Progressive download
-
One MP4 served over HTTP, with
faststartso the index is at the start. The browser starts playing early and uses range requests to seek. It is simple, it works behind any CDN, and it is a good fit for short clips and for a single rendition. It cannot change quality mid-playback, so a viewer on a slow link stalls, and a viewer on a fast link is stuck at the one quality you chose. - Adaptive streaming
-
Several renditions, each cut into segments of a few seconds, plus manifests. The player measures throughput and buffer level and picks the best rendition for each segment, so it can step down before it stalls. Use it for anything longer than a minute or two, for mixed audiences, and for live.
HLS, DASH and CMAF
| HLS | DASH | |
|---|---|---|
Specification |
RFC 8216 and the later HLS draft, maintained by Apple. |
ISO/IEC 23009-1, summarised by the DASH Industry Forum. |
Manifest |
Text playlists: a multivariant playlist ( |
One XML file ( |
Segments |
MPEG-TS or fMP4. |
fMP4 (and WebM). |
Native support |
Safari, iOS, tvOS, many TVs. Other browsers use hls.js. |
No native support in browsers. Played with dash.js or Shaka Player over Media Source Extensions. |
CMAF (Common Media Application Format) is a restricted form of fragmented MP4. When segments are CMAF, one set of media files can be described by both an HLS and a DASH manifest, so you store and cache the media once. In practice a platform that wants maximum reach produces fMP4 segments, an HLS playlist, and a DASH manifest over the same files. Encrypted CMAF needs the same encryption mode for both (see the DRM note below).
Segment length
Apple’s HLS Authoring Specification recommends a target duration of 6 seconds and segments that start with an IDR frame, with media segments not exceeding the target duration by more than half a second. Shorter segments (2 to 4 seconds) lower start-up and switching latency and raise overhead and request count. Longer ones reduce overhead and slow adaptation. A keyframe every two seconds with six-second segments gives three keyframes per segment, and the switch points line up in every rendition only if the keyframe interval is fixed, see the GOP note in encoding fundamentals.
Playlists
The multivariant playlist (called the master playlist in older text) lists the renditions:
#EXTM3U
#EXT-X-VERSION:7
#EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-STREAM-INF:BANDWIDTH=3300000,AVERAGE-BANDWIDTH=3000000,RESOLUTION=1280x720,CODECS="avc1.64001f,mp4a.40.2",FRAME-RATE=24
720p/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=7200000,AVERAGE-BANDWIDTH=6000000,RESOLUTION=1920x1080,CODECS="avc1.640028,mp4a.40.2",FRAME-RATE=24
1080p/index.m3u8
Each EXT-X-STREAM-INF gives the peak BANDWIDTH, the AVERAGE-BANDWIDTH, the resolution and the codecs. A
media playlist lists the segments of one rendition:
#EXTM3U
#EXT-X-VERSION:7
#EXT-X-TARGETDURATION:6
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-MAP:URI="init.mp4"
#EXTINF:6.000,
seg_000.m4s
#EXTINF:6.000,
seg_001.m4s
#EXT-X-ENDLIST
Segments can be separate files (simple to cache and to debug, many objects) or byte ranges of a single file (the
playlist uses EXT-X-BYTERANGE, so there is one object per rendition, fewer requests to the origin, and CDN caching works
per range). Separate files are the safer default.
A DASH manifest expresses the same thing as an XML tree of Period, AdaptationSet (video, audio), Representation
(one rendition) and a SegmentTemplate that gives the segment URL pattern.
Producing HLS and DASH with FFmpeg
FFmpeg has hls and dash
muxers. Check the option names against the documentation of the FFmpeg build you use. The commands below were run against FFmpeg 6.1.
# Single-rendition HLS with fMP4 segments, 6 second target, keyframe every 2 seconds at any frame rate.
ffmpeg -i input.mp4 -c:v libx264 -preset slow -crf 22 -force_key_frames "expr:gte(t,n_forced*2)" -sc_threshold 0 \
-c:a aac -b:a 128k \
-f hls -hls_time 6 -hls_playlist_type vod -hls_segment_type fmp4 \
-hls_fmp4_init_filename init.mp4 -hls_segment_filename "720p/seg_%03d.m4s" 720p/index.m3u8
# Several renditions in one go, with a generated multivariant playlist.
ffmpeg -i input.mp4 \
-filter_complex "[0:v]split=2[v1][v2];[v1]scale=-2:1080[v1o];[v2]scale=-2:720[v2o]" \
-map "[v1o]" -map "[v2o]" -map 0:a -map 0:a \
-c:v libx264 -preset slow -crf 22 -force_key_frames "expr:gte(t,n_forced*2)" -sc_threshold 0 -c:a aac -b:a 128k \
-f hls -hls_time 6 -hls_playlist_type vod -hls_segment_type fmp4 \
-var_stream_map "v:0,a:0 v:1,a:1" -master_pl_name master.m3u8 \
-hls_segment_filename "stream_%v/seg_%03d.m4s" "stream_%v/index.m3u8"
# DASH (MPD plus fMP4 segments).
ffmpeg -i input.mp4 -c:v libx264 -crf 22 -force_key_frames "expr:gte(t,n_forced*2)" -sc_threshold 0 -c:a aac -b:a 128k \
-f dash -seg_duration 6 -use_template 1 -use_timeline 1 manifest.mpd
FFmpeg is a good start for small catalogues. For production-grade packaging, with DRM, with several audio languages and subtitles, with a CMAF output shared between HLS and DASH, use a dedicated packager.
Shaka Packager and Bento4
Shaka Packager takes encoded MP4 or WebM files and produces HLS and
DASH with shared fMP4 segments, and applies encryption for DRM. Bento4 offers a similar set of
command-line tools (mp4fragment, mp4dash, mp4hls).
packager \
'in=720p.mp4,stream=video,init_segment=720p/init.mp4,segment_template=720p/seg_$Number$.m4s,playlist_name=720p/index.m3u8' \
'in=1080p.mp4,stream=video,init_segment=1080p/init.mp4,segment_template=1080p/seg_$Number$.m4s,playlist_name=1080p/index.m3u8' \
'in=1080p.mp4,stream=audio,init_segment=audio/init.mp4,segment_template=audio/seg_$Number$.m4s,playlist_name=audio/index.m3u8' \
--segment_duration 6 \
--hls_master_playlist_output master.m3u8 \
--mpd_output manifest.mpd
The packager works on already-encoded files, so keep encoding and packaging as two steps of the same job. Managed services do both: see AWS streaming and delivery and Google Cloud Transcoder API.
CDN delivery, signed URLs and cookies
-
Serve playlists and segments from a CDN, with the segments cached for a long time (they never change) and the playlists cached briefly for live and for a long time for VOD with
EXT-X-ENDLIST. -
Use the right
Content-Typefor each file. Apple listsapplication/vnd.apple.mpegurlfor.m3u8andvideo/mp4orvideo/iso.segmentfor fMP4. Misconfigured types are a frequent cause of "plays in Safari only". -
Enable CORS for the player’s origin, since hls.js and dash.js fetch with XHR or fetch, see video playback on the web.
-
For private video, signed URLs protect one object. They do not suit streaming, because a playlist references many segment URLs relative to itself and each would need its own signature. Signed cookies (or a token in the path, or a policy that covers a prefix) authorise a whole prefix such as
/videos/<id>/*for a limited time, which is what HLS and DASH need. CloudFront, Google Media CDN and Azure Front Door all offer one of these, see AWS streaming and delivery.
A note on DRM
Signed access stops casual sharing of links. It does not stop a viewer from saving the video. DRM (Widevine, FairPlay, PlayReady) encrypts the media and releases keys through a licence server only to approved players. Common encryption (CENC) lets one encrypted CMAF file serve Widevine and PlayReady, and FairPlay uses its own HLS key delivery, so a platform that needs all three plans for an encryption mode that suits each and for a licence service. Packagers such as Shaka Packager and managed services support this. For most platforms, unencrypted ABR with signed cookies is enough.