Skip to content
>_ TrueFileSize.com
·9 min read

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

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.