ffmpeg.wasm(ブラウザで動く FFmpeg)を Web アプリに組み込むと、SharedArrayBuffer is not defined などのエラーが出て動かないことがあります。ブラウザのセキュリティの仕様が変わったためです。
典型的なエラーメッセージ
SharedArrayBuffer is not defined
ReferenceError: SharedArrayBuffer is not defined
DataCloneError: Failed to execute 'postMessage' on 'Worker': SharedArrayBuffer transfer requires self.crossOriginIsolated.
原因
SharedArrayBuffer は、複数のスレッドでメモリを共有するための仕組みです。CPU の脆弱性 Spectre/Meltdown への対策として、2018年の初めにいったん使えなくなりました。現在のブラウザでは、Cross-Origin Isolated(クロスオリジン分離)なページでしか使えません。
ffmpeg.wasm は、マルチスレッドで処理するために、内部で SharedArrayBuffer を使います。そのため、ページがクロスオリジン分離されている必要があります。
Cross-Origin Isolated にするための条件
ページの HTTP レスポンスヘッダーに、次の両方が必要です(それぞれ COOP、COEP と略します)。
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 src="/coi-serviceworker.js"></script>
このファイルが、自分自身を Service Worker として登録します。
注意点
coi-serviceworker.jsは、CDN からではなく、サイトと同じオリジンから配信する必要があります- HTTPS か localhost でしか動きません
- 初めて開いたときは、ページが自動で再読み込みされます(次からは不要です)
解決方法3: credentialless モード(Chrome 96+)
COEP: require-corp の代わりに credentialless を使うと、Google Fonts や CDN など外部サイトのファイルを、Cookie などの認証情報を付けずに読み込めます。
Cross-Origin-Embedder-Policy: credentialless
注意: Firefox はデスクトップ版の 119 以降で対応していますが、Safari は対応していません。対応していないブラウザでは
require-corpを使う必要があります。
解決方法4: シングルスレッド版に切り替えて COOP/COEP をやめる
SharedArrayBuffer(SAB)が必要なのは、マルチスレッド版の @ffmpeg/core-mt だけです。シングルスレッド版の @ffmpeg/core は、SAB もクロスオリジン分離も使いません。広告や埋め込みフォーム、コメント欄があるサイトに変換ツールを置くなら、シングルスレッド版が現実的です。
広告と埋め込みフォームがあるページで、COEP: credentialless と coi-serviceworker を使ったところ、次の問題が起きました。COOP と COEP の両方のヘッダーを外すと、埋め込みが表示され、ツールもそのまま動きました。
| 起きたこと(Chromium 147 で確認) | 原因 |
|---|---|
| お問い合わせフォーム(Tally の iframe 埋め込み)が本番だけ真っ白 | credentialless のもとでは、埋め込んだ別オリジンの iframe 自身も COEP を返す必要がある。CORP ヘッダーでは解決しない |
| AdSense / Adsterra / A-ADS の広告 iframe が同じ理由で表示されない | 広告ネットワークが配信する HTML は COEP を返さない(adsbygoogle.js には credentialless という文字列もない) |
crossOriginIsolated === false、typeof SharedArrayBuffer === "undefined" の状態でも、音声削除(-c copy)と速度変更(libx264 で再エンコード)は正常に出力できた |
使っているのが @ffmpeg/[email protected](シングルスレッド版)だから |
判断の目安
- ツール専用のドメインで、埋め込みがなく、速度を最優先したい →
core-mt+ COOP/COEP(解決方法 1) - 記事・広告・フォーム・コメントと同じサイトに置く →
core(シングルスレッド版)にして、ヘッダーを送らない。マルチスレッド版より遅くなる(ffmpeg.wasm の公式の計測では約 2 倍)が、ページのほかの部分は壊れない - 両方を取りたい → ツールのページだけを別オリジン(サブドメイン)に分け、そこだけ COOP/COEP を送る
Service Worker(SW)を残す場合は、ヘッダーの付け足しをやめて、wasm/core ファイルのキャッシュだけをする SW に変えます。こうすると、既存ユーザーのブラウザに登録済みの SW を、skipWaiting と clients.claim で安全に置き換えられます。
ブラウザの動作確認
ページがクロスオリジン分離されているかは、Chrome DevTools の Console で次を実行すると確認できます。
console.log(self.crossOriginIsolated); // true であれば OK
Astroプロジェクトでの設定例
Astro の開発用サーバーでも、ヘッダーの設定が必要です。Astro の server.headers に書くと、astro dev と astro preview の両方でヘッダーが付きます。
// astro.config.mjs
export default defineConfig({
server: {
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.2+ |
| COOP/COEP ヘッダー | マルチスレッド版だけ必要。サーバーで設定するか、Service Worker で付け足す。シングルスレッド版なら不要 |
| WebAssembly 対応 | 現在の主要ブラウザはすべて対応 |
関連ツール
関連記事
よくある質問
SharedArrayBuffer と FFmpeg の関係は?
ffmpeg.wasm は、マルチスレッドでデコードやエンコードをするときに SharedArrayBuffer を使います。クロスオリジン分離(COOP と COEP のヘッダー)がないと、ブラウザが SAB を使えなくするので、WASM モジュールが起動できません。
SAB なしで ffmpeg.wasm を使える?
使えます。シングルスレッド版に切り替えます(@ffmpeg/core-mt → @ffmpeg/core)。COOP/COEP は不要ですが、マルチコアの環境ではマルチスレッド版より遅くなります。
COOP/COEP ヘッダはどう設定する?
Cross-Origin-Opener-Policy: same-origin と Cross-Origin-Embedder-Policy: require-corp を、サーバーやホスティングサービスの設定で送ります。Cloudflare Pages なら、_headers ファイルを静的ファイルのフォルダ(フレームワークなら public/ など、ビルドでそのまま出力にコピーされるフォルダ)に置きます。
dev では動くが本番で壊れる
localhost でも、COOP/COEP がなければ SAB は使えません。dev で動くのは、開発サーバーがヘッダーを送っている場合です。本番のサーバーや CDN でも同じヘッダーを送っているか、ブラウザのコンソールで crossOriginIsolated === true になっているかを確認してください。
これらのヘッダを追加すると AdSense / アナリティクスが壊れる?
広告は表示されなくなります。アナリティクスのように iframe を使わないスクリプトは、credentialless なら配信元に CORP ヘッダーがなくても読み込めますが、広告の iframe は credentialless でも表示されません(埋め込まれた iframe 自身に COEP が必要なため)。ツールのページだけを別オリジンに分けるか、シングルスレッド版に切り替えてヘッダーを送らないか(解決方法 4)のどちらかにします。