# How youtube-dl Handles HLS and DASH Streaming Formats for Downloads

> Learn how youtube-dl handles HLS and DASH streams by downloading segments, decrypting content, and assembling files. Discover its ffmpeg fallback.

- Repository: [youtube-dl/youtube-dl](https://github.com/ytdl-org/youtube-dl)
- Tags: deep-dive
- Published: 2026-02-25

---

**youtube-dl processes HLS and DASH streams through specialized fragment downloaders that download individual media segments, decrypt AES-128 encrypted content, and assemble the final file, falling back to ffmpeg when encountering unsupported manifest features.**

The youtube-dl project (`ytdl-org/youtube-dl`) implements a robust architecture for downloading modern adaptive bitrate streams. When an extractor identifies a streaming manifest—either an HLS `.m3u8` or DASH `.mpd`—it populates `info_dict['url']` with the manifest location and sets `format_id` values such as **`hls`**, **`dash`**, or **`dashsegments`**. During the download phase, the engine delegates to protocol-specific downloaders that handle the complexities of fragmented media delivery.

## Fragmented Download Architecture

Both HLS and DASH downloaders inherit from **`FragmentFD`**, defined in [`youtube_dl/downloader/fragment.py`](https://github.com/ytdl-org/youtube-dl/blob/main/youtube_dl/downloader/fragment.py). This base class implements the generic lifecycle for all fragmented downloads:

1. **Preparation** – Creates a temporary file, an optional `.ytdl` bookkeeping file for resumption, and instantiates an `HttpQuietDownloader` for lightweight fragment fetching.
2. **Progress Handling** – Registers a fragment-progress hook that updates the global progress bar and ETA as individual segments complete.
3. **Fragment Fetching** – Repeatedly calls `_download_fragment` while respecting `fragment_retries` and `skip_unavailable_fragments` settings.
4. **Assembly** – Appends raw fragment bytes to the destination stream, optionally cleaning up temporary files immediately after appending.
5. **Finalization** – Renames the temporary file to its final name, removes the `.ytdl` bookkeeping file, and reports completion.

## Native HLS Implementation (`HlsFD`)

The native HLS downloader resides in [`youtube_dl/downloader/hls.py`](https://github.com/ytdl-org/youtube-dl/blob/main/youtube_dl/downloader/hls.py) and handles standard HTTP Live Streaming manifests through the `HlsFD` class.

### Feature Detection and FFmpeg Fallback

Before downloading, `HlsFD.can_download` (lines 68-78) scans the manifest for unsupported features. The native downloader falls back to `FFmpegFD` when it encounters:

- **`#EXT-X-KEY`** with encryption methods other than AES-128
- Byte-range playlists (indicated by `#EXT-X-BYTERANGE`)
- Live-stream heuristics that suggest ongoing broadcasts

Users can override this behavior with `--hls-prefer-native` to force the native implementation or `--hls-prefer-ffmpeg` to always use the external tool.

### Manifest Parsing and Decryption

The downloader fetches the manifest via `urlh.read()`, then processes each line:

- Non-comment lines are treated as fragment URLs relative to the manifest base
- **`#EXT-X-KEY`** lines are parsed using `parse_m3u8_attributes` from [`youtube_dl/utils.py`](https://github.com/ytdl-org/youtube-dl/blob/main/youtube_dl/utils.py) to extract decryption parameters
- When `METHOD=AES-128` is detected, the key is downloaded (unless cached) and the fragment is decrypted using **PyCrypto** (`AES.new(...).decrypt`)
- **`#EXT-X-BYTERANGE`** specifications are converted into HTTP `Range` headers for the subsequent request

The decryption step is automatically skipped during unit testing when `test=True`.

### Ad Segment Filtering

`HlsFD` recognizes proprietary advertising markers such as `#ANVATO-…` and `#UPLYNK-…` in the manifest. These segments are silently dropped from the download queue and counted separately in progress reports rather than being processed as media fragments.

## DASH Streaming Support (`DashSegmentsFD`)

Dynamic Adaptive Streaming over HTTP is handled by `DashSegmentsFD` in [`youtube_dl/downloader/dash.py`](https://github.com/ytdl-org/youtube-dl/blob/main/youtube_dl/downloader/dash.py).

### Fragment List Structure

DASH extractors populate `info_dict['fragments']` with a list of dictionaries containing:

- **`url`**: The direct segment address, or `path` combined with `fragment_base_url`
- **`range`**: Optional byte-range specifications for partial segment downloads

### Initialization Segment Handling

The first segment in a DASH stream typically contains the MP4 initialization box required to make the file playable. `DashSegmentsFD` treats the first fragment as fatal (`fatal = frag_index == 1`); if this download fails, the entire operation aborts immediately rather than continuing with subsequent segments.

### Retry Logic and Byte Ranges

The downloader implements a retry loop beginning at line 51 of [`youtube_dl/downloader/dash.py`](https://github.com/ytdl-org/youtube-dl/blob/main/youtube_dl/downloader/dash.py). On any `HTTPError`, the fragment is retried up to `fragment_retries` times. When a fragment dictionary contains a `range` entry, the downloader constructs a `Range` header with the format `'bytes=%s'` to request only the specified portion of the resource.

## Command-Line Configuration Options

youtube-dl exposes fine-grained control over streaming behavior through options defined in [`youtube_dl/options.py`](https://github.com/ytdl-org/youtube-dl/blob/main/youtube_dl/options.py):

- **`--hls-prefer-native`** / **`--hls-prefer-ffmpeg`** – Explicitly choose between the native Python implementation and the external ffmpeg tool.
- **`--hls-use-mpegts`** – Download HLS fragments as MPEG-TS instead of MP4, affecting how fragments are concatenated during assembly.
- **`--fragment-retries`** – Set the number of retry attempts for failed fragments (applies to both HLS and DASH).
- **`--skip-unavailable-fragments`** – Continue downloading if individual fragments return 404 or other errors, rather than aborting.

### Practical Usage Examples

Force ffmpeg for HLS when the native downloader fails to handle the encryption method:

```bash
youtube-dl --hls-prefer-ffmpeg -f best https://example.com/stream.m3u8

```

Download a DASH manifest with aggressive retry settings and skip missing segments:

```bash
youtube-dl --fragment-retries 5 --skip-unavailable-fragments -f best https://example.com/manifest.mpd

```

Using the Python API to configure HLS preferences:

```python
from youtube_dl import YoutubeDL

ydl_opts = {
    'format': 'bestvideo+bestaudio',
    'hls_prefer_native': True,
    'fragment_retries': 3,
    'skip_unavailable_fragments': True,
}

with YoutubeDL(ydl_opts) as ydl:
    info = ydl.extract_info('https://example.com/video.m3u8', download=False)
    ydl.download([info['webpage_url']])

```

## Summary

- **youtube-dl** delegates HLS and DASH streams to specialized fragment downloaders (`HlsFD` and `DashSegmentsFD`) that inherit from the base `FragmentFD` class.
- The native **HLS downloader** in [`youtube_dl/downloader/hls.py`](https://github.com/ytdl-org/youtube-dl/blob/main/youtube_dl/downloader/hls.py) handles AES-128 decryption via PyCrypto, filters ad markers, and falls back to ffmpeg for unsupported features like non-AES encryption or live streams.
- The **DASH downloader** in [`youtube_dl/downloader/dash.py`](https://github.com/ytdl-org/youtube-dl/blob/main/youtube_dl/downloader/dash.py) processes explicit fragment lists, treats the initialization segment as fatal if missing, and supports byte-range requests for partial segments.
- Both downloaders respect `--fragment-retries` and `--skip-unavailable-fragments` for resilient downloads across unstable network conditions.

## Frequently Asked Questions

### When does youtube-dl use ffmpeg instead of the native HLS downloader?

youtube-dl falls back to ffmpeg when `HlsFD.can_download` detects unsupported manifest features in lines 68-78 of [`youtube_dl/downloader/hls.py`](https://github.com/ytdl-org/youtube-dl/blob/main/youtube_dl/downloader/hls.py). These include encryption methods other than AES-128 (such as SAMPLE-AES), byte-range playlists, or heuristics indicating a live stream. You can force the native downloader with `--hls-prefer-native` or always use ffmpeg with `--hls-prefer-ffmpeg`.

### How does youtube-dl handle encrypted HLS streams?

When the manifest contains `#EXT-X-KEY` with `METHOD=AES-128`, the `HlsFD` class downloads the specified key (caching it for reuse) and decrypts each fragment using PyCrypto's `AES.new(...).decrypt` before appending it to the output file. This occurs in [`youtube_dl/downloader/hls.py`](https://github.com/ytdl-org/youtube-dl/blob/main/youtube_dl/downloader/hls.py) and relies on `parse_m3u8_attributes` from [`youtube_dl/utils.py`](https://github.com/ytdl-org/youtube-dl/blob/main/youtube_dl/utils.py) to parse the key parameters.

### What happens if a DASH initialization segment fails to download?

The DASH downloader treats the first fragment (index 1) as fatal. If the initialization segment fails, the download aborts immediately (`fatal = frag_index == 1` in [`youtube_dl/downloader/dash.py`](https://github.com/ytdl-org/youtube-dl/blob/main/youtube_dl/downloader/dash.py)) because the MP4 file would be unplayable without this metadata. Subsequent fragments can be skipped if `--skip-unavailable-fragments` is set, but the initialization box is mandatory.

### Can I force MPEG-TS format instead of MP4 for HLS downloads?

Yes. Use the `--hls-use-mpegts` flag to keep fragments in MPEG-TS transport stream format rather than converting them to MP4 during assembly. This affects how `HlsFD` writes data to the destination file and is useful when you need the original broadcast format without container conversion.