Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】Why Your Browser-Based Video Export Freezes When You Switch Tabs (and How to Fix It)

2026年10月10日に「Dev.to」が公開したITニュース「Why Your Browser-Based Video Export Freezes When You Switch Tabs (and How to Fix It)」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

ブラウザで動画を書き出す際、タブを切り替えると`requestAnimationFrame`が停止し、動画がフリーズ(最初のフレームが続く)する問題が報告された。これはブラウザの省電力機能によるもの。対策として、`setInterval`を使い、手動でキャンバスのフレームをキャプチャする方式に変更することで、バックグラウンドでも正確な動画書き出しが可能になった。

ITニュース解説

ブラウザ上で動作するアプリケーション、特に動画編集やホワイトボード、ゲームクリップの録画など、ウェブページ上の描画領域であるキャンバスに描画した内容を動画としてエクスポートする機能を持つものには、開発者が気づきにくい落とし穴がある。これは、開発中のテスト環境では問題なく動作するように見えても、実際のユーザーが使うと予期せぬ挙動を起こす可能性がある、という種類の問題である。具体的には、ブラウザベースの動画エクスポート中にユーザーが別のタブに切り替えると、最終的な動画ファイルが正しく生成されない、というバグが挙げられる。開発者は通常、エクスポート中に進行状況を示すバーをじっと見ているため、このバグに気づきにくい。しかし、実際のユーザーはエクスポートにかかる数十秒間、他の作業をするためにタブを切り替えることがよくあるため、この問題は顕在化しやすい。

あるブラウザベースの動画編集ツールで、ユーザーが複数のスライドを含む動画をエクスポートしたところ、完成した動画ファイルには最初のスライドしか含まれておらず、それが動画の全時間にわたって繰り返されている、という報告があった。この問題は、VLC、Windows Media Player、Films & TVといった複数の動画プレイヤーで再現された。この点が重要な手がかりとなった。もし動画ファイルのエンコード方式やコンテナ形式の問題であれば、プレイヤーによって再生結果が異なる可能性があるが、どのプレイヤーでも同じ結果になったということは、ファイルの内容そのものがおかしい、ということを示していた。つまり、動画ファイルには実際に最初のフレームの内容しか記録されていなかったのである。

多くのサーバーサイドでのレンダリングパイプラインを持たないブラウザベースの動画エディタでは、動画のエクスポートは、プロジェクトをリアルタイムで再生しながら、その描画内容をキャンバスから直接録画するという方法で行われる。これは、ウェブページ上のキャンバスから動画ストリームを取得し(canvas.captureStream(fps))、そのストリームを「MediaRecorder」というAPIを使って録画する、という手順である。動画の再生は通常、requestAnimationFrame (rAF) というブラウザのAPIを使ったループで行われる。rAFは、ブラウザが次の画面描画を行う直前にコールバック関数を実行する仕組みで、アニメーションやゲームなどのスムーズな描画によく使われる。開発者がエクスポートのテストを行う際は、当然ながらそのタブを最前面に表示して見ているため、このrAFベースの再生ループは完璧に動作し、期待通りの動画が生成される。

しかし、ここに落とし穴があった。例えば20枚のスライドを含む40秒間の動画をエクスポートする場合、リアルタイム再生のためには約40秒の実時間が必要となる。ユーザーはその間ずっと進捗バーを眺めているわけではない。多くのユーザーは、エクスポートが完了するまでの間、他のタブに切り替えたり、チャットを確認したり、別の作業を行ったりする。ここで、ブラウザの重要な最適化機能が影響してくる。requestAnimationFrameは、タブがバックグラウンドに移動した瞬間、ブラウザによってスロットリング(処理速度が制限されること)されたり、あるいは完全に停止させられたりする。これは、バッテリー消費やCPU使用率を抑えるための意図的で文書化されたブラウザの最適化であり、ブラウザのバグではない。

