← Back to blog

How to Download m3u8 / HLS Streams: Complete Guide 2026

What m3u8 actually is, why HLS streams need merging, and three practical ways to download them as a single MP4 — from beginner-friendly to power user.

You found a video you want to save. You opened the Network tab in Chrome, hit play, and saw a request to a .m3u8 URL. Now what? The file you can download from that URL is a 204-byte text file, not a video. The actual video is hundreds of small .ts files referenced inside it. Welcome to HLS — the streaming protocol that powers most live and on-demand video on the web in 2026.

This guide explains what HLS actually is, why “downloading m3u8” is more complicated than copying a URL, and walks through three practical methods from beginner to power user.

What is HLS, and what is m3u8

HLS stands for HTTP Live Streaming. Apple created it in 2009 as a way to stream video over standard HTTP connections without needing specialized streaming servers. It is now the dominant protocol for video on the web — broadcast networks, video-on-demand platforms, live streams, embedded lectures, and most ad-supported video all use it.

The trick is that an HLS “video” is not a single file. It is:

  1. A playlist file with a .m3u8 extension (essentially UTF-8 text)
  2. A list of media segments — usually .ts files (MPEG transport stream) or .m4s fragments (fragmented MP4) — typically 2-10 seconds long each
  3. Sometimes a separate audio playlist with its own audio segments
  4. Sometimes subtitle playlists

Your browser’s video player loads the playlist, downloads segments in order, and concatenates them in real-time as you watch. When you “download the m3u8,” you have only downloaded the table of contents.

What an m3u8 file actually contains

A typical media playlist looks like this:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0

#EXTINF:10.0,
segment000.ts
#EXTINF:10.0,
segment001.ts
#EXTINF:9.5,
segment002.ts
#EXT-X-ENDLIST

Each #EXTINF line declares the duration of the next segment. The #EXT-X-ENDLIST at the end marks this as VOD (video-on-demand); for a live stream, that line is absent and the manifest gets new segments appended every few seconds.

A master playlist is one level higher and points to multiple media playlists at different qualities:

#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=1280000,RESOLUTION=720x480
480p/playlist.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2560000,RESOLUTION=1280x720
720p/playlist.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080
1080p/playlist.m3u8

Modern master playlists also reference separate audio manifests, separate subtitle manifests, and sometimes alternative video streams (HDR, different codecs). For a deep dive on the audio/video separation specifically, see HLS Download Audio and Video Are Separate? Here’s How to Fix It.

Why “just download the m3u8 and rename to mp4” doesn’t work

This is the single most common mistake. The .m3u8 file is text; renaming it changes nothing about its contents. You have a 204-byte text file. Even if you somehow got the player to ingest it, it would try to fetch the segment URLs, which is exactly the work the original player was already doing.

To turn an HLS stream into a playable MP4, you need to:

  1. Resolve the master playlist → pick the quality you want → fetch the corresponding media playlist
  2. Fetch every segment listed in the media playlist (could be hundreds of files)
  3. If the stream uses encrypted segments (AES-128 or SAMPLE-AES), fetch the decryption key and decrypt each segment as it comes in
  4. Concatenate the segments in order
  5. If audio and video are in separate manifests, do all of the above twice in parallel and merge the result
  6. Fix any timestamp issues introduced by the concatenation
  7. Wrap the result in an MP4 container (or keep it as .ts for some uses)

This is enough work that you almost never want to do it manually. Below are three ways to delegate it.

Method 1: A Chrome extension (no command line)

A browser extension that runs inside the same tab as the video player can:

  • See the master playlist as the player loads it
  • Inherit the player’s authentication (cookies, headers, signed URLs)
  • Automatically pair audio and video manifests when they are split
  • Handle AES-128 decryption transparently
  • Mux the result into a single MP4 in the browser via FFmpeg.wasm

Video Downloader One-for-All is the extension we maintain for this purpose. The workflow:

  1. Install from the Chrome Web Store, pin to toolbar
  2. Visit a page with HLS content; the extension auto-detects manifests as the player loads
  3. Click the extension icon — toolbar badge turns blue when content is detected
  4. Pick the quality from the dropdown
  5. Click Download

The full feature surface for HLS is on the HLS downloader page. For technical context on what the extension does behind the scenes, this guide is the long-form companion.

Method 2: yt-dlp (command line, batch-friendly)

yt-dlp is a Python CLI that ingests media URLs and resolves the underlying stream automatically. It is excellent for batch operations, scripting, and pipelines. Install:

# macOS
brew install yt-dlp

# pip
pip install yt-dlp

Then to download an HLS stream:

yt-dlp "https://example.com/path/to/master.m3u8"

For the highest quality with audio and video merged:

yt-dlp -f "bv*+ba/b" --merge-output-format mp4 "https://example.com/master.m3u8"

The -f "bv*+ba/b" selector means “best video stream + best audio stream, falling back to the best single combined stream.” This handles separate audio/video manifests automatically.

When yt-dlp works well:

  • Public VOD with no auth wall
  • Sites that yt-dlp’s extractor library specifically supports (thousands listed)
  • Batch downloads from a list of URLs

When yt-dlp struggles:

  • Streams that require an authenticated browser session
  • Sites behind aggressive Cloudflare or token-rotation walls
  • Live streams (yt-dlp can record live HLS but reliability varies)
  • Sites with TLS fingerprint detection

For all of those, a browser extension is more reliable because it inherits the browser’s session.

Method 3: FFmpeg (one-liner, lowest level)

FFmpeg can handle an m3u8 URL directly:

ffmpeg -i "https://example.com/master.m3u8" -c copy output.mp4

This works when:

  • The master playlist is reachable without auth
  • Audio is muxed into the video segments (older HLS streams)
  • The CDN does not require special headers

For modern streams with separate audio and video, you need to specify both manifests:

ffmpeg \
  -i "https://example.com/video/1080p/playlist.m3u8" \
  -i "https://example.com/audio/eng/playlist.m3u8" \
  -c copy \
  -bsf:a aac_adtstoasc \
  output.mp4

If the CDN requires a Referer header (common for Bilibili, some Japanese broadcasters, and many embedded players):

ffmpeg \
  -headers "Referer: https://example.com/video-page" \
  -i "https://cdn.example.com/master.m3u8" \
  -c copy \
  output.mp4

FFmpeg is the most flexible tool but also the most fiddly. You often have to find URLs in DevTools, set the right headers, handle expiring tokens, and re-run if the stream rotates. Good for one-off forensics, painful for daily use.

How to find the m3u8 URL

If the page does not expose a direct download link (almost none do), find the m3u8 URL in DevTools:

  1. Open the video page in Chrome
  2. Press F12 or Cmd+Option+I to open DevTools
  3. Click the Network tab
  4. In the filter box, type m3u8
  5. Click play on the video
  6. The network log will show requests to one or more .m3u8 URLs
  7. The first one (often called master.m3u8 or playlist.m3u8) is the master playlist
  8. Right-click → Copy → Copy URL

If you see multiple m3u8 requests, the first one is usually the master, and subsequent ones are the per-quality media playlists. The master is what you want for FFmpeg/yt-dlp; the extension does this resolution automatically.

Common HLS gotchas

The m3u8 URL expires in 60 seconds

Many CDNs sign URLs with a short expiry (typical: 60-300 seconds). If you copy a URL and try it 5 minutes later, you get a 403. Solutions: refresh the page in the browser to get a fresh URL, or use a tool that runs in the browser context.

Segments use AES-128 encryption

You will see #EXT-X-KEY:METHOD=AES-128,URI="key.bin" in the manifest. The segments are encrypted and need the key to decrypt. yt-dlp and FFmpeg handle this automatically. Older browser extensions do not — they download the encrypted bytes and produce unplayable files.

Stream uses SAMPLE-AES (DRM-adjacent)

If you see #EXT-X-KEY:METHOD=SAMPLE-AES, the encryption is partial — only certain samples within each segment are encrypted, not the whole segment. This requires more sophisticated handling. yt-dlp covers most cases. Some sites (FairPlay, Widevine HLS) require keys that are only available through DRM systems — those are not legally downloadable and we do not support them.

Live stream cuts off after a few minutes

The live HLS manifest is a sliding window — it only contains the most recent ~30-60 seconds of segments. If you point FFmpeg at it once and walk away, the manifest will rotate and your download stops. For live recording, you need a tool that keeps re-fetching the manifest as new segments appear. Our extension’s live recorder does this; FFmpeg can with -live_start_index flags but the configuration is involved.

My downloaded file plays but has no audio

You have the audio/video manifest separation problem covered in the audio/video split companion article. You need to merge both manifests, not just download the video manifest.

Comparison: which method when

SituationBest tool
Curious one-off download from a public siteOnline m3u8-to-mp4 tool
Repeated downloads from sites in your browserChrome extension
Batch script for many URLs you already haveyt-dlp
Stream needs your login sessionChrome extension
Live stream recordingChrome extension or FFmpeg with live flags
Stream behind exotic auth (custom JWT, mTLS)FFmpeg with custom headers
Educational / debugging the protocol itselfFFmpeg + DevTools

Why Stream Recorder is no longer the answer

If you are reading this because Stream Recorder, the Chrome Web Store’s most-installed HLS extension, is no longer producing playable files — that’s a separate story. Stream Recorder has not received an update since 2025-08-01, and the modern audio/video manifest separation breaks it. We covered this in detail in Stream Recorder Not Working in 2026? Best HLS/m3u8 Alternative.

Bottom line

m3u8 is just a text file pointing at video segments. Downloading “the m3u8” gives you the table of contents, not the video. To get a playable file you have to fetch the segments, decrypt them if encrypted, merge audio with video if separated, and mux into MP4.

For most people, a Chrome extension automates all of that without leaving the browser. Install Video Downloader One-for-All if that fits your use case. For batch CLI work, yt-dlp is excellent. For surgical control, FFmpeg.

Each of the three has a place. The mistake to avoid is treating the m3u8 file as if it were the video.