Back to blog

Understanding CORS: Why Your M3U8 Stream Won't Play

A practical guide to CORS errors when testing HLS streams, with real debugging steps you can use right now in your browser.

The playlist loaded fine. So why is the video black?

You paste an M3U8 URL into a player, the playlist parses correctly, the quality levels show up, and then… nothing. The video element stays dark. No error in the playlist syntax. No obvious typo in the URL. The stream just refuses to play.

Nine times out of ten, this is a CORS problem.

CORS stands for Cross-Origin Resource Sharing. It is a browser security mechanism that controls whether a web page on one domain can fetch resources from another domain. When you load a playlist from cdn.example.com and the segments live on media.cdn.example.com, the browser checks whether those servers explicitly allow your page to access their files. If the answer is no, the browser blocks the request silently from JavaScript, and the player sees a network failure.

The frustrating part is that the playlist itself might load without issues while every single .ts or .m4s segment fails. That is because CORS headers can differ between the playlist endpoint and the media endpoint. A server might return Access-Control-Allow-Origin: * for the m3u8 file but forget to add it for the segment files.

What CORS headers actually look like

When a browser makes a cross-origin request, the server needs to respond with the right headers. The most common one is:

Access-Control-Allow-Origin: *

Or, for a specific origin:

Access-Control-Allow-Origin: https://tools.anoiona.net

For some requests, the browser sends a preflight OPTIONS request first. The server must respond to that preflight with the correct CORS headers too. If the preflight fails, the actual request never happens.

You can see this in action in DevTools. Open the Network tab, filter for the segment requests, and click on one. Look at the Response Headers section. If Access-Control-Allow-Origin is missing, that segment will fail in the player even though you can open the same URL directly in a browser tab.

The difference between “works in a tab” and “works in a player”

This trips up a lot of people. You can open the M3U8 URL in a browser tab and see it load. You can even click a segment URL and download the file. So why does the player fail?

When you open a URL directly in the browser address bar, there is no cross-origin context. The browser fetches the file as a top-level navigation. CORS does not apply.

When JavaScript in a web page fetches the same URL using fetch() or XMLHttpRequest, the browser treats it as a cross-origin request if the page and the resource are on different domains. That is when CORS headers matter.

HLS.js uses JavaScript to fetch playlists and segments. So does native HLS on some browsers through Media Source Extensions. That means every segment request goes through the CORS check. A stream that plays perfectly in Safari’s address bar might still fail in a player page if the CORS headers are wrong.

Practical debugging steps

Start with the Network tab. Filter for m3u8, ts, or m4s. Reload the page and trigger playback. Look for red entries or entries with a non-200 status code.

Click on a failed segment request. The status code tells you a lot:

  • 403 Forbidden: The server recognized your request but denied access. This often happens with token-based authentication, expired links, or referrer restrictions.
  • 404 Not Found: The segment URL is wrong or the file has been removed. Check whether the playlist uses relative paths that resolve correctly.
  • 200 but no CORS headers: The file loads, but JavaScript cannot read the response. The player sees an empty or failed response.

For the 200-without-CORS case, look at the response headers specifically for Access-Control-Allow-Origin. If it is missing, the server needs to be configured to include it. There is nothing you can do on the client side to fix this.

Some servers also use Access-Control-Allow-Methods and Access-Control-Allow-Headers to restrict what kind of requests are allowed. For HLS playback, the browser typically needs GET and sometimes HEAD to be allowed.

Referrer and Origin headers can block playback too

Some CDNs and streaming servers check the Referer or Origin header on incoming requests. If the request comes from a domain that is not on their allowlist, they return a 403 or redirect.

You can check this in DevTools too. Click on a segment request and look at the Request Headers section. The Referer header will show the page URL that triggered the request. If the server rejects requests from tools.anoiona.net but allows requests from example.com, that explains the failure.

The tricky part is that Referer policies vary. Some pages set Referrer-Policy: no-referrer to avoid leaking the URL. Some servers need the referrer to be present. These two requirements conflict, and the result depends on which side controls the policy.

What our M3U8 player does with CORS errors

Our player does not bypass CORS. That would be a security violation, and it would also require a server-side proxy that we intentionally do not run.

What we do is surface CORS errors in the diagnostics panel. When a segment request fails with a network error, we log it so you can see exactly which request failed and why. The player also shows the failure status in the main status area so you do not have to dig through the diagnostics to understand what went wrong.

If the playlist loads but segments fail, the player will report that the stream could not be played. It will not silently retry forever or pretend the stream is loading. That honesty is intentional: a CORS problem is a server configuration issue, and hiding it would only make debugging harder.

A quick CORS checklist for stream operators

If you are serving HLS streams and want them to work in browser-based players:

  1. Add Access-Control-Allow-Origin: * to both playlist and segment responses.
  2. If you use token-based URLs, make sure the tokens do not expire before the playlist finishes.
  3. Test the stream in a player page, not just in a browser tab. The CORS behavior is different.
  4. Check that your CDN passes through CORS headers. Some CDN configurations strip them.
  5. If you use Referer restrictions, allow the player domain or use a different authentication method.

CORS is not a bug in the player. It is a security feature that protects users from malicious cross-origin requests. The fix is always on the server side: tell the server which origins are allowed to access the resources.