この最適化によって、以下のような問題が発生していた。ユーザーがエクスポートボタンをクリックし、録画が開始され、rAFループによって動画の再生が始まる。ユーザーが数秒後に別のタブに切り替える(この時、最初のスライドが表示されている)。対象のタブがバックグラウンドに移動した結果、requestAnimationFrameのコールバックが停止し、キャンバスの描画も停止する。キャンバスには最初のスライドがフリーズした状態で残り続ける。canvas.captureStream(fps)は、キャンバスの内容が変化しているかどうかを知らないし、気にしない。設定されたフレームレートで、フリーズしたキャンバスのピクセルを繰り返しサンプリングし続ける。MediaRecorderは、canvas.captureStreamから送られてくる情報(つまり、最初のスライドのフリーズした画像)を忠実に記録し続ける。その結果、エクスポートされた動画ファイルの再生時間は正しかったものの、その内容は最初の1フレームが動画の全時間にわたって繰り返されたものになってしまった。これが、どのプレイヤーでも同じ問題が再現された理由である。

この問題を解決するためには、動画のエクスポート処理をrequestAnimationFrameに依存させない、別のループで駆動する必要がある。ユーザーが画面を見ている間にスムーズなプレビューを行うためにはrAFが適切なツールだが、エクスポートのようにタブの可視性に依存せず動作し続ける必要がある処理には、別の仕組みが必要となる。

ここで採用されたのが、setIntervalというAPIである。setIntervalもバックグラウンドタブでスロットリングされることはあるが、requestAnimationFrameのように完全に停止することはない。そして、各実行時に「壁時計時間」(実際の経過時間)を基準にしてフレームの時間を計算するようにすれば、たとえスロットリングされても、その時点での正しいフレームを描画し続けることができる。これにより、バックグラウンドでは描画されるフレームの数は少なくなるものの、動画が完全にフリーズして同じフレームを繰り返し記録してしまう、という事態は避けられる。

さらに、より確実なフレームキャプチャ方法が導入された。これは、canvas.captureStream(0)を使用してキャンバスから動画トラックを取得し、その動画トラックに対してvideoTrack.requestFrame()を明示的に呼び出すことで、必要なタイミングで正確なフレームを録画にプッシュするという方法である。captureStream(0)は「手動モード」を意味し、ブラウザによる自動サンプリングに頼らず、開発者が明示的にフレームをリクエストするまで何もキャプチャしない。

この二つの組み合わせにより、エクスポートループは完全に自己完結型となった。具体的には、setIntervalで一定間隔(例えば、目標とするフレームレートに合わせて)で処理関数を呼び出す。この関数内で、前回の実行からの経過時間に基づいて現在の動画再生時間を計算し、その時間に応じたフレームをキャンバスに描画する。そして、videoTrack.requestFrame()を呼び出すことで、描画されたばかりのフレームを録画に送り込む。このループを動画の総再生時間分だけ繰り返し、完了したらclearIntervalでループを停止し、MediaRecorderを停止させる。

コードを修正しただけでは、タイミングに起因するバグが本当に修正されたとは言えない。重要なのは、実際にバグが発生した状況を再現し、修正が機能していることを確認することである。この開発者は、4つの異なる色を示す5秒ごとのスライドで構成される20秒のテストプロジェクトを作成した。そして、エクスポートを開始した後、意図的に別の空白タブを開いてフォーカスを移し、エクスポート中ずっと対象タブをバックグラウンドに置いた。これは、当初バグを報告したテスターがとった行動を正確に再現するものである。エクスポート完了後、出力された動画ファイルから特定のタイムスタンプのフレームを抽出し、そのピクセル色を検証した。結果、タブがバックグラウンドに置かれた状態であっても、全ての4つのスライドが正しい順序で、期待通りのタイミングで含まれていることが確認できた。この「実際の動作が壊れる状況でも機能するか」という検証こそが、この種のバグ修正において最も重要なテストであった。

この経験から得られる教訓は非常に重要である。もし開発中のアプリケーションが、タブがバックグラウンドにある状態で時間ベースの処理(キャンバス録画、リアルタイムに同期するアニメーション、定期的なポーリングなど)を行う必要がある場合、requestAnimationFrameが常に安全なデフォルトであるとは限らない、ということだ。rAFは、ユーザーが画面を見ている時に最高のパフォーマンスを発揮するレンダリングツールだが、ユーザーが見ていない時でも動作し続ける必要がある処理には向かない。タブの可視性やフォーカス状態に左右されずに動作するループ(例えば、setIntervalを適切に活用し、壁時計時間で同期する)を選択する必要がある。

関連コンテンツ

関連IT用語

関連ITニュース