The fastest way to test an HLS stream without installing anything is to paste the playlist URL into a browser-based player, then work down a short checklist if it fails. A good check covers the HTTP response, the playlist syntax, every variant, a sample of segments, the CORS headers, and any encryption key. Skipping straight to "does it play" hides which layer is broken.
How it works
An HLS delivery chain has four layers. First, the playlist request must return HTTP 200 with a playlist MIME type. Second, the text must be a valid UTF-8 playlist with the required tags: #EXTM3U on the first line, #EXT-X-VERSION, #EXT-X-TARGETDURATION, #EXTINF entries, and #EXT-X-ENDLIST for VOD. Third, every referenced URL - variants, segments, and #EXT-X-KEY URIs - must resolve and return the expected bytes. Fourth, if a web player reads them through JavaScript, each response needs CORS headers.
A master playlist looks like this:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=1600000,RESOLUTION=1280x720,CODECS="avc1.4d401f,mp4a.40.2"
v1600/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=854x480,CODECS="avc1.4d401e,mp4a.40.2"
v800/index.m3u8
Relative paths are resolved against the playlist URL, not against the page that embeds the player. That single rule explains most "works locally, 404 in production" reports.
Step-by-step: a repeatable test
1. Check the response headers.
URL="https://cdn.example.com/vod/master.m3u8"
curl -s -D - -o /dev/null -H "Origin: https://m3u8.space" "$URL" \
| grep -iE "HTTP/|content-type|access-control|cache-control"
curl -s "$URL" | head -12
Expect application/vnd.apple.mpegurl (or audio/mpegurl) and an Access-Control-Allow-Origin line. If the body is HTML, you are looking at a login page, a geo block, or a hotlink-protection redirect.
2. Validate variants and segments. Fetch the first media playlist, then the first two segments:
curl -s "$URL" | grep -E "^#EXT-X-STREAM-INF|^[^#]" | head -8
curl -s -o /dev/null -w "status=%{http_code} type=%{content_type} time=%{time_total}\n" \
"https://cdn.example.com/vod/v1600/seg_00000.ts"
ffprobe -v error -show_entries format=duration -of csv=p=0 \
"https://cdn.example.com/vod/v1600/seg_00000.ts"
3. Test playback in a real browser engine. Open the online HLS player, paste the URL, and watch the console. Use the stream inspector to list variants, segment durations, and encryption tags. A hosted player is a better signal than ffprobe because it exercises MSE, CORS, and codec support exactly as a viewer would.
4. Record the result in a checklist so a regression can be spotted on the next deploy:
| Check | Expected | What a failure means |
|---|---|---|
| Playlist status | 200 | Auth, geo rule, or wrong path |
Content-Type | HLS MIME type | Server falls back to text/plain |
| First line | #EXTM3U | Wrong file or truncated response |
TARGETDURATION | >= longest EXTINF | Invalid playlist, player may stall |
| Variant URLs | 200 for all | Ladder references missing renditions |
| First 2 segments | 200, correct bytes | Relative path or CDN purge problem |
BANDWIDTH | Matches measured bitrate | ABR picks an unsustainable level |
#EXT-X-KEY URI | 200, 16 bytes | Encrypted segments will fail to decode |
| CORS headers | Present on all hosts | Browser player blocked |
ENDLIST | Present for VOD | Player waits forever at the end |
Common mistakes and parameters
| Tool | Answers | Limitation |
|---|---|---|
curl -I | Status, MIME, CORS, cache | Does not decode media |
ffprobe | Codecs, duration, timestamps | Does not test browser CORS |
| Browser player | Real user experience | Needs a public URL |
| Stream inspector | Ladder and segment map | Needs CORS to read the playlist |
| Status | Typical cause | First action |
|---|---|---|
| 403 | Hotlink rule, token expiry, referer check | Compare with and without Referer |
| 404 | Relative path resolution | Print absolute URLs resolved from the playlist |
| 206 | Range request | Normal for media bytes, not an error |
| 301 / 302 | Redirect to another host | Check CORS on the final host too |
| 200 with HTML | Auth or error page | Inspect the body, not just the status |
Troubleshooting
- Safari plays but Chrome stalls: Safari may use native HLS while Chrome needs hls.js, so check CORS on every host in the chain.
- The first segment loads and the rest 404: the media playlist is stale relative to a live sliding window; refetch the playlist instead of caching it.
ffprobereports a valid duration but the browser shows a black frame: codec or profile is outside browser support; checkCODECSin the master playlist.- Everything works with
curlbut not in the player: anOPTIONSpreflight is failing, or the header is missing on 206 and 404 responses. - Encrypted playlists fail with a key error: confirm the key URI is reachable, returns exactly 16 bytes, and is served with CORS headers.
- Intermittent failures only for some viewers: a CDN edge is caching an error or an older playlist; purge it and retest with a cache-busting query string.
Related reading
- Open the online HLS player to run the playback part of this checklist.
- Inspect the ladder and segment timings with the stream analyzer.
- Browse all HLS guides for CORS, buffering, and ffmpeg conversion steps.
- How to Fix hls.js CORS Errors - diagnose cross-origin and playlist request failures.
- ffmpeg Command to Convert m3u8 to MP4 - remux a playlist into MP4 with ffmpeg.
- Why Your HLS Stream Keeps Buffering - tune the bitrate ladder and buffer targets.