How to Download m3u8 (HLS) Streams on Mac in 2026 — Including AES-128 Encrypted
HLS (HTTP Live Streaming) is how most of the web delivers video in 2026. Instead of one big MP4, the server hands the player a small text file called an m3u8 playlist, which points to hundreds of small .ts chunks the player assembles in real time. It's efficient, it's adaptive, and it's really annoying to download — because there's no single file.
This guide covers three practical paths, from easiest to most technical:
- Using shooff's built-in HLS capture (no CLI)
- Using yt-dlp on the command line
- Using ffmpeg directly when yt-dlp won't do
It also covers the thing that trips up most casual downloaders: AES-128 encrypted HLS streams where the decryption key requires session authentication.
First: what you're dealing with
An m3u8 file looks like this when you open it in a text editor:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:6
#EXT-X-KEY:METHOD=AES-128,URI="https://cdn.example.com/key?token=xyz"
#EXTINF:5.0,
segment_001.ts
#EXTINF:5.0,
segment_002.ts
#EXTINF:5.0,
segment_003.ts
...
#EXT-X-ENDLIST
Key lines:
#EXT-X-KEY: the stream is AES-128 encrypted. Without fetching this key URL, the segments are meaningless noise.- The
.tslist: hundreds of 5-10 second segments. Downloading them all takes forever sequentially, so any real downloader fetches them in parallel. - A master playlist: sometimes what you grab is actually a master playlist that lists other playlists (one per bitrate). You pick one (usually the highest bandwidth) and follow that.
A proper HLS downloader handles all of this. The challenge is when the key fetch needs cookies.
Path 1: shooff (easiest, GUI)
shooff has a built-in webview that watches every HTTP request the page makes. When it spots an .m3u8 URL, it pops a download button into the UI. Because the playlist is fetched inside an authenticated browser session, the AES key URL is also fetchable.
- Open shooff. Click the Browser tab.
- Paste the video page URL into the address bar. Hit Enter.
- Play the video (or just let the page load — shooff catches m3u8 requests either way).
- A stream detected card appears with the title, resolution, and duration. Click + Add to Queue.
- shooff downloads segments in parallel (16 concurrent), fetches the AES key through the webview session, merges everything to MP4, and encrypts the result into your library.
No command line. No manual key handling. No session cookie export. For most people this is where the guide ends. Download shooff if you want this.
Path 2: yt-dlp (CLI, free)
yt-dlp is the standard open-source tool. Install with Homebrew:
brew install yt-dlp ffmpeg
Unencrypted m3u8
If you already have the m3u8 URL:
yt-dlp "https://cdn.example.com/stream/master.m3u8"
yt-dlp will fetch the playlist, download all segments, and remux them to MP4. Default output filename is [title].mp4 in the current directory.
AES-128 encrypted m3u8 (with public key URL)
If the key URL doesn't need cookies:
yt-dlp -N 16 "https://cdn.example.com/stream/master.m3u8"
The -N 16 flag downloads 16 segments concurrently, which is much faster.
AES-128 with session-authenticated key
This is the hard case. You need to pass the same cookies the browser used to the tool:
- Install a cookie exporter extension in Firefox or Chrome (like "Get cookies.txt LOCALLY").
- Open the video page, play the stream briefly, then export cookies for that domain to a file.
- Run yt-dlp with the cookie file:
yt-dlp --cookies cookies.txt \
--referer "https://originsite.example.com/" \
-N 16 \
"https://cdn.example.com/stream/master.m3u8"
The --referer flag is often needed because the CDN checks it before serving anything.
Open the video page in Safari or Chrome, open the Developer Tools → Network tab, play the video, and filter requests by m3u8. Right-click the first matching request and copy the URL. For master playlists, look for a list of bitrate-tagged child playlists and pick the highest.
Path 3: ffmpeg directly
ffmpeg can consume HLS natively. It's less friendly than yt-dlp but occasionally handles edge cases yt-dlp chokes on.
Simple case
ffmpeg -i "https://cdn.example.com/stream/master.m3u8" \
-c copy \
output.mp4
-c copy tells ffmpeg to remux without re-encoding, which is fast and lossless.
With cookies and referer
ffmpeg -headers "Referer: https://site.example.com/\r\nCookie: session=xyz\r\n" \
-i "https://cdn.example.com/stream/master.m3u8" \
-c copy \
output.mp4
With a local m3u8 file (key URL rewritten)
Sometimes the m3u8 references URI="https://..." for the AES key but that URL needs cookies ffmpeg can't pass. Workaround: save the m3u8 locally, fetch the key separately, rewrite the URI to a local file path, then feed the local m3u8 to ffmpeg:
# 1. Fetch the original m3u8
curl --cookie "session=xyz" -o stream.m3u8 "https://cdn.example.com/master.m3u8"
# 2. Fetch the key with the same cookie
curl --cookie "session=xyz" -o key.bin "https://cdn.example.com/key?token=xyz"
# 3. Edit stream.m3u8, replace URI="https://..." with URI="./key.bin"
# 4. Feed local playlist to ffmpeg
ffmpeg -allowed_extensions ALL \
-protocol_whitelist file,http,https,tcp,tls,crypto \
-i stream.m3u8 \
-c copy output.mp4
The -allowed_extensions ALL and -protocol_whitelist flags are required for ffmpeg to accept local playlist files that reference mixed protocols.
This is exactly what shooff does internally — but in a UI, with the session cookies taken from the authenticated webview automatically.
Common errors and fixes
"HTTP error 403 Forbidden"
Missing referer or cookie. Add --referer and --cookies in yt-dlp, or use shooff's built-in browser which handles both automatically.
"Unable to decrypt data"
The AES key didn't download correctly (wrong cookie, expired token). Refresh the page, re-export cookies, try again. Tokens often expire in minutes.
Output file is choppy or has gaps
Some segments failed to download. Increase concurrency carefully — too aggressive and the CDN rate-limits you. -N 8 is usually safe.
"Non monotonic DTS" warning but file plays
Harmless timestamp warning from ffmpeg on some HLS sources. The file is fine.
Master playlist but you only got 480p
yt-dlp selects by format by default. Use -f "bv*+ba/b" to force best video + best audio.
When to use which tool
- shooff: you don't want to touch the terminal, the stream requires authentication, or you want the file encrypted in your library afterwards.
- yt-dlp: you're comfortable with CLI, want maximum control, or are scripting.
- ffmpeg: something exotic — streams with unusual key delivery, DASH + HLS fallbacks, or format manipulation.
Frequently asked
Is downloading HLS streams illegal?
Downloading streams you're authorized to access (content you purchased, content published under Creative Commons, content on sites that permit downloads) is generally legal. Downloading copyrighted content for redistribution, or bypassing DRM, is not. DRM-protected HLS (Widevine, FairPlay) is a separate thing — the tools in this guide don't touch it. This guide covers AES-128-encrypted streams, which are a transport-level obfuscation, not DRM.
What's the difference between AES-128 in HLS and DRM?
AES-128 in HLS is just transport encryption — it protects the stream in transit. The key is delivered to the player (with optional access control). DRM (Widevine, FairPlay, PlayReady) uses a hardware-backed key exchange and prevents the key from ever being accessible to user-space tools. You can't download Widevine-protected content with any of these tools.
How big will my downloaded file be?
Roughly the sum of all segment sizes. For 1080p, expect about 100-200 MB per hour. Lower bitrate streams will be smaller. Your downloader re-muxes segments into MP4 without re-encoding, so the file size matches the source.
Can I download HLS live streams?
While the stream is live, yt-dlp can record it in real time with --live-from-start. After the stream ends, the VOD replay (if the site offers one) is just a regular HLS download. For shooff, use the webview to open the live page and it'll start capturing automatically.
The stream works in Safari but not in a downloader. Why?
Usually the CDN is checking the User-Agent or Referer header and serving errors to anything that doesn't look like a browser. Pass --user-agent and --referer in yt-dlp, or use shooff's browser-based capture.
Can I convert m3u8 to MP4 after I already downloaded the .ts files?
Yes, if you have all the segments and (for encrypted streams) the AES key. Concatenate the decrypted .ts files: cat *.ts > combined.ts then remux: ffmpeg -i combined.ts -c copy output.mp4. Much easier to start with the m3u8 URL and let a tool do the whole pipeline.