Skip to main content
Live Streaming Architecture

How to Configure the DVR Window Size for Live M3U8 Streams

One of the most powerful features of HTTP Live Streaming (HLS) is the ability to provide a DVR window (Digital Video Recorder). This allows viewers watching a live stream to pause, rewind, and catch up on missed action without leaving the stream. In this guide, we dive deep into the mechanics of the sliding window playlist, how segment retention affects storage, and how to optimally configure your packager to deliver an excellent DVR experience.

Unlike Video on Demand (VOD) playlists that contain every segment from start to finish, live HLS streams utilize a "sliding window." As new segments are recorded and added to the bottom of the playlist, old segments are removed from the top. The number of segments kept in the playlist dictates the size of the DVR window.

The Anatomy of a Live M3U8 Playlist

Let's look at a typical live media playlist snapshot:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:1045
#EXTINF:6.000,
segment_1045.ts
#EXTINF:6.000,
segment_1046.ts
#EXTINF:6.000,
segment_1047.ts
#EXTINF:6.000,
segment_1048.ts
#EXTINF:6.000,
segment_1049.ts

There are two key observations here:

  • No #EXT-X-ENDLIST tag: This absence tells the player that the stream is ongoing and it should periodically reload the playlist to discover new segments.
  • The #EXT-X-MEDIA-SEQUENCE tag: This denotes the integer ID of the first segment currently in the playlist. As segments slide off the top, this number increments.

Calculating the DVR Window Size

The math behind a DVR window is straightforward:

DVR Window (Seconds) = Number of Segments in Playlist × Target Duration

For example, if your playlist retains 60 segments, and each segment is 6 seconds long (#EXT-X-TARGETDURATION:6), your DVR window is 360 seconds (6 minutes).

If you want to offer a 2-hour DVR window for a sports broadcast, you would need to retain 1,200 segments in the playlist (assuming 6-second segments).

Configuring the DVR in Common Packagers

The configuration to control this list size happens at the encoding/packaging layer. Here is how to adjust it in standard software tools.

FFmpeg

When using FFmpeg to generate an HLS stream, the -hls_list_size parameter controls the number of segments retained in the playlist. The -hls_time controls the segment length.

ffmpeg -i input_live_stream.mp4 -c:v libx264 -c:a aac -f hls \
  -hls_time 6 \
  -hls_list_size 60 \
  -hls_flags delete_segments \
  output.m3u8

In this example, FFmpeg keeps exactly 60 segments in output.m3u8. The delete_segments flag tells FFmpeg to delete the physical .ts files from the disk once they fall off the playlist, preventing your server's storage from filling up. If you want an infinite DVR (event playlist), you set -hls_list_size 0.

Nginx-RTMP Module

If you are using the popular Nginx-RTMP module, you control the DVR window using the hls_playlist_length directive.

application live {
    live on;
    hls on;
    hls_path /tmp/hls;
    hls_fragment 6s;
    hls_playlist_length 1h; # Provides a 1-hour DVR window
}

Live vs. Event Playlists

If you want users to be able to rewind all the way to the very beginning of a live event (like a keynote speech or a concert), you don't want a sliding window. You want an Event Playlist.

You signal this by adding #EXT-X-PLAYLIST-TYPE:EVENT to the manifest. In an Event playlist, segments are appended to the bottom, but nothing is ever removed from the top. The #EXT-X-MEDIA-SEQUENCE remains at 0 (or the starting segment). The playlist simply grows larger until the event finishes and an #EXT-X-ENDLIST tag is appended.

Memory Constraints

Very large DVR windows (e.g., a 24-hour retention resulting in 14,400 segments) create massive M3U8 files. A player fetching a 5MB text file every 6 seconds will consume tremendous bandwidth and may crash lower-end mobile browsers due to memory constraints when parsing the DOM.

Storage Costs

If your CDN or origin server is retaining segments for a 4-hour DVR window across 5 different ABR (Adaptive Bitrate) renditions, you must ensure your storage backend has the capacity to write and hold tens of thousands of files concurrently.

Client Player Behavior and Troubleshooting

Having a large DVR window configured on the server is only half the battle. The HTML5 player (like Video.js, HLS.js, or Shaka Player) must properly interpret the window.

Tuning the Live Edge

Players generally do not start playback on the absolute newest segment in the playlist. Doing so risks buffer underruns if the next segment is delayed by network jitter. By default, players start 3 target durations back from the live edge (roughly 18 seconds behind real-time). You can tune this in player configs, for instance in HLS.js:

var hls = new Hls({
  liveSyncDurationCount: 3, // Start 3 segments back
  liveMaxLatencyDurationCount: 10 // Max allowed distance before seeking to live
});

404 Errors on Seek

The most common issue with DVR windows is when a user pauses the stream for an extended period. If they pause for 10 minutes, but your DVR window (hls_list_size) is only 5 minutes, the segments they were buffered at will fall off the playlist and be deleted from the server. When they hit play, the player requests segments that yield HTTP 404 errors, causing playback to fail.

Solution: Always ensure your physical file retention on the origin server outlives your playlist window by at least 2-3 minutes to handle edge-case requests from lagging clients.

Summary of Best Practices

  • Balance Size vs Latency: A 2 to 4 minute DVR window is standard for 24/7 linear TV streams, allowing robust buffering without excessive playlist bloat.
  • Use Event Type for Shows: Use #EXT-X-PLAYLIST-TYPE:EVENT for distinct events where you want full VOD capability instantly after the broadcast ends.
  • Manage Origin Storage: Ensure your packager cleans up old .ts files gracefully to prevent disk exhaustion.
  • Optimize Segment Size: Shorter segments (2-4 seconds) reduce latency but increase playlist bloat for long DVR windows. Standardize on 6 seconds for most use cases.