ffmpeg.wasm をWebアプリに組み込んだとき、SharedArrayBuffer is not definedAtomics is not defined といったエラーが出て動作しないことがあります。このエラーはセキュリティ仕様の変更によるものです。


典型的なエラーメッセージ

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

原因

SharedArrayBufferSpectre/Meltdown 脆弱性対策として、2018年以降のブラウザでは Cross-Origin Isolated なページでのみ使用できるよう制限されました。

ffmpeg.wasm は内部でマルチスレッド処理のために SharedArrayBuffer を使います。そのため、ページが Cross-Origin Isolated である必要があります。

Cross-Origin Isolated にするための条件

ページのHTTPレスポンスヘッダーに以下の両方が必要です:

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

解決方法1: サーバーでHTTPヘッダーを設定する(推奨)

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 ファイル)

/*
  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();
});

解決方法2: Service Worker(coi-serviceworker)を使う

サーバー設定を変更できない場合(GitHub Pages、Astro の静的サイトなど)は coi-serviceworker を使います。これはService Workerがリクエストを横断してCOOP/COEPヘッダーを注入する手法です。

セットアップ手順

1. coi-serviceworker.js を public ディレクトリに置く

gzuidhof/coi-serviceworker から最新版をダウンロードし、サイトのルートに配置します。

2. HTMLに登録コードを追加する

<script>
  if (!self.crossOriginIsolated) {
    navigator.serviceWorker.register('/coi-serviceworker.js').then(function(reg) {
      if (!navigator.serviceWorker.controller) {
        // Service Worker が新規登録された場合はリロードが必要
        window.location.reload();
      }
    }).catch(function(e) {
      console.warn('coi-serviceworker registration failed:', e);
    });
  }
</script>

注意点:

  • Service Workerは同一オリジンのリクエストにのみ機能します
  • HTTPSまたはlocalhost環境でのみ動作します
  • 初回アクセス時にページリロードが発生します(次回以降は不要)

解決方法3: credentialless モード(Chrome 96+)

COEP: require-corp の代わりに credentialless を使うと、サードパーティリソース(Google Fonts、CDNなど)をクレデンシャルなしで読み込めます。

Cross-Origin-Embedder-Policy: credentialless

注意: Firefox 119+、Safari 17+ で対応。旧ブラウザでは require-corp にフォールバックが必要です。


解決方法4: シングルスレッド版に切り替えて COOP/COEP をやめる(本サイトの実運用)

SharedArrayBuffer が要るのは マルチスレッド版 @ffmpeg/core-mt だけです。シングルスレッド版 @ffmpeg/core は SAB もクロスオリジン分離も使いません。変換ツールを「広告・埋め込みフォーム・コメントがあるサイト」に同居させるなら、こちらが現実解になります。

本サイトのブラウザツール(43 本)は 2026-09 まで COEP: credentialless + coi-serviceworker で運用していましたが、実測で次が分かったので ヘッダを全撤去 しました。

観測したこと(Chromium 147、Playwright で本番 URL を計測) 原因
お問い合わせフォーム(Tally の iframe 埋め込み)が本番だけ真っ白 credentialless 下では 入れ子の cross-origin iframe 自身も COEP を返す必要がある。CORP ヘッダでは救えない
AdSense / Adsterra / A-ADS の広告 iframe が同じ理由で描画されない 広告ネットワークの配信 HTML は COEP を返さない(adsbygoogle.jscredentialless の文字列すら無い)
crossOriginIsolated === falsetypeof SharedArrayBuffer === "undefined" の状態でも音声削除(-c copy)・速度変更(libx264 再エンコード)が正常出力 使っているのが @ffmpeg/[email protected](シングルスレッド)だから

判断の目安

  • ツール専用ドメイン・埋め込み無し・速度最優先 → core-mt + COOP/COEP(解決方法 1)
  • 記事・広告・フォーム・コメントと同居 → core(シングルスレッド)にして ヘッダを送らない。数十秒級の変換なら体感差は小さく、ページの他要素が壊れない方が価値が高い
  • 両立したい → ツールページだけ別オリジン(サブドメイン)に分離し、そこだけ COOP/COEP

Service Worker を残す場合も、ヘッダ注入はやめて wasm/core のキャッシュ専用 に縮退させると、既存ユーザーの登録済み SW を(skipWaiting + clients.claim で)安全に置き換えられます。


ブラウザの動作確認

ページが正しく Cross-Origin Isolated になっているかは以下で確認できます:

console.log(self.crossOriginIsolated); // true であれば OK

または Chrome DevTools の Console で確認してください。


Astroプロジェクトでの設定例

Astroの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',
      },
    },
  },
});

静的ビルドをNetlifyやVercelにデプロイする場合は上記の各設定ファイルを使います。


ffmpeg.wasm が動作する環境の条件まとめ

条件 詳細
HTTPS または localhost HTTP本番環境では Service Worker が動作しない
SharedArrayBuffer 対応ブラウザ Chrome 68+, Firefox 79+, Safari 15+
COOP/COEP ヘッダー マルチスレッド版のみ必要。サーバーで設定 または Service Worker で注入。シングルスレッド版なら不要
WebAssembly 対応 現在の主要ブラウザはすべて対応

関連ツール

当サイトのブラウザツールはすべてこの問題に対応しています:


関連記事


一次ソース: developer.mozilla.org — SharedArrayBuffer / gzuidhof/coi-serviceworker


よくある質問

SharedArrayBuffer と FFmpeg の関係は?

ffmpeg.wasm はマルチスレッドのデコード / エンコードに SharedArrayBuffer を使う。クロスオリジン分離(COOP+COEP ヘッダ)が無いとブラウザが SAB をブロックし、WASM モジュールが起動失敗する。

SAB なしで ffmpeg.wasm を使える?

使えます — シングルスレッドビルド(@ffmpeg/core-mt → @ffmpeg/core)に切替。COOP/COEP 不要だが、マルチコア環境では 2〜4 倍遅い。

COOP/COEP ヘッダはどう設定する?

Cross-Origin-Opener-Policy: same-originCross-Origin-Embedder-Policy: require-corp をホスティング層から送信。Cloudflare Pages なら _headers をプロジェクトルートに置く。

dev では動くが本番で壊れる

ローカル dev サーバーは COOP/COEP 無しでも SAB を許可することが多い。本番 CDN は厳格。ブラウザコンソールで crossOriginIsolated === true を確認してください。

これらのヘッダを追加すると AdSense / アナリティクスが壊れる?

はい。スクリプトは CORP で通せても、広告の iframecredentialless 下でも描画されません(入れ子 iframe 自身に COEP が要るため)。ツールページだけ別オリジンに分けるか、シングルスレッド版に切り替えてヘッダを送らない(解決方法 4)のどちらかになります。