How to Inspect an M3U8 Stream with Browser Developer Tools
Trace HLS manifests, variants, segments, keys, CORS headers, timing, and player errors in Chrome, Safari, or Firefox without exposing private tokens.
Browser developer tools can turn a generic โvideo failed to loadโ message into a specific failed manifest, segment, key, header, or decoding stage. The key is to capture the session from its first request and follow the HLS dependency chain in order.
This guide uses M3U8Online as the player, but the workflow applies to most browser-based HLS players. Inspect only streams you own or are authorized to access. Developer tools expose URLs, cookies, request headers, and responses that may grant access to private media, so sanitize everything before sharing.
Prepare the capture before pressing Play
Open the player page, then open developer tools:
- Chrome or Edge:
F12,Ctrl+Shift+I, orCommand+Option+I. - Firefox:
F12,Ctrl+Shift+I, orCommand+Option+I. - Safari on macOS: enable developer features in Safari settings, then open Web Inspector.
Select Network and apply these settings:
- Enable Preserve log so navigation and redirects do not erase the first manifest request.
- Enable Disable cache while developer tools are open. This prevents a cached success from hiding the current server behavior.
- Clear the existing log.
- Reload the page.
- Paste the authorized playlist URL and start playback.
Do not begin by filtering to โMediaโ only. Depending on the browser and playback library, .m3u8 files may appear as Fetch/XHR or another request type, while native HLS may expose fewer internal requests. Start with All, then search by URL or extension.
Find the HLS request chain
Use the filter box with terms such as:
m3u8
.ts
.m4s
.mp4
.aac
.vtt
key
Chrome also supports property filters such as status-code:403, domain:cdn.example.test, or url:m3u8. A typical multivariant session appears in this order:
master.m3u8
โโ 720p/index.m3u8
โโ audio/en.m3u8
โโ subtitles/en.m3u8
โโ init.mp4
โโ segment-48120.m4s
โโ segment-48121.m4s
โโ segment-48122.m4s
The exact order varies, but the relationship matters. The master or multivariant playlist selects child playlists; media playlists then identify initialization data, encryption keys, subtitles, audio, and media segments.
What our successful capture contained
For the September 3, 2026 release-candidate check, we played the public Mux x36xhzz VOD playlist in M3U8Online. Bundled Chromium 151, Chrome 152, and Edge 152 all reached readyState 4 with no media error. The decoded video size was 320 ร 184 and the reported duration was 634.634 seconds.
A Chrome 152 capture included the following chain:
| Resource | HTTP result | Response content type |
|---|---|---|
Top-level x36xhzz.m3u8 | 206 | audio/mpegurl |
Selected child .m3u8 playlists | 206 | audio/mpegurl |
Selected .ts segments | 206 | application/octet-stream |

