HLS vs MP4 for Video Streaming
Two ways to serve video on the web: progressive MP4 download or adaptive streaming (HLS/DASH). The right choice depends on video length, audience size, and network conditions. Here's how to decide and how to test each.
TL;DR
- Short clips under 2 minutes → MP4 direct
- Long-form, variable network → HLS
- Live streaming → HLS or LL-HLS (sub-second latency)
- Android-only audience → DASH works well; iOS needs HLS
How progressive MP4 works
Browser downloads the file like any other asset. HTML5 <video> plays it as bytes arrive. Simple, cache-friendly, works everywhere — but you send the same bitrate to every user.
<video controls preload="metadata" poster="thumb.jpg">
<source src="video.mp4" type="video/mp4" />
</video>
How HLS works
Video is pre-segmented into 2-10 second chunks at multiple bitrates (e.g., 240p, 480p, 720p, 1080p). A manifest (.m3u8) lists the chunks. The player downloads chunks based on measured bandwidth, upgrading or downgrading on the fly.
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=640x360
360p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2000000,RESOLUTION=1280x720
720p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080
1080p.m3u8
HLS in the browser
Safari/iOS play HLS natively. Chrome/Firefox need hls.js:
import Hls from 'hls.js';
const video = document.querySelector('video');
if (Hls.isSupported()) {
const hls = new Hls();
hls.loadSource('/video/master.m3u8');
hls.attachMedia(video);
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = '/video/master.m3u8';
}
Generating HLS with ffmpeg
# Encode 3 bitrate ladder
ffmpeg -i source.mp4 \
-filter_complex "[0:v]split=3[v1][v2][v3]; \
[v1]scale=640:360[v1out]; \
[v2]scale=1280:720[v2out]; \
[v3]scale=1920:1080[v3out]" \
-map "[v1out]" -c:v:0 libx264 -b:v:0 800k \
-map "[v2out]" -c:v:1 libx264 -b:v:1 2500k \
-map "[v3out]" -c:v:2 libx264 -b:v:2 5000k \
-map a:0 -c:a aac -b:a 128k \
-f hls -hls_time 6 -hls_list_size 0 \
-master_pl_name master.m3u8 \
-hls_segment_filename "stream_%v/segment_%03d.ts" \
stream_%v/playlist.m3u8
Testing each approach
- Sample MP4 files — test progressive download with 1MB to 500MB
- MOV from Apple devices — validate MOV-to-MP4 transcoding
- WebM samples — for DASH or WebM-native browsers
When to use which
- Marketing videos, product demos → MP4 (simpler CDN, cheaper)
- Tutorial platform, long courses → HLS (users on slow connections won't bail)
- Live event → HLS with 2-second segments
- Video with ads / DRM → HLS with encryption keys
Related
For upload side concerns, see chunked video upload. For general video testing, read testing video upload.