How HLS Works
HLS (HTTP Live Streaming) is Apple’s streaming format. Almost every platform can play it. FFmpeg creates HLS output with -f hls.
HLS output is made of two kinds 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) |
Any web server can host them as ordinary files. The player (hls.js in a browser, the native player on iOS and macOS, or an Android player) downloads the playlist first, then fetches the segments as it plays.
Minimal HLS Output
ffmpeg -i input.mp4 -c:v libx264 -c:a aac -f hls /tmp/playlist.m3u8
This writes /tmp/playlist.m3u8 and numbered segments (/tmp/playlist0.ts, /tmp/playlist1.ts, …) with the default settings.
Controlling Segment Duration
-hls_time N sets the target length of each segment, in seconds (default 2):
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -c:a aac -f hls -hls_time 6 /tmp/playlist.m3u8
A segment can only start at a keyframe (a frame that can be decoded on its own). By default, libx264 can leave up to 250 frames between keyframes (about 8.3 s at 30 fps). So even with -hls_time 6, segments can run longer than 8 s. For even segments, force a fixed 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 puts a keyframe every 60 frames (2 seconds at 30 fps). -sc_threshold 0 stops libx264 from adding extra keyframes at scene changes. With -hls_time 6, each segment then holds exactly 3 keyframe intervals.
Retaining All Segments in the Playlist
By default -hls_list_size is 5, so the playlist lists only the last 5 segments. That suits a live stream. For VOD (video on demand: a finished video that viewers play from the start), set it to 0 to keep every segment in the playlist:
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
-hls_segment_filename sets the folder and file name of the segments:
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 becomes a 3-digit number padded with zeros: segment_000.ts, segment_001.ts, and so on. The pattern can also include a folder:
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
Create the folder before you run the command. FFmpeg does not create it for you.
Complete VOD Example
A full command that turns a file into 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
Upload the whole /tmp/hls_vod/ folder to any web server. Then point your player at index.m3u8.
Live Streaming — Delete Old Segments
In a live stream, a segment is no longer needed once it drops out of the playlist. -hls_flags delete_segments deletes those files for you:
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. FFmpeg deletes the files of segments that have left the playlist, except the one that left last, so 6 segment files stay on disk. -hls_delete_threshold sets how many of these extra files to keep (default 1).
Adaptive Bitrate Ladder (Overview)
An ABR (adaptive bitrate) ladder is the same video encoded at several resolutions and bitrates. The player switches between these versions (variants) to match the viewer’s connection. Building one takes two steps:
- Encode each resolution and bitrate as its own HLS stream.
- Write a master playlist (
.m3u8) that lists each variant with an#EXT-X-STREAM-INFtag.
FFmpeg can do both in one -f hls output. Add -var_stream_map (for example "v:0,a:0 v:1,a:1") and -master_pl_name, and put %v in the output name (for example stream_%v.m3u8). One run then writes a playlist for each variant plus the master playlist. The output needs a video and an audio stream for each variant: for two variants, map them twice with -map 0:v -map 0:a -map 0:v -map 0:a, and give each copy its own settings, such as -s:v:1 1280x720 -b:v:1 3M. For ABR in production, a dedicated tool such as AWS MediaConvert, or an open-source transcoder built on FFmpeg, is 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
Without -hls_list_size 0, a VOD playlist lists only the last few segments (5 by default). The video plays, but only its last part.
Output Directory Does Not Exist
If the segment folder does not exist, FFmpeg prints Failed to open file and writes no segments. FFmpeg 8.1 then stops with Conversion failed! when the first segment is complete. A video short enough to fit in one segment runs to the end instead and exits with code 0, but still leaves no segment. Create the folder 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
The Player Won’t Play the Stream
Check that your web server sends the right MIME type (the file type it reports to the player) for each file:
.m3u8→application/vnd.apple.mpegurlorapplication/x-mpegURL.ts→video/mp2t
Some servers do not know these types until you add them to their configuration.