Tuning HLS Segment Duration for Seek Latency
This page sits beneath adaptive bitrate packaging within the Media Delivery & Publishing Automation layer, and it answers the one number the packaging recipe deliberately leaves as a variable: how long should each HLS segment be? The concrete workflow for building the package lives in packaging HLS with FFmpeg and Bento4; this page is about choosing ABR_SEGMENT_DURATION and living with the consequences.
Problem Framing
Segment duration is a single knob wired to four outcomes that pull in different directions. Set it too long and seeking feels sluggish and startup drags; set it too short and playlists bloat, CDN object counts explode, and per-request overhead eats your egress budget. Teams usually discover the wrong value from user-facing symptoms rather than from packaging:
# 10s segments — a support ticket
player-analytics: p75 startupTimeMs=4200 seekResponseMs=2600 (target <1500)
# 2s segments on a VOD back catalogue — a cost alert
cdn-billing: requests +38% MoM cache_fill_ratio 0.71 -> 0.52 small-object overhead high
storage: object_count 1.2M -> 5.9M after re-segmenting library to 2s
The startup cost is structural: most players buffer three segments before playback begins, so time-to-first-frame is roughly 3 × segment_duration plus network. Ten-second segments mean a ~30-second buffer target and a visibly slow start; two-second segments start fast but quadruple your object count versus eight. Seeking has the same shape — a player can only resume cleanly at a segment boundary, so the segment length is the worst-case seek granularity. The job is to pick the point on that curve that matches the delivery goal, then make the encoder honour it exactly.
Solution Architecture
There is no universal best value — there is a best value per delivery mode. The diagram below places the common choices on the latency-versus-efficiency curve. For an on-demand podcast or video back catalogue, 6 seconds is the Apple-authored default and the safe starting point. Push to 4 when finer seeking matters more than cache efficiency. Only drop toward 2 seconds with low-latency HLS (LL-HLS) partial segments, because plain 2-second segments pay the object-count cost without the latency payoff that LL-HLS parts provide.
Whatever value you land on, one constraint is non-negotiable: the encoder GOP must equal fps × segment_duration so that every segment begins on an IDR frame. A segment that starts on a P-frame can’t be decoded independently, which breaks both seeking and rendition switching — the same alignment rule enforced when packaging HLS with FFmpeg and Bento4.
Implementation
Rather than hard-code a number, encode the delivery goal as policy and compute the segment length from it. The helper below returns a segment duration and the matching GOP, and refuses combinations that would place a boundary off an IDR frame.
# python 3.10+ (pure stdlib; drives ffmpeg/mp4hls parameters elsewhere)
from dataclasses import dataclass
@dataclass(frozen=True)
class SegmentPlan:
seconds: int
gop_frames: int
ll_hls_parts: bool
# Delivery goal -> recommended segment length (seconds).
POLICY = {
"vod_catalogue": 6, # Apple default; best all-round for on-demand
"fine_seek": 4, # chapter-heavy content where scrubbing matters
"low_latency_live": 2, # ONLY paired with LL-HLS partial segments
}
def plan_segments(goal: str, fps: float) -> SegmentPlan:
if goal not in POLICY:
raise ValueError(f"unknown delivery goal: {goal!r}")
seconds = POLICY[goal]
gop = round(fps * seconds)
# Guard: fps*seconds must be a whole number of frames, or boundaries drift.
if abs(gop - fps * seconds) > 1e-6:
raise ValueError(
f"fps={fps} × {seconds}s is not an integer frame count; "
f"pick a segment length that divides evenly (e.g. 4 or 6 at {fps} fps)."
)
return SegmentPlan(seconds=seconds, gop_frames=gop, ll_hls_parts=(goal == "low_latency_live"))
The EXT-X-TARGETDURATION tag the packager writes must be the rounded-up maximum segment duration in whole seconds; players use it to size their buffer, and Apple’s validator flags a value that is smaller than the longest real segment. With a fixed GOP the segments come out uniform, so TARGETDURATION equals your chosen length (the final, short segment doesn’t raise it). If you need sub-2-second glass-to-glass latency, the answer is not smaller regular segments but LL-HLS: keep 4–6 second parent segments and publish partial segments (EXT-X-PART) within them, so latency drops without multiplying full-segment object count. LL-HLS is only worth the added serving complexity for genuinely live delivery.
Verification
Confirm the chosen length actually landed in the package and that TARGETDURATION is honest.
# 1. TARGETDURATION should equal your chosen length (whole seconds, rounded up).
grep "EXT-X-TARGETDURATION" package/media-1/media-1.m3u8
# 2. Real segment durations should cluster tightly at that value (last one shorter).
grep "EXTINF" package/media-1/media-1.m3u8 | awk -F'[:,]' '{print $2}' | sort | uniq -c
# 3. Cross-check GOP alignment on the encoded rung: keyframe interval == fps × seconds.
ffprobe -v error -select_streams v:0 -show_entries frame=pict_type,pts_time \
-of csv=p=0 -read_intervals "%+#40" package_src/1080.mp4 | grep ",I" | head
# 4. Sanity-check object economics before publishing a large library.
find package -name '*.m4s' | wc -l # segments per asset; multiply by catalogue size
A correct package shows a single EXT-X-TARGETDURATION equal to the plan, EXTINF values all at that duration (bar the final tail segment), and I-frames landing exactly segment_duration apart in the ffprobe dump. If I-frames appear at irregular intervals, the GOP wasn’t pinned — fix the encoder before re-measuring seek behaviour, because no segment-length tuning will help a mis-aligned ladder.
Failure Modes & Edge Cases
| Edge case | Symptom | Remediation |
|---|---|---|
| Segment length too long (10s+) | Slow startup, coarse scrubbing complaints | Drop to 6s for VOD; reserve long segments for archival-only feeds |
| Segment length too short (2s) without LL-HLS | CDN request count and storage explode, cache fill ratio falls | Return to 4–6s, or adopt LL-HLS parts if latency is the real goal |
fps × seconds not an integer |
Boundaries drift; validator flags uneven durations | Choose a length that divides evenly at the source frame rate |
EXT-X-TARGETDURATION under real max |
mediastreamvalidator error; players under-buffer |
Round the tag up to the ceiling of the longest segment |
| Mixed segment lengths across renditions | Rendition switches stall at boundaries | Encode every rung against the same ABR_SEGMENT_DURATION |
| Re-segmenting a published library | Old segment URLs go stale in caches | Use content-hashed paths and purge per CDN cache invalidation |
FAQ
What segment duration should I start with for on-demand podcasts and video?
Start at 6 seconds. It is the Apple-authored default for VOD and balances startup time, seek granularity, and cache efficiency well for on-demand content. Move to 4 seconds only if finer scrubbing is a measured requirement, and treat anything below that as a live/low-latency decision rather than a VOD one.
Do shorter segments always mean lower latency?
Not on their own. Startup latency scales with roughly three segment durations, so shorter helps there — but for true low latency you want LL-HLS partial segments inside normal-length parents, not smaller full segments. Plain short segments pay the object-count and cache-efficiency cost without delivering the sub-two-second latency that LL-HLS parts do.
Why must the GOP match the segment duration exactly?
A segment has to begin with an IDR keyframe so a player can decode it without the previous segment. If the GOP is not fps × segment_duration, boundaries fall on P-frames, which breaks clean seeking and rendition switching. Pinning keyint and min-keyint to that product is what keeps every segment independently decodable.
Related
- Up to the parent guide: Adaptive Bitrate Packaging
- Sibling recipe: Packaging HLS with FFmpeg and Bento4
- Why alignment starts upstream: Extracting keyframe timestamps from MP4
- When you re-segment: Invalidating CDN cache on episode republish
- Pipeline overview: Media Delivery & Publishing Automation