Stream Video Downloader logo
← Back to Blog

Live HLS vs. VOD: How the Playlist Difference Affects Recording

July 21, 2026 · 6 min read

The HLS overview poston this blog describes media playlists, segment files, and the master playlist that ties them together. What it doesn't spend much time on is the difference between a live playlist and a VOD playlist — two formats that look almost identical in a text editor but behave completely differently when you try to record or convert them. That difference is also exactly what /play?broadcast=1is signaling to this site's player.

The three playlist types HLS defines

RFC 8216 defines an optional #EXT-X-PLAYLIST-TYPE tag that a media playlist can carry. It takes one of two values:

  • #EXT-X-PLAYLIST-TYPE:VOD — the playlist is complete and will never change. All segments are listed. The file ends with #EXT-X-ENDLIST. A client can fetch the playlist once and download all segments without re-polling.
  • #EXT-X-PLAYLIST-TYPE:EVENT — segments are added to the end of the playlist as the event progresses, but existing segments are never removed. The playlist grows over time and ends with #EXT-X-ENDLIST only when the event finishes.

The third case — a live sliding window — carries no #EXT-X-PLAYLIST-TYPE tag at all. New segments are appended and old segments are removed as the window advances, so the playlist represents only a short slice of the stream (typically 3–6 segments, or roughly 15–30 seconds of content). There is no #EXT-X-ENDLIST, and the playlist file is different on every poll.

Why VOD converts cleanly

When /convertreceives a VOD playlist URL, it has everything it needs up front. The playlist lists every segment, all segment URLs are stable (they point to files that already exist on the CDN and won't be removed), and the total duration is known before a single byte of media is downloaded. This is the same reason ffmpeg.wasm can operate in a single pass: it downloads all the segments, passes them sequentially to FFmpeg's stream-copy mux, and writes a complete MP4 output file. There is no ambiguity about when to stop, and no race against a CDN that might expire a segment URL mid-download.

Why live recording is a different problem

A live sliding-window playlist changes the constraints entirely. There is no #EXT-X-ENDLIST, so a naive downloader that polls until it sees that tag will poll forever. The playlist only ever contains the most recent few segments, so any segment that scrolls off the window before it's downloaded is gone — CDNs commonly expire short-lived live segments after 30 to 120 seconds. And because the stream is ongoing, the total duration is unknown; the converter can't pre-allocate a buffer or report meaningful progress.

Recording a live stream correctly requires a different architecture: poll the playlist on a short interval (matching the target segment duration — typically 2–10 seconds), track which segment URIs have already been downloaded to avoid fetching the same one twice, and accumulate segments into a buffer that grows as the recording continues. This is fundamentally a streaming write problem, not a batch download problem. The Low-Latency HLS post describes the polling mechanics in more detail — blocking playlist reload, partial segments, and the #EXT-X-PART tag that LL-HLS adds to reduce latency further.

What /play?broadcast=1 actually does

The extension popup sets the broadcast=1 parameter automatically when it detects a stream that looks like a live broadcast rather than a VOD asset. On the player side, this flag changes two things:

  • lowLatencyMode is enabled in hls.js. This turns on the partial-segment fetch and blocking-reload behavior described in the Low-Latency HLS post, which reduces playback latency by several seconds compared to the default live playlist polling behavior.
  • A LIVE badge is shown in the player UI.The badge signals to the viewer that they are watching a live stream, not scrubbing through a recorded VOD asset. This matters for user expectation: seeking behavior is limited for a live stream, and the “current” position moves with the live edge rather than being a fixed point in a complete file.

Neither change affects the underlying HLS fetch or playback mechanism — hls.js handles both VOD and live streams without explicit differentiation — but they tune the player's behavior for the live use case and give the viewer accurate context about what they're watching.

Why /convert doesn't support live streams

The converter at /convert is designed around the VOD model: download all segments, concatenate, mux into MP4, download the file. It has no playlist-polling loop and no mechanism to accumulate segments over time. A live stream passed to the converter would either time out (if it waits for #EXT-X-ENDLIST that never arrives) or produce only the handful of segments present in the playlist at the moment of the first fetch — a few seconds of content, not a recording of the broadcast.

Supporting live recording properly would require a separate recording mode: a timer-based UI that shows elapsed recording time and a stop button, a polling loop that tracks seen segments, and a streaming write to an accumulating buffer rather than a fixed-size download. That is a different tool from the batch converter, and the reason the extension routes live streams to /play?broadcast=1 rather than to/convert.

How to tell VOD from live in a playlist

If you have the raw playlist text, the indicators are unambiguous:

  • VOD: #EXT-X-PLAYLIST-TYPE:VOD present, or #EXT-X-ENDLIST present, or both. The segment list is stable.
  • EVENT: #EXT-X-PLAYLIST-TYPE:EVENT present, no #EXT-X-ENDLIST yet. Segments accumulate; the playlist will eventually end.
  • Live sliding window: No #EXT-X-PLAYLIST-TYPE tag, no #EXT-X-ENDLIST. The playlist changes on every fetch; old segments disappear.

This site's own parser in lib/m3u8-parser.ts reads segment URIs and the #EXT-X-ENDLIST tag but does not currently inspect #EXT-X-PLAYLIST-TYPE. The live vs. VOD distinction that drives broadcast=1is made by the extension's content script at detection time, not by the website's parser after the URL arrives.