MpegFlowBlogBack to home
← Recipes·HLS VOD packaging·packaging

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.

ByMpegFlow Engineering Team·FFmpeg recipe
·3 variants·May 9, 2026

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

Single-bitrate HLS VOD package
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.m3u8

hls_time 4 = 4-second segments. hls_playlist_type vod = on-demand (vs event/live).

Multi-bitrate HLS package (variant playlist)
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.

CMAF/fMP4 segments (modern HLS)
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.m3u8

CMAF/fMP4 segments instead of MPEG-TS. Same package format used for DASH — enables single-package multi-protocol delivery.

What each parameter does

  • -hls_time N

    Target segment duration in seconds. 4 is the standard for VOD; 2 for low-latency live. Must align with keyframe interval.

  • -hls_playlist_type vod

    Marks the manifest as VOD (vs event for growing-DVR streams or live for real-time). Required for proper player behavior.

  • -hls_segment_type fmp4

    CMAF/fMP4 segments instead of MPEG-TS. Smaller files, supports DRM (CENC), interoperable with DASH. The modern default for new packages.

  • -master_pl_name

    Master playlist filename. Generates the multi-rendition entry point that players load first.

  • -var_stream_map

    Maps 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

  1. Keyframe interval must align with segment length. 4-second segments with keyframes every 2 seconds produces irregular segment boundaries that break some players.
  2. Default GOP includes scene-cut keyframes. Always disable with no-scenecut or sc_threshold=0 — otherwise scene changes insert keyframes mid-segment.
  3. CMAF (fmp4) segments are not playable by older HLS players (pre-iOS 10). For maximal compatibility use TS; for modern players prefer fmp4.
  4. 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.
  5. 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.
  6. 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.

How MpegFlow handles this

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.

Topics
  • FFmpeg
  • hls
  • packaging
  • VOD
  • cmaf
See also
  • Recipe
    Abr Ladder Generation
  • Recipe
    Keyframe Interval Tuning For Hls
  • Architecture
    Broadcast Grade Vod Transcoding
Running this at scale?

Get the orchestration layer for free.

The hls vod packaging command above is the easy part. The queue, retries, audit trail, encoder-version pinning, and multi-tenant security around it are what every video team rebuilds from scratch. We did the rebuild — design partners run it free during beta.

Apply More recipes
© 2026 MpegFlow, Inc. · Trust & complianceAll systems nominal·StatusPrivacy