When you embed ffmpeg.wasm into a web app, you may run into errors like SharedArrayBuffer is not defined or Atomics is not defined. These come from a change in browser security policy.


Typical Error Messages

SharedArrayBuffer is not defined
ReferenceError: SharedArrayBuffer is not defined
Cannot use SharedArrayBuffer in a cross-origin isolated context
Uncaught (in promise) ReferenceError: Atomics is not defined

Cause

SharedArrayBuffer was restricted as part of mitigations for Spectre/Meltdown; since 2018, it has only been available on cross-origin isolated pages.

Internally, ffmpeg.wasm uses SharedArrayBuffer for multithreading — which means the hosting page must be cross-origin isolated.

Requirements for Cross-Origin Isolation

The page’s HTTP response must include both of the following headers:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Nginx

location / {
    add_header Cross-Origin-Opener-Policy "same-origin";
    add_header Cross-Origin-Embedder-Policy "require-corp";
}

Apache (.htaccess)

Header always set Cross-Origin-Opener-Policy "same-origin"
Header always set Cross-Origin-Embedder-Policy "require-corp"

Netlify (_headers file)

/*
  Cross-Origin-Opener-Policy: same-origin
  Cross-Origin-Embedder-Policy: require-corp

Vercel (vercel.json)

{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        { "key": "Cross-Origin-Opener-Policy", "value": "same-origin" },
        { "key": "Cross-Origin-Embedder-Policy", "value": "require-corp" }
      ]
    }
  ]
}

Node.js / Express

app.use((req, res, next) => {
  res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
  res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
  next();
});

Fix 2: Use a Service Worker (coi-serviceworker)

When you can’t modify server headers (GitHub Pages, static sites built with Astro, etc.), use coi-serviceworker. It injects COOP/COEP headers across requests via a Service Worker.

Setup

1. Place coi-serviceworker.js in the public directory

Download the latest version from gzuidhof/coi-serviceworker and place it at the site root.

2. Add the registration code to your HTML

<script>
  if (!self.crossOriginIsolated) {
    navigator.serviceWorker.register('/coi-serviceworker.js').then(function(reg) {
      if (!navigator.serviceWorker.controller) {
        // Reload is required when the Service Worker registers for the first time
        window.location.reload();
      }
    }).catch(function(e) {
      console.warn('coi-serviceworker registration failed:', e);
    });
  }
</script>

Caveats:

  • Service Workers only work for same-origin requests
  • Only runs on HTTPS or localhost
  • A page reload occurs on first visit (not needed after that)

Fix 3: credentialless Mode (Chrome 96+)

Using credentialless instead of require-corp lets you load third-party resources (Google Fonts, CDNs, etc.) without credentials.

Cross-Origin-Embedder-Policy: credentialless

Note: Supported in Firefox 119+ and Safari 17+. Older browsers need a require-corp fallback.


Verifying in the Browser

Confirm whether the page is actually cross-origin isolated:

console.log(self.crossOriginIsolated); // true means you're good

You can also check from the Chrome DevTools Console.


Example: Astro Project Configuration

You’ll also need to configure Astro’s dev server:

// astro.config.mjs
export default defineConfig({
  vite: {
    server: {
      headers: {
        'Cross-Origin-Opener-Policy': 'same-origin',
        'Cross-Origin-Embedder-Policy': 'require-corp',
      },
    },
    preview: {
      headers: {
        'Cross-Origin-Opener-Policy': 'same-origin',
        'Cross-Origin-Embedder-Policy': 'require-corp',
      },
    },
  },
});

When deploying a static build to Netlify or Vercel, use the platform-specific configuration files shown above.


Environment Checklist for ffmpeg.wasm

RequirementDetails
HTTPS or localhostService Workers don’t run over plain HTTP in production
SharedArrayBuffer-capable browserChrome 68+, Firefox 79+, Safari 15+
COOP/COEP headersSet on the server, or injected via Service Worker
WebAssembly supportAll major current browsers support it

All of our browser-based tools are already configured for this:


Frequently Asked Questions

What does SharedArrayBuffer have to do with FFmpeg?

ffmpeg.wasm uses SharedArrayBuffer for multi-threaded decode/encode. Without cross-origin isolation (COOP+COEP headers) the browser blocks SAB and the WASM module fails to start.

Can I use ffmpeg.wasm without SharedArrayBuffer?

Yes — load the single-threaded build (@ffmpeg/core-mt → @ffmpeg/core). It runs without COOP/COEP but is 2–4× slower on multi-core machines.

How do I add COOP/COEP headers?

Send Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp from your hosting layer. On Cloudflare Pages, use _headers at the project root.

Why does it work in dev but break in production?

Local dev servers usually allow SAB without COOP/COEP. Production CDNs lock it down. Test with crossOriginIsolated === true in the browser console.

Will adding the headers break my AdSense or analytics?

Yes — third-party scripts must serve Cross-Origin-Resource-Policy: cross-origin headers, otherwise they get blocked. Add a service worker fallback for tools pages so the rest of the site can still load AdSense.



Primary sources: developer.mozilla.org — SharedArrayBuffer / gzuidhof/coi-serviceworker