Stream Video Downloader logo
← Back to Blog

How HLS Error Recovery Works: Segment Retry, Playlist Reload, and What hls.js Does When a Fetch Fails

July 22, 2026 · 7 min read

When a video plays smoothly, HLS looks simple: a playlist of segments, each downloaded and handed to the decoder in order. What you don’t see is the recovery machinery running underneath — retries, bitrate drops, and a series of escalating fallbacks that kick in whenever a fetch fails.

Understanding that machinery matters if you’re debugging a player, building a download tool, or just trying to understand why a live stream sometimes stutters before stabilizing.

The normal fetch cycle

An HLS player runs a continuous loop. It reads the media playlist, finds the next segment it needs, fetches it, hands it to the buffer, and updates the playlist if the stream is live. Each step can fail independently.

Segment fetches fail most often. A CDN edge server returns a 503. A network hiccup stalls the download past the timeout. A signed URL expires between playlist generation and the fetch. Each failure has a different correct response.

Retry: the first line of defense

For transient failures — a brief network drop, a 503 that clears in a second — the player’s first move is to retry the same segment from the same URL. hls.js allows you to configure the maximum retry count and the delay between attempts separately for fragments, playlists, and key fetches.

The default configuration retries segment fetches a small number of times with exponential backoff. If all retries fail, the player escalates.

Bitrate fallback: trading quality for reliability

If the current variant’s segments are consistently failing or taking too long, hls.js’s ABR (adaptive bitrate) logic interprets that as a bandwidth signal. It drops to a lower bitrate variant — smaller segments, smaller files, less likely to time out on a constrained connection.

This is why a struggling stream drops to a lower resolution before it freezes. The player is being conservative: get something to the decoder rather than nothing at the high bitrate.

Playlist reload failures and live streams

For live streams, the player polls the media playlist continuously. If the playlist itself fails to load — server error, stale CDN cache, network partition — the player first retries with increasing delays. If the playlist stays unavailable long enough, hls.js fires a NETWORK_ERROR event with subtype LEVEL_LOAD_ERROR.

Unlike segment failures, playlist failures are more serious. A missing segment means one chunk of the video is unavailable; a missing playlist means the player can’t discover any future segments at all. Most players surface this as a distinct, more urgent error state.

Key fetch failures: encryption errors

Encrypted HLS streams carry a #EXT-X-KEY tag pointing to a key URL. If that key fetch fails — the key server is down, the session has expired, the key URL has rotated — the player can’t decrypt the segments even if it downloads them successfully.

hls.js retries key fetches separately from segment fetches. A key failure fires a KEY_LOAD_ERROR event. If the session has expired and the site requires re-authentication, no amount of retrying will succeed without user action.

The error event hierarchy

hls.js distinguishes between recoverable and fatal errors. A recoverable error (such as a single segment timeout that resolves on retry) is surfaced through the Hls.Events.ERROR event but does not stop playback. A fatal error does stop playback and requires the application to decide what to do — destroy and reinitialize the player, show an error UI, or prompt the user to retry.

The distinction matters for building a download tool. A downloader looping over segments needs its own retry logic independent of the player layer. The retry behavior tuned for buffering doesn’t translate directly to the all-or-nothing requirement of a complete download.

What happens in this site’s converter when a segment fails

This site’s /convert route downloads all segments before handing them to ffmpeg.wasm. Each segment fetch goes through the extension bridge first (for CORS bypass), with a direct fetch() fallback.

If a segment fails on both paths, the converter surfaces an error and stops — it doesn’t silently skip the segment and produce a corrupt file. A complete download or an explicit failure is the only acceptable outcome when you’re building a file from sequential, ordered chunks.

Recovery at the download layer is simpler than at the playback layer: retry the same segment with a fresh URL from a fresh playlist fetch, or give up and tell the user. There’s no ABR logic because the converter picks a single variant at the start and commits to it.

Debugging HLS errors in the browser

The fastest way to diagnose an HLS problem is the Network tab in Chrome DevTools, filtered to XHR/Fetch requests. Look for:

  • Segment requests with 403 or 404 status — usually expired signed URLs
  • Segment requests that stall and timeout — CDN edge issues
  • Playlist requests that return a 200 but with stale content — caching problems on a live stream
  • Key requests that fail — encryption configuration or session expiry

The hls.js error event includes a details string that maps to specific failure types. Logging data.details in the error handler gives you the exact subtype — far more useful than a generic “playback failed” message.

Summary

HLS error recovery is layered: retries for transient failures, bitrate drops for sustained bandwidth problems, and explicit error events for failures the player can’t automatically resolve. The distinction between recoverable and fatal errors is what separates a player that self-heals from one that freezes.

For download tools, the simpler framing applies: fetch every segment, retry on failure, fail loudly rather than producing a broken file.