An M3U8 tester checks that an HLS playlist loads and plays. This one goes further: it reads the master and media playlists, downloads at most 256 KB of the first segment to see which tracks really exist, tests the AES-128 key, detects DRM, live and on-demand streams, and prints the ffmpeg command that keeps picture and sound together.
What the tester checks
A player only tells you whether the video starts. The tester also reads what the player keeps to itself, from the playlists and from the first bytes of a real segment:
| Check | What you learn |
|---|---|
| Master or media playlist | Whether you have the playlist with every quality, or one variant copied from DevTools |
| Variants | Resolution, bitrate, codecs and frame rate of each quality, and the one a download would use |
| Separate audio | An audio track declared with EXT-X-MEDIA:TYPE=AUDIO and its own URI: the most common cause of silent downloads |
| Real tracks | Video only, audio only or both, read from the MPEG-TS program table, the fragmented MP4 init segment or raw AAC, MP3 and AC-3 |
| Encryption | None, AES-128 (the key URL is fetched and must return 16 bytes), SAMPLE-AES, or DRM |
| DRM | Widevine, PlayReady and FairPlay keys in the playlist, and CENC-encrypted init segments |
| Live or on-demand | Total duration and segment count, Low-Latency HLS, playlists still growing |
| Server answers | HTTP 403, 404 or 410, a web page returned instead of a playlist, a DASH manifest, an expired signed URL |
| Oddities | Byte ranges, discontinuities from inserted ads, segments disguised as .jpg or .png |
Why a stream plays but downloads without sound
Adaptive streams often keep the audio apart from the picture. The master playlist lists the qualities on #EXT-X-STREAM-INF lines and points each of them to an audio group. When that group has its own URI, the video playlist carries pictures only, and the player quietly loads two playlists at once.
Copy only the video URL into ffmpeg and you get a silent file. The fix is two inputs mapped together:
ffmpeg -i "video.m3u8" -i "audio.m3u8" -map 0:v:0 -map 1:a:0 -c copy out.mp4
The tester finds both URLs and prints that command for you. When you paste a single variant, it reads the first segment: if there is no audio track inside, it says so and tells you to go back to the master playlist. Other causes of silent files, such as a muted preview or an audio codec the container cannot hold, are covered in the guide to videos downloaded without sound.
Reading the result
- Green check: confirmed, nothing to do. For example "Picture and sound found (H.264 + AAC)".
- Blue i: useful context, such as byte-range segments or a browser User-Agent being required.
- Yellow !: a download would work but needs care: separate audio, discontinuities, a signed URL close to expiry.
- Red ✗: blocking. A silent variant, a forbidden playlist or key, DRM. No command is printed for DRM content.
AES-128 is not DRM
AES-128 is the standard encryption of HLS. The playlist names a key URL, and any player allowed to fetch that key can decrypt the segments, ffmpeg included. The tester fetches the key, checks that it is 16 bytes long, and decrypts the start of the first segment to read its tracks. If the key returns 403, the site wants a cookie, a token or a Referer that the request did not carry. The encrypted M3U8 guide walks through those cases.
Widevine, PlayReady and FairPlay are DRM: the key never leaves a licensed decryption module. The tester names the system and stops there.
Testing a live stream
A playlist without #EXT-X-ENDLIST is live. The preview starts near the live edge, and the printed command records from now until you press q, into a .ts file that stays playable even if the recording is cut. A sliding live playlist cannot give back what aired before you started.
Use it from the command line
The engine of this page is open source: m3u8-doctor on GitHub, MIT licensed, with no dependencies. In a terminal it is not limited by CORS and it can send the headers a site expects:
npx m3u8-doctor "https://cdn.example.com/hls/index.m3u8" --referer "https://example.com/watch/42"
It prints the same diagnosis, quotes the command for bash, zsh or PowerShell, and has a --json mode for scripts. To convert the stream once it is diagnosed, the M3U8 to MP4 guide compares ffmpeg with the other methods.
Limits worth knowing
- A web page can only read streams whose server allows it (CORS). Many video sites allow only their own player: the tester then says it could not read the stream, which says nothing about the stream itself.
- Browsers do not let a page set the Referer, cookies or User-Agent of its requests. Sites that check them work from the command line or from the page where the video plays.
- The tester reads at most 256 KB of the first segment (or an init segment of up to 2 MB) per playlist. It never downloads the whole video.
- DASH manifests (
.mpd) are recognized and sent to the DASH MPD analyzer.