Understanding CORS: Why Some Video URLs Won't Play (And How to Bypass)

You found a direct URL to a video file. Maybe it's an m3u8 stream, maybe it's a raw MP4 sitting on someone's server. You paste it into a web player, hit play, and... nothing. You open the browser console and see something like:
Access to XMLHttpRequest at 'https://video-server.com/stream.m3u8'
from origin 'https://my-player.com' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Welcome to CORS hell.
If you're a developer, you've probably encountered this. If you're just trying to watch a video, it's infuriating. This guide will explain what CORS is, why it exists, and—most importantly—how to work around it.
What Is CORS, Really?
CORS stands for Cross-Origin Resource Sharing. It's a browser security feature that restricts web pages from making requests to a different domain than the one that served the page.
The "Origin" Concept
An "origin" is defined by: protocol + domain + port
These are the same origin:
https://example.com/page1https://example.com/page2
These are different origins:
https://example.comvshttp://example.com(different protocol)https://example.comvshttps://api.example.com(different subdomain)https://example.comvshttps://example.com:8080(different port)
Why It Exists
Without CORS, any website could:
- Read your cookies for other sites
- Make authenticated requests to your bank on your behalf
- Steal sensitive data from APIs you're logged into
CORS is the browser's way of asking: "Hey, server at video-server.com, is it okay if the page at my-player.com accesses your content?"
If the server doesn't explicitly say "yes" (via the Access-Control-Allow-Origin header), the browser blocks the request.
Why Videos Are Affected
Here's where it gets interesting. There are actually two types of requests when it comes to CORS:
1. Simple Requests (Usually Allowed)
A simple <video src="..."> or <img src="..."> tag can load content from any origin. This is why you can embed images from other websites.
<video src="https://other-site.com/video.mp4"></video>
This works. The browser loads the video, regardless of CORS headers.
2. Fetch/XHR Requests (CORS-Restricted)
But if your video player needs to:
- Read video metadata via JavaScript
- Parse an m3u8 playlist (HLS streaming)
- Add custom headers (like authentication tokens)
- Access video data for processing
Then it must use fetch() or XMLHttpRequest, and that's where CORS kicks in.
// This will fail without proper CORS headers
fetch("https://other-site.com/stream.m3u8")
.then((res) => res.text())
.then((playlist) => parseHLS(playlist));
The M3U8 Problem
HLS (HTTP Live Streaming) uses .m3u8 playlist files that contain URLs to video segments. To play an HLS stream, the player must:
- Fetch the m3u8 playlist (CORS-restricted)
- Parse it (JavaScript)
- Fetch each segment URL (CORS-restricted)
If the server doesn't allow cross-origin requests, the entire stream fails—even though direct MP4 playback works fine.
How to Know If CORS Is Your Problem
Check the Console
Open Developer Tools (F12) → Console. Look for errors containing:
- "CORS policy"
- "Access-Control-Allow-Origin"
- "blocked by CORS"
Check the Network Tab
- Open Developer Tools → Network
- Find the failed request (it'll be red)
- Look at the Response Headers
- If there's no
Access-Control-Allow-Origin, that's your problem
The Quick Test
Try opening the video URL directly in a new browser tab. If it plays there but not in your web player, it's almost certainly CORS.
Solutions: The Server-Side Fix
If you control the video server, the fix is simple: add the right headers.
For Apache (.htaccess):
<IfModule mod_headers.c>
Header set Access-Control-Allow-Origin "*"
Header set Access-Control-Allow-Methods "GET, OPTIONS"
Header set Access-Control-Allow-Headers "Range"
</IfModule>
For Nginx:
location /videos/ {
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Range' always;
if ($request_method = 'OPTIONS') {
return 204;
}
}
For Cloudflare Workers:
addEventListener("fetch", (event) => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
const response = await fetch(request);
const newHeaders = new Headers(response.headers);
newHeaders.set("Access-Control-Allow-Origin", "*");
return new Response(response.body, {
status: response.status,
headers: newHeaders,
});
}
For S3 / Cloud Storage:
Most cloud storage providers have CORS configuration:
AWS S3:
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "HEAD"],
"AllowedOrigins": ["*"],
"ExposeHeaders": ["Content-Length", "Content-Range"]
}
]
Google Cloud Storage:
[
{
"origin": ["*"],
"method": ["GET", "HEAD"],
"responseHeader": ["Content-Type", "Content-Length"],
"maxAgeSeconds": 3600
}
]
Solutions: When You Don't Control the Server
This is the tricky part. If you don't control the video server, you have limited options.
Option 1: CORS Proxy
A CORS proxy is a server that fetches the resource on your behalf and adds the proper headers.
Public proxies (risky, often rate-limited):
https://cors-anywhere.herokuapp.com/(deprecated)https://api.allorigins.win/raw?url=
Self-hosted proxy (Cloudflare Worker example):
export default {
async fetch(request) {
const url = new URL(request.url);
const targetUrl = url.searchParams.get("url");
if (!targetUrl) {
return new Response("Missing url parameter", { status: 400 });
}
const response = await fetch(targetUrl, {
headers: request.headers,
});
const newHeaders = new Headers(response.headers);
newHeaders.set("Access-Control-Allow-Origin", "*");
return new Response(response.body, {
status: response.status,
headers: newHeaders,
});
},
};
Option 2: Browser Extensions
Extensions like "CORS Unblock" or "Allow CORS" modify browser behavior to ignore CORS restrictions. This works but:
- Only works for you (not your users)
- Reduces security
- Some sites detect and block this
Option 3: Native Video Element (for MP4)
If it's a direct MP4/WebM URL (not HLS), the native <video> element might work:
<video src="https://other-site.com/video.mp4" controls></video>
This bypasses CORS for basic playback (but you won't have JavaScript access to the video data).
Option 4: Desktop App
Native applications (Electron, Tauri) don't have CORS restrictions because they're not running in a browser security context.
The OnlinePlayer Approach
OnlinePlayer handles video playback using the browser's native video element when possible, which helps avoid many CORS issues.
For direct MP4/WebM URLs:
- The video is loaded directly into the
<video>element - CORS is not an issue for basic playback
For HLS/DASH streams:
- These do require CORS headers on the server
- If missing, OnlinePlayer will show a clear error
For cloud storage (Google Drive, Dropbox, OneDrive):
- OAuth authentication is used
- CORS is handled by the cloud provider's API
This is why OnlinePlayer can play many URLs that fail in other web players—it falls back to the simplest, most compatible method when possible.
Quick Reference
| Scenario | Solution |
|---|---|
| You own the server | Add Access-Control-Allow-Origin: * header |
| AWS S3 / Cloud Storage | Configure CORS in bucket settings |
| CDN (Cloudflare, etc.) | Use Workers or Transform Rules |
| No server access + MP4 | Use native <video> element |
| No server access + HLS | Need a CORS proxy |
| Local development | Use browser extension (temporarily) |
TL;DR
- CORS is a browser security feature, not a bug
- Servers must opt-in to allow cross-origin access
- Direct
<video>tags often work even without CORS headers - HLS/DASH streams always need CORS because they use JavaScript fetch
- If you don't control the server, use a proxy or a player that uses native elements
Understanding CORS will save you hours of debugging. And when you do hit a wall, at least you'll know exactly what's blocking you.