Video frame: ยฉ 2008 Blender Foundation / Big Buck Bunny, CC BY 3.0.
The segment content type was generic, yet playback succeeded. That observation is not permission to ignore MIME configuration; it is a reminder to record the complete chain and the actual media outcome before assigning a single header as the cause.
Inspect the manifest response
Click the first .m3u8 request and review:
Headers
Record the request URL, final URL after redirects, status code, request method, origin, referrer, and response content type. For JavaScript-based cross-origin playback, inspect Access-Control-Allow-Origin and, when credentials are used, Access-Control-Allow-Credentials and Vary: Origin.
Useful content types include:
Content-Type: application/vnd.apple.mpegurl
Content-Type: application/x-mpegURL
A filename ending in .m3u8 does not guarantee that the response is a playlist.
Response
When the browser exposes the body, the first non-empty line should be #EXTM3U. If the body begins with HTML, a JSON error, a bot challenge, or a login page, the URL did not return an HLS manifest.
Our controlled 200 text/html fixture produced net::ERR_BLOCKED_BY_ORB in Chromium 151. Browser automation recorded a failed request rather than a normal response event; we did not manually inspect a DevTools Response tab in that run. Record the blocked result and check the origin response or server logs. Do not report a parser error unless the player actually received the body.
Classify the playlist:
#EXT-X-STREAM-INFindicates a multivariant playlist whose next URI is a media playlist.#EXTINFand media segment URIs indicate a media playlist.- A file containing both multivariant and media-segment tags is invalid.
Copy a child URI from the response and resolve it against the final manifest URL. Check that the browser requested the same URL you expect.
Timing
The Timing tab separates connection setup, waiting for the server, and content download. One slow request may be an origin or CDN issue. A repeating pattern where every segment completes after more media time than it contains means the selected bitrate is not sustainable, even if all statuses are 200.
Initiator
Chrome's Initiator information can show which script or request chain caused a resource to load. This helps distinguish a player request from unrelated page traffic and can reveal whether a child playlist was discovered from the intended manifest.
Read status codes as evidence
Sort or filter the request list by status:
| Status or browser result | What it usually proves | Next check |
|---|---|---|
(blocked:mixed-content) | HTTPS page attempted to load HTTP media | Use a valid HTTPS stream origin |
| CORS error with server response | Browser received a response but would not expose it to script | CORS headers on that exact resource |
| 301 / 302 / 307 / 308 | Resource moved | Final URL, lost query parameters, CORS on final response |
| 401 | Authentication was not accepted | Authorization method and token validity |
| 403 | Server understood but refused the request | Signature, cookie, origin/referrer, geography, segment permissions |
| 404 | Requested resource does not exist | Relative path, live window, publication timing, cache |
| 410 | Resource intentionally expired or removed | Refresh URL or live playlist |
| 429 | Rate limit applied | Retry policy and CDN/origin limits |
| 5xx | Origin or intermediary failed | Server logs, CDN status, packager health |
Inspect the response body when the browser makes it available. A CDN may include an error code or request ID that the HTTP status alone does not reveal; CORS, mixed-content, or ORB enforcement can prevent the body from being exposed.
Check CORS across every resource
A frequent mistake is adding CORS only to master.m3u8. hls.js may need cross-origin access to:
- child media playlists
- MPEG-TS or fMP4 segments
- initialization sections
- AES keys
- subtitle playlists and WebVTT files
- alternate audio playlists and segments
For public non-credentialed media, Access-Control-Allow-Origin: * may be appropriate. For credentialed access, the server must return an allowed origin explicitly; the wildcard cannot be combined with credentials. Apply the narrowest access policy that meets your use case.
If Safari native HLS plays but Chrome and Firefox report CORS, that does not show the JavaScript player is broken. It shows the two playback paths interact with browser security differently.
In a separate JavaScript probe, our fixture returned Access-Control-Allow-Origin: https://wrong.example to http://localhost:3000. The browser's built-in fetch() rejected it with TypeError: Failed to fetch, and Console named the origin mismatch. The player's native-HLS run showed a generic error without a CORS diagnostic. These are separate observations: the fetch test proves JavaScript access was blocked, but does not establish the cause of the native player's failure.
The generic player screenshot contains no Network or Console trace. For diagnosing this case, the specific fetch error is more useful than the player image.
Verify child URLs and signatures
Open the manifest Response tab and compare every relative URI with the actual Network request. Check for:
- a missing directory after URL resolution
- double slashes or incorrect case
- a redirect to another hostname
- query parameters present on the master but absent from child resources
- signatures that expire during playback
- a cookie scoped to the manifest host but not the segment host
- a key URI protected by a different authorization policy
Query parameters are not automatically inherited by relative child URLs. If your authorization design requires a signature on every resource, generate signed URIs for the entire playlist graph or use another supported delivery mechanism.
Inspect segment timing and live behavior
For a healthy live session, new segments should become available as the media playlist advertises them. Look for repeating patterns:
Repeated 404 on the newest segment
The playlist may be published before the segment reaches the origin or CDN. Compare the playlist fetch time, media sequence, segment URL, cache age, and first successful retry.
Successful but slow segment requests
Compare download time with the segment's #EXTINF duration. If a six-second segment repeatedly takes longer than six seconds to arrive, the buffer will eventually drain unless the player switches down or already has enough reserve.
Gaps after a discontinuity
Locate #EXT-X-DISCONTINUITY, the segments before and after it, and any new initialization section. Decoder or append errors near that point suggest a timestamp, track, codec, or packaging transition problem.
Live window moved past the requested segment
A paused or recovering client may ask for a segment that the sliding playlist and origin have already removed. Record #EXT-X-MEDIA-SEQUENCE, player position, and the response status before increasing retry counts.
Use the Console with the Network panel
Network proves what moved over HTTP; Console reveals what the browser or player rejected. Look for:
- CORS and mixed-content messages
- autoplay promise rejection
- manifest parsing errors
- unsupported or incompatible codec messages
- SourceBuffer append failures
- media decoding errors
- hls.js network, media, mux, buffer, and key-system details
If you control the player integration, log structured hls.js error data:
hls.on(Hls.Events.ERROR, (_event, data) => {
console.table({
type: data.type,
details: data.details,
fatal: data.fatal,
url: data.url || data.frag?.url,
status: data.response?.code,
message: data.error?.message,
});
});
Start with the earliest relevant failed request and error event, including nonfatal events. A fatal event may come after several retries and hide the initial cause.
List media resources from the Console
When a large page makes many requests, the Resource Timing API can provide a quick list of URLs whose names look like HLS resources:
performance.getEntriesByType('resource')
.filter(entry => /\.m3u8|\.m4s|\.ts|\.vtt|\/key/i.test(entry.name))
.map(entry => ({
url: entry.name,
startMs: Math.round(entry.startTime),
durationMs: Math.round(entry.duration),
bytes: entry.transferSize,
}));
This is a convenience view, not a replacement for Network. Browser privacy rules, cache behavior, and server timing headers affect which fields are available.
Chrome, Firefox, and Safari differences
Test coverage: The release-candidate observations in this article were reproduced on Windows with Chromium 151, Chrome 152, and Edge 152. Firefox and Safari were not available locally; the sections below describe the documented inspection workflow and still require a real run on those target platforms.
Chrome and Edge
Use Network request filters, Headers, Response, Initiator, and Timing. hls.js requests are usually visible because JavaScript fetches the playlists and media for MSE playback.
Firefox
The Network Monitor request detail pane provides Headers, Response, Cache, Timings, Security, and stack information where available. Compare the first failed request with Chrome before treating it as a Firefox-only problem.
Safari
Web Inspector provides Network and Console views, but native HLS may expose a different level of request detail from hls.js. Record what is visible and combine it with the video element's error, Apple validation tools, and a direct media-playlist test.
Exporting a HAR safely
A HAR file can preserve URLs, headers, timing, redirects, and sometimes response content. It can also contain credentials and signed media URLs. Before sharing:
- Work on a copied file, not the only original.
- Remove
Authorization, cookies, bearer tokens, signatures, session identifiers, and private hostnames. - Check query strings and response bodies manually.
- Replace secrets consistently so related requests can still be compared.
- Restrict distribution and expire any credentials that may have leaked.
For many incidents, a smaller report is safer: first failed URL pattern, sanitized headers, status, timing, hls.js error fields, browser version, and UTC timestamp.
A ten-point inspection checklist
- Capture from page reload with Preserve log enabled.
- Find the first manifest and verify its final URL.
- Confirm status, content type, CORS, and a
#EXTM3Uresponse. - Classify it as multivariant or media.
- Follow one variant to its first initialization file or segment.
- Check alternate audio, subtitle, and key requests.
- Identify the earliest failed status or blocked response.
- Compare segment transfer time with media duration.
- Correlate the first network failure with the first Console or hls.js error.
- Save a sanitized, reproducible report.
Following the chain in order prevents three common mistakes: blaming the codec for a 403, blaming CORS for an invalid playlist body, or blaming the player for a segment the origin never published.