Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/puemos/hls-downloader/llms.txt

Use this file to discover all available pages before exploring further.

The Fetch loader service provides resilient HTTP fetching with automatic retry logic and exponential backoff for downloading HLS fragments and playlists.

Overview

This service handles:
  • Downloading text-based playlists and manifests
  • Downloading binary fragment data as ArrayBuffers
  • Automatic retry with exponential backoff for transient network errors
  • HTTP error detection and propagation

API

The FetchLoader object exports two methods:
export const FetchLoader = {
  fetchText,
  fetchArrayBuffer,
};

Methods

fetchText

Fetches a resource as text with automatic retry.
async function fetchText(url: string, attempts: number = 1): Promise<string>

Parameters

url
string
required
URL of the resource to fetch
attempts
number
default:"1"
Maximum number of fetch attempts before giving up

Returns

Promise<string>
Promise resolving to the response body as text

Behavior

  • Uses the Fetch API to retrieve the resource
  • Checks response.ok and throws HttpError if status indicates failure
  • Retries on network errors (but not HTTP errors)
  • Applies exponential backoff starting at 100ms, increasing by 15% per retry

Example

import { FetchLoader } from "./fetch-loader";

// Fetch with 3 retry attempts
const playlist = await FetchLoader.fetchText(
  "https://example.com/playlist.m3u8",
  3
);
console.log(playlist);

fetchArrayBuffer

Fetches a resource as an ArrayBuffer with automatic retry.
async function fetchArrayBuffer(
  url: string,
  attempts: number = 1
): Promise<ArrayBuffer>

Parameters

url
string
required
URL of the resource to fetch
attempts
number
default:"1"
Maximum number of fetch attempts before giving up

Returns

Promise<ArrayBuffer>
Promise resolving to the response body as an ArrayBuffer

Behavior

  • Uses the Fetch API to retrieve the resource
  • Checks response.ok and throws HttpError if status indicates failure
  • Retries on network errors (but not HTTP errors)
  • Applies exponential backoff starting at 100ms, increasing by 15% per retry

Example

import { FetchLoader } from "./fetch-loader";

// Download fragment with retry
const fragmentData = await FetchLoader.fetchArrayBuffer(
  "https://example.com/segment0.ts",
  5
);
console.log(fragmentData.byteLength);

Retry mechanism

The retry logic is implemented in the internal fetchWithRetry function:
async function fetchWithRetry<Data>(
  fetchFn: FetchFn<Data>,
  attempts: number = 1
): Promise<Data>

Retry behavior

Initial delay
100ms
First retry waits 100ms after failure
Backoff multiplier
1.15
Each subsequent retry increases the delay by 15%
HTTP errors
no retry
HTTP errors (4xx, 5xx) are not retried and throw immediately
Network errors
retry
Network failures, timeouts, and other non-HTTP errors trigger retries

Retry timeline example

For attempts = 5:
  1. Initial attempt fails (network error)
  2. Wait 100ms, retry (fails)
  3. Wait 115ms (100 × 1.15), retry (fails)
  4. Wait 132ms (115 × 1.15), retry (fails)
  5. Wait 152ms (132 × 1.15), retry (succeeds)
If all retry attempts are exhausted, the last error is thrown to the caller.

Error handling

HttpError

Custom error class for HTTP status errors.
class HttpError extends Error {
  constructor(readonly status: number)
}

Properties

status
number
HTTP status code (e.g., 404, 500)
name
string
Always set to "HttpError"
message
string
Error message in format "HTTP {status}"

Example

try {
  await FetchLoader.fetchText("https://example.com/missing.m3u8", 3);
} catch (error) {
  if (error instanceof Error && error.name === "HttpError") {
    console.error("HTTP error:", (error as any).status);
  }
}

Error propagation

  • HTTP errors (4xx, 5xx): Thrown immediately as HttpError, no retries
  • Network errors: Retried up to the specified number of attempts
  • After all retries fail: The last error is thrown
  • Invalid attempts parameter: Throws "Attempts less then 1" if attempts < 1
HTTP errors are considered permanent failures and will not be retried. This includes 404 (Not Found), 403 (Forbidden), and 500 (Internal Server Error) responses.

Usage patterns

Fetching playlists

import { FetchLoader } from "./fetch-loader";

// Master playlist with 3 retries
const masterPlaylist = await FetchLoader.fetchText(
  "https://example.com/master.m3u8",
  3
);

// Media playlist with 5 retries
const mediaPlaylist = await FetchLoader.fetchText(
  "https://example.com/stream.m3u8",
  5
);

Downloading fragments

import { FetchLoader } from "./fetch-loader";

// Download multiple fragments with retry
const fragmentUrls = [
  "https://example.com/seg0.ts",
  "https://example.com/seg1.ts",
  "https://example.com/seg2.ts",
];

const fragments = await Promise.all(
  fragmentUrls.map((url) => FetchLoader.fetchArrayBuffer(url, 5))
);

Error handling with retries

import { FetchLoader } from "./fetch-loader";

try {
  const data = await FetchLoader.fetchArrayBuffer(
    "https://example.com/segment.ts",
    5
  );
  console.log("Downloaded:", data.byteLength, "bytes");
} catch (error) {
  if (error instanceof Error) {
    if (error.name === "HttpError") {
      console.error("HTTP error:", (error as any).status);
    } else {
      console.error("Network error after retries:", error.message);
    }
  }
}

Performance considerations

  • Concurrency: The loader itself does not limit concurrency. Use Promise.all with care to avoid overwhelming the server
  • Retry overhead: With 5 attempts and exponential backoff, total retry time can exceed 500ms
  • Memory: fetchArrayBuffer loads the entire response into memory as an ArrayBuffer
For production use, consider implementing concurrency limits using p-limit or similar libraries when downloading many fragments in parallel.

Source location

~/workspace/source/src/background/src/services/fetch-loader.ts:14

Build docs developers (and LLMs) love