What is LL-HLS?
HLS splits video into short files called segments, usually 6 seconds each. This makes the delay from camera to viewer’s screen (glass-to-glass latency) long. For live streams, players are advised to start at least three target durations (three times the segment length) behind the newest segment. A player that follows this advice is 18 seconds or more behind with 6-second segments (see below).
LL-HLS (Low-Latency HLS) is an extension of HLS that Apple announced in 2019 and revised in 2020. It cuts the delay with these mechanisms:
| Mechanism | Summary |
|---|---|
| Partial Segments | Break regular .ts / .m4s segments into shorter “parts” that are exposed in the playlist before the full segment is ready. Apple recommends a part target of one second |
| Blocking Playlist Reload | When the playlist isn’t updated yet, the server holds the HTTP response until a new part is available |
| Preload Hint | Announce the next upcoming part URL in the playlist so the player can pre-request it |
FFmpeg handles the basic parts: short segments, independent segments, and program date-time. For full LL-HLS you also need a CDN that can send blocking responses, or a dedicated packager placed after FFmpeg.
Prerequisite: fixed keyframe interval / GOP
For LL-HLS, fix the IDR keyframe interval (the GOP) at the segment length, or at a length that divides it evenly, so that every segment starts with an IDR keyframe. An IDR keyframe is a frame that playback can start from on its own. FFmpeg starts a new segment only at a keyframe, so if the interval doesn’t fit, segments come out longer than hls_time and the delay grows.
ffmpeg -i input.mp4 -c:v libx264 -preset veryfast -tune zerolatency -profile:v baseline -pix_fmt yuv420p -g 30 -keyint_min 30 -sc_threshold 0 -b:v 2500k -c:a aac -b:a 128k -f hls -hls_time 1 -hls_list_size 10 -hls_flags independent_segments+delete_segments -hls_segment_type mpegts -hls_segment_filename "/tmp/seg_%04d.ts" /tmp/playlist.m3u8
What the main options do:
-g 30+-keyint_min 30+-sc_threshold 0: force an IDR frame every 30 frames (1 s at 30 fps)-tune zerolatency: turns off B-frames and cuts the delay inside the encoder-profile:v baseline: plays on the widest range of devices (iOS / Android)-pix_fmt yuv420p: sets the 4:2:0 color format that baseline requires-hls_time 1: aims for 1-second segments-hls_flags independent_segments: writes in the playlist that each segment can be decoded on its own
Minimal short-segment HLS output
ffmpeg -i input.mp4 -c:v libx264 -preset veryfast -tune zerolatency -g 30 -keyint_min 30 -sc_threshold 0 -c:a aac -f hls -hls_time 1 -hls_list_size 20 -hls_flags independent_segments+delete_segments+program_date_time -master_pl_name master.m3u8 -hls_segment_filename "/tmp/seg_%04d.ts" /tmp/stream.m3u8
This writes:
/tmp/stream.m3u8: the media playlist/tmp/seg_0000.ts,seg_0001.ts, …: 1-second segments/tmp/master.m3u8: the master playlist
Important LL-HLS options
| Option | Recommended | Purpose |
|---|---|---|
-hls_time |
1 |
Target segment duration (seconds); in real LL-HLS the short units are the parts, not the segments |
-hls_list_size |
10–20 |
Max number of segments retained in the playlist |
-hls_flags independent_segments |
required | Signals that every segment is standalone-decodable |
-hls_flags delete_segments |
recommended | Prevents disk bloat during live streaming |
-hls_flags program_date_time |
recommended | Adds EXT-X-PROGRAM-DATE-TIME (useful for sync + seeking) |
-hls_segment_type |
mpegts or fmp4 |
With fmp4, FFmpeg also writes an init.mp4 init segment (-hls_fmp4_init_filename changes the name) |
-g / -keyint_min |
fps × hls_time |
Keeps keyframes at segment boundaries |
-sc_threshold |
0 |
Disables scene-change keyframes (keeps interval fixed) |
-tune zerolatency |
recommended | libx264’s low-latency tuning |
fMP4 segments (preferred)
Apple’s HLS authoring spec accepts MPEG-TS or fragmented MP4 (fMP4) for H.264 and requires fMP4 for HEVC and AV1. This command writes fMP4 segments:
ffmpeg -i input.mp4 -c:v libx264 -preset veryfast -tune zerolatency -g 30 -keyint_min 30 -sc_threshold 0 -c:a aac -f hls -hls_time 1 -hls_list_size 20 -hls_segment_type fmp4 -hls_fmp4_init_filename "init.mp4" -hls_flags independent_segments+delete_segments+program_date_time -hls_segment_filename "/tmp/seg_%04d.m4s" /tmp/stream.m3u8
init.mp4 is the initialization segment. It holds only the ftyp and moov boxes, and the player must load it before any media segment.
Sample LL-HLS playlist
The command above writes a stream.m3u8 that looks roughly like this:
#EXTM3U
#EXT-X-VERSION:7
#EXT-X-TARGETDURATION:1
#EXT-X-MEDIA-SEQUENCE:42
#EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-MAP:URI="init.mp4"
#EXTINF:1.000000,
#EXT-X-PROGRAM-DATE-TIME:2026-09-24T17:48:35.393+0900
seg_0042.m4s
#EXTINF:1.000000,
#EXT-X-PROGRAM-DATE-TIME:2026-09-24T17:48:36.393+0900
seg_0043.m4s
#EXTINF:1.000000,
#EXT-X-PROGRAM-DATE-TIME:2026-09-24T17:48:37.393+0900
seg_0044.m4s
A playlist that fully follows LL-HLS also needs tags like these. Usually a CDN or a dedicated packager after FFmpeg writes them:
#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=3.0
#EXT-X-PART-INF:PART-TARGET=0.33
#EXT-X-PART:DURATION=0.33,URI="seg_0044.1.m4s"
#EXT-X-PART:DURATION=0.33,URI="seg_0044.2.m4s"
#EXT-X-PRELOAD-HINT:TYPE=PART,URI="seg_0044.3.m4s"
FFmpeg doesn’t write partial-segment tags. For full LL-HLS, pair it with a tool that supports LL-HLS, such as Apple’s mediastreamsegmenter.
Delivering from a live source
This takes an RTMP or UDP feed and writes short-segment HLS:
ffmpeg -i rtmp://localhost/live/stream -c:v libx264 -preset ultrafast -tune zerolatency -g 30 -keyint_min 30 -sc_threshold 0 -b:v 3000k -c:a aac -b:a 128k -f hls -hls_time 1 -hls_list_size 10 -hls_flags delete_segments+independent_segments+program_date_time -hls_segment_filename "/tmp/live_seg_%04d.ts" /tmp/live_stream.m3u8
-preset ultrafastkeeps the encoder delay as small as possible-hls_list_size 10keeps the last 10 seconds of segments in the playlist- Serve the output folder over HTTP and open
live_stream.m3u8in the player
Replace the rtmp:// URL with your own source.
Player compatibility
| Player | LL-HLS support | Notes |
|---|---|---|
| iOS 14+ Safari / tvOS 14+ apps | ✅ Full | Native AVPlayer |
| macOS Safari 14+ | ✅ Full | Same AVPlayer pipeline |
| hls.js 1.0+ | ⚠️ Partial | Requires lowLatencyMode (on by default) and a CDN that implements blocking reload |
| ExoPlayer (Android) | ⚠️ Partial | Version-dependent |
| Shaka Player | ✅ Supported | Partial segments, preload hints, delta updates, and blocking playlist reload |
| dash.js | ❌ | DASH only; use LL-DASH on the DASH side |
Measuring glass-to-glass latency
One simple way:
- Burn the time into the video on the sending side (an OBS timer source or FFmpeg
drawtext) - Take one photo that shows both the sender’s screen and the viewer’s screen
- The difference between the two times in the photo is the latency
To burn the computer’s local time into the video with FFmpeg:
ffmpeg -i input.mp4 -vf "drawtext=text='%{localtime}':fontcolor=white:fontsize=36:x=10:y=10:box=1:[email protected]" -c:v libx264 -preset veryfast -tune zerolatency -g 30 -c:a aac -f hls -hls_time 1 -hls_list_size 10 -hls_flags delete_segments+independent_segments -hls_segment_filename "/tmp/burnin_%04d.ts" /tmp/burnin.m3u8
drawtext needs a font file. On some systems you also have to pass fontfile=/path/to/font.ttf.
Common issues
Segment boundaries don’t align with keyframes
If segments don’t come out at the hls_time length, the GOP length usually doesn’t match hls_time. Check that -g equals fps × hls_time, or divides it evenly. The warning Stream 0 packet with pts ... has duration 0. The segment duration may not be precise. has another cause. It appears when packets arrive without duration information.
iOS Safari can’t play the stream
EXT-X-INDEPENDENT-SEGMENTS is not required for playback. CORS (the Access-Control-Allow-Origin header that lets a page load files from another site) matters when a script such as hls.js loads the playlist and segments from a different domain than the page. When Safari plays a URL set directly on a <video> element, CORS is not used.
hls.js feels slow
Check that lowLatencyMode is not set to false in the hls.js config (the default is true). If the playlist says CAN-BLOCK-RELOAD=YES, your CDN must also send blocking responses. Otherwise the latency is the same as classic HLS.
CPU is maxed out
An IDR frame every second mostly raises the bitrate; it hardly changes the CPU load. If encoding can’t keep up, try -preset ultrafast or switch to a hardware encoder (h264_nvenc, h264_videotoolbox, h264_qsv).
Latency comparison (floors for players that follow the spec)
For live streams, RFC 8216 advises players to start at least three target durations behind the newest segment. The table shows the values for players that follow this advice. In LL-HLS with parts, PART-HOLD-BACK sets this distance, and at least three part durations is recommended. Real latency is this plus the time for encoding and delivery.
| Setup | Segment length | Minimum latency | Notes |
|---|---|---|---|
| Classic HLS | 6 s | 18 s | hls_time 6 |
| Short-segment HLS | 2 s | 6 s | hls_time 2 |
| 1-second segments (FFmpeg only) | 1 s | 3 s | hls_time 1 (the commands above) |
| LL-HLS + partials | 1 s + 0.33 s parts | The PART-HOLD-BACK value (about 1 s or more recommended) |
Full spec (needs dedicated packager) |
Related
Frequently Asked Questions
Does hls_time 1 alone make it LL-HLS?
No. hls_time 1 only makes the segments 1 second long. Real LL-HLS also needs partial-segment tags in the playlist and a server that can send blocking responses. With FFmpeg alone you get short-segment HLS: with 1-second segments, players that follow the spec still start at least 3 s behind (see above). That is enough for many uses.
Can I play LL-HLS anywhere but iOS?
hls.js 1.0 and later supports part of it (lowLatencyMode is on by default). Shaka Player also supports partial segments and blocking playlist reload. Support in Android’s ExoPlayer and in smart-TV players varies by product. On UWP and Chromecast, LL-DASH is often easier to use.
Do 1-second segments play poorly with CDNs?
You’ll send about 6× as many HTTP requests (6 s → 1 s). Choose a CDN that charges little per request, or one with official LL-HLS settings (CloudFront, Fastly, Cloudflare).
How do I reduce CPU?
Try -preset ultrafast or a hardware encoder (h264_nvenc, h264_videotoolbox, h264_qsv). If your input is already H.264 with a keyframe at every segment boundary, use -c:v copy to skip re-encoding altogether. Copying keeps the keyframe interval as it is.
Is LL-HLS useful for VOD?
No. LL-HLS is for live streaming. For video on demand (VOD), use classic HLS with 6-second segments and every segment kept in the playlist. LL-HLS for VOD only adds CDN load and gives viewers nothing.