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

Author: OnlinePlayer Team
technicalcorssecuritystreamingdeveloper
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/page1
  • https://example.com/page2

These are different origins:

  • https://example.com vs http://example.com (different protocol)
  • https://example.com vs https://api.example.com (different subdomain)
  • https://example.com vs https://example.com:8080 (different port)

Why It Exists

Without CORS, any website could:

  1. Read your cookies for other sites
  2. Make authenticated requests to your bank on your behalf
  3. 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:

  1. Fetch the m3u8 playlist (CORS-restricted)
  2. Parse it (JavaScript)
  3. 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

  1. Open Developer Tools → Network
  2. Find the failed request (it'll be red)
  3. Look at the Response Headers
  4. 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

  1. CORS is a browser security feature, not a bug
  2. Servers must opt-in to allow cross-origin access
  3. Direct <video> tags often work even without CORS headers
  4. HLS/DASH streams always need CORS because they use JavaScript fetch
  5. 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.