What You Will Learn
- How HLS works: playlists (
.m3u8) and transport stream segments (.ts) - Core options:
-hls_time,-hls_list_size,-hls_segment_filename - Creating a complete HLS output ready for a static HTTP server
- Using
-hls_flags delete_segmentsfor live stream cleanup - Notes on adaptive bitrate ladder creation
Tested version: FFmpeg 6.1 (ubuntu-latest / CI-validated)
Target OS: Windows / macOS / Linux
How HLS Works
HLS (HTTP Live Streaming) is Apple’s adaptive streaming protocol, now an industry standard supported on virtually every platform. FFmpeg can generate HLS output with the -f hls muxer.
An HLS output consists of two types of files:
| File type | Extension | Description |
|---|---|---|
| Playlist | .m3u8 | Index file listing all segment URLs |
| Segments | .ts | Short video/audio chunks (typically 2–10 seconds each) |
A web server only needs to serve these static files over HTTP. Players (browsers via hls.js, native iOS/macOS, Android) download the playlist, then fetch segments on demand.
Minimal HLS Output
ffmpeg -i input.mp4 -c:v libx264 -c:a aac -f hls /tmp/playlist.m3u8
This produces /tmp/playlist.m3u8 and numbered segment files (/tmp/out000.ts, /tmp/out001.ts, …) using default settings.
Controlling Segment Duration
-hls_time N sets the target segment duration in seconds (default: 2):
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -c:a aac -f hls -hls_time 6 /tmp/playlist.m3u8
Segments are split on keyframe boundaries, so actual durations may vary slightly. To get consistent splits, add a forced keyframe interval:
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -g 60 -keyint_min 60 -sc_threshold 0 -c:a aac -f hls -hls_time 6 /tmp/playlist.m3u8
-g 60 sets a keyframe every 60 frames (2 seconds at 30 fps). Combined with -hls_time 6, each segment spans exactly 3 keyframe intervals.
Retaining All Segments in the Playlist
By default, -hls_list_size is 5, meaning the playlist contains only the last 5 segments (useful for live streams). For VOD (Video on Demand), set it to 0 to retain all segments:
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -c:a aac -f hls -hls_time 6 -hls_list_size 0 /tmp/playlist.m3u8
Custom Segment Filename Pattern
Use -hls_segment_filename to control the path and naming of segment files:
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -c:a aac -f hls -hls_time 6 -hls_list_size 0 -hls_segment_filename "/tmp/segment_%03d.ts" /tmp/playlist.m3u8
%03d is a printf-style format that produces zero-padded numbers: segment_000.ts, segment_001.ts, etc. You can also include a directory path:
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -c:a aac -f hls -hls_time 6 -hls_list_size 0 -hls_segment_filename "/tmp/hls/seg_%04d.ts" /tmp/hls/playlist.m3u8
Make sure the output directory exists before running (FFmpeg will not create it automatically).
Complete VOD Example
A production-ready command for converting a file to HLS for on-demand playback:
ffmpeg -i input.mp4 -c:v libx264 -preset slow -crf 22 -c:a aac -b:a 128k -f hls -hls_time 6 -hls_list_size 0 -hls_segment_filename "/tmp/hls_vod/seg_%04d.ts" /tmp/hls_vod/index.m3u8
Deploy the entire /tmp/hls_vod/ directory to any HTTP server. Point your player at index.m3u8.
Live Streaming — Delete Old Segments
For a live stream, old segments are no longer needed once they leave the playlist window. Use -hls_flags delete_segments to remove them automatically:
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -c:a aac -f hls -hls_time 6 -hls_list_size 5 -hls_flags delete_segments /tmp/playlist.m3u8
With -hls_list_size 5, the playlist holds 5 segments. Any segment removed from the playlist is automatically deleted from disk.
Adaptive Bitrate Ladder (Overview)
A full ABR ladder requires encoding the same content at multiple bitrates and resolutions, then creating a master playlist that references each variant. The typical workflow:
- Encode each bitrate/resolution as a separate HLS stream
- Write a master playlist (
.m3u8) that lists each variant with#EXT-X-STREAM-INFtags
FFmpeg can produce multiple outputs in one pass using -map and multiple -f hls outputs, but this requires a complex command. For production ABR use cases, dedicated tools such as AWS MediaConvert or open-source transcoders built around FFmpeg are more practical.
Key Options Reference
| Option | Default | Description |
|---|---|---|
-hls_time N | 2 | Target segment duration in seconds |
-hls_list_size N | 5 | Max segments in playlist (0 = keep all) |
-hls_segment_filename PATTERN | (auto) | Segment file naming pattern |
-hls_flags delete_segments | off | Auto-delete segments removed from playlist |
-hls_flags append_list | off | Append to existing playlist instead of overwriting |
-start_number N | 0 | Starting sequence number for segments |
Common Pitfalls
Segments Are Not in the Playlist
If you forget -hls_list_size 0 for a VOD file, only the last N segments will appear in the playlist. The file will play, but only the last portion.
Output Directory Does Not Exist
FFmpeg will fail with a “No such file or directory” error if the parent directory of the segment path does not exist. Create it first:
mkdir -p /tmp/hls_output
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -c:a aac -f hls -hls_time 6 -hls_list_size 0 -hls_segment_filename "/tmp/hls_output/seg_%04d.ts" /tmp/hls_output/playlist.m3u8
Player Shows “No Playable Sources”
Ensure your HTTP server sets the correct MIME types:
.m3u8→application/vnd.apple.mpegurlorapplication/x-mpegURL.ts→video/mp2t
Some servers need explicit configuration for these types.
Related Articles
- Video Format Conversion — Transcoding to MP4
- Compressing Video — CRF and Bitrate Targets
- FFmpeg Basic Syntax and Options
Tested with: ffmpeg 6.1.1 / Ubuntu 24.04 (GitHub Actions runner)
Primary sources: ffmpeg.org/ffmpeg-formats.html#hls-1 / trac.ffmpeg.org/wiki/StreamingGuide