Package HLS VOD output with FFmpeg: segments, manifests, byte-range delivery
Generate HLS-compliant VOD packages with proper segment timing, manifest generation, and the byte-range patterns for efficient CDN delivery.
When to use this
You package HLS VOD output for adaptive streaming delivery to web/iOS/Android players. HLS (HTTP Live Streaming) is the dominant streaming format for VOD — every modern player supports it natively (or via hls.js polyfill). The packaging step takes encoded renditions and produces .m3u8 manifests + .ts (or .m4s for CMAF) segment files. FFmpeg can do this end-to-end; in production, dedicated packagers (Shaka Packager, Bento4) often replace this step for advanced features.
Command variants
ffmpeg -i input.mp4 \
-c:v libx264 -preset medium -crf 22 \
-x264opts keyint=48:min-keyint=48:no-scenecut \
-c:a aac -b:a 192k \
-hls_time 4 \
-hls_playlist_type vod \
-hls_segment_filename "segment_%03d.ts" \
output.m3u8hls_time 4 = 4-second segments. hls_playlist_type vod = on-demand (vs event/live).
ffmpeg -i input.mp4 \
-map 0:v -map 0:v -map 0:v -map 0:a -map 0:a -map 0:a \
-c:v libx264 -preset medium \
-filter:v:0 scale=1920:1080 -b:v:0 5500k \
-filter:v:1 scale=1280:720 -b:v:1 3500k \
-filter:v:2 scale=854:480 -b:v:2 1400k \
-x264opts keyint=48:min-keyint=48:no-scenecut \
-c:a aac -b:a 128k \
-f hls -hls_time 4 -hls_playlist_type vod \
-master_pl_name master.m3u8 \
-var_stream_map "v:0,a:0,name:1080p v:1,a:1,name:720p v:2,a:2,name:480p" \
-hls_segment_filename "stream_%v/segment_%03d.ts" \
"stream_%v/playlist.m3u8"Generates master.m3u8 + per-rendition manifests + segments. Three renditions in one command.
ffmpeg -i input.mp4 \
-c:v libx264 -preset medium -crf 22 \
-x264opts keyint=48:min-keyint=48:no-scenecut \
-c:a aac -b:a 192k \
-hls_time 4 \
-hls_playlist_type vod \
-hls_segment_type fmp4 \
-hls_fmp4_init_filename "init.mp4" \
-hls_segment_filename "segment_%03d.m4s" \
output.m3u8CMAF/fMP4 segments instead of MPEG-TS. Same package format used for DASH — enables single-package multi-protocol delivery.
What each parameter does
-hls_time NTarget segment duration in seconds. 4 is the standard for VOD; 2 for low-latency live. Must align with keyframe interval.
-hls_playlist_type vodMarks the manifest as VOD (vs event for growing-DVR streams or live for real-time). Required for proper player behavior.
-hls_segment_type fmp4CMAF/fMP4 segments instead of MPEG-TS. Smaller files, supports DRM (CENC), interoperable with DASH. The modern default for new packages.
-master_pl_nameMaster playlist filename. Generates the multi-rendition entry point that players load first.
-var_stream_mapMaps streams to renditions for multi-bitrate output. Each entry: comma-separated stream selectors + name.
What this outputs
A complete HLS package: master.m3u8 (multi-rendition only), per-rendition .m3u8 manifests, and .ts (or .m4s) segment files. Total file count for a 10-minute video at 4-second segments: ~150 segments + 1-2 manifests.
Pitfalls
- Keyframe interval must align with segment length. 4-second segments with keyframes every 2 seconds produces irregular segment boundaries that break some players.
- Default GOP includes scene-cut keyframes. Always disable with no-scenecut or sc_threshold=0 — otherwise scene changes insert keyframes mid-segment.
- CMAF (fmp4) segments are not playable by older HLS players (pre-iOS 10). For maximal compatibility use TS; for modern players prefer fmp4.
- CDN cache headers matter: HLS manifests should be short-cache (5-30s); segments should be long-cache (1 hour+). Wrong cache headers break adaptive playback.
- Multi-rendition packaging in one FFmpeg invocation loses ~5-10% efficiency vs per-rendition encoding. At production scale, encode each rendition separately, then package together.
- For DRM-protected content, FFmpeg's HLS output doesn't natively support DRM key insertion — use Shaka Packager or Bento4 instead.
At production scale
HLS packaging is decoder + encoder + I/O bound. At scale, packaging is typically <10% of total encode cost — the ABR ladder generation dominates. For production at 10M+ assets/month, dedicated packagers (Shaka, Bento4) often replace FFmpeg's built-in HLS output for better DRM, SCTE-35 marker handling, and advanced multi-codec output.
MpegFlow models packaging as a separate DAG stage from encoding. Encoded renditions feed into a packaging stage that produces HLS, DASH, or CMAF output. The package stage is swappable: FFmpeg-based for simple cases, Shaka Packager for advanced workflows requiring DRM or SCTE-35.