任意の要素から動画ストリームをキャプチャする

François Beaufort
François Beaufort

Screen Capture API を使用すると、現在のタブ全体をキャプチャできます。Element Capture API を使用すると、特定の HTML 要素をキャプチャして記録できます。タブ全体のキャプチャを特定の DOM サブツリーのキャプチャに変換し、ターゲット要素の直接の子孫のみをキャプチャします。つまり、遮蔽されたコンテンツと遮られたコンテンツの両方を切り抜いて取り除きます。

要素キャプチャを使用する理由

ビデオ会議アプリケーションの要件を検討することで、要素キャプチャが役立つ場面を理解できます。ビデオ会議アプリケーションでサードパーティ製アプリケーションを iframe に埋め込める場合は、その iframe を動画としてキャプチャし、リモートの参加者に送信したい場合があります。

Chrome でのビデオ会議通話のスクリーンショット。
エラドさんは、フランソワさんとのビデオ会議通話でサードパーティ製アプリを使用しています。

getDisplayMedia() を呼び出してユーザーが現在のタブを選択できるようにすると、現在のタブ全体が送信されます。これにより、自分の動画が視聴者に伝わりやすくなります。Region Capture を使用してこれを切り抜くことができます。

しかし、プレゼンターがビデオ会議アプリケーションを使用しており、プルダウン リストなどのコンテンツがキャプチャ対象のコンテンツの上に描画されるとどうなるでしょうか。

キャプチャを意図したコンテンツを覆うプルダウン リストのスクリーンショット。
キャプチャするコンテンツの上にプルダウン リストが表示されます。

Region Capture は役に立たない。プルダウン リストの一部が、リモート参加者の画面に表示される場合があります。

プルダウン リストのスクリーンショット。
Elad のプルダウン リストは、François が受信したコンテンツの上に表示されます。

リージョン キャプチャはこのように要素の一部をキャプチャする(コンテンツを非表示にする機能)ため、次のような複数の問題が発生します。

  • コンテンツが遮られると、ユーザーが共有するはずのコンテンツが表示されなくなる可能性があります。
  • 非表示のコンテンツは非公開になる場合があります(チャットの通知など)。
  • コンテンツが混ざっていると混乱を招く可能性があります。(たとえば、アプリのレイアウト変更により、リモート参加者自身の動画をキャプチャ ターゲットに短時間移動させることができます)。

Element Capture API を使用すると、共有する要素をターゲットにできるので、こうした問題をすべて解決できます。

プルダウン リストが表示されないターゲット要素のスクリーンショット。
François に Elad のプルダウン リストが表示されない。

要素キャプチャの使用方法

captureTarget は、ユーザーがキャプチャするコンテンツが含まれているページ上の Element です。ビデオ会議ウェブアプリで captureTarget をキャプチャしてリモート参加者と共有します。captureTarget から RestrictionTarget を取得します。この RestrictionTarget を使用して動画トラックを制限すると、その動画トラックのフレームは、captureTarget とその直接の DOM の子孫の一部であるピクセルのみで構成されるようになります。

captureTarget がサイズ、形状、位置を変更すると、ウェブアプリからの追加入力を必要とせずに、動画トラックが追従します。コンテンツの表示、非表示、移動にも同様に特別な処理は不要です。

次の手順をもう一度確認してください。

まず、ユーザーが現在のタブをキャプチャできるようにします。

// Ask the user for permission to start capturing the current tab.
const stream = await navigator.mediaDevices.getDisplayMedia({
 preferCurrentTab: true,
});
const [track] = stream.getVideoTracks();

選択した要素を入力として RestrictionTarget.fromElement() を呼び出して、RestrictionTarget を定義します。

// Associate captureTarget with a new RestrictionTarget
const captureTarget = document.querySelector("#captureTarget");
const restrictionTarget = await RestrictionTarget.fromElement(captureTarget);

次に、RestrictionTarget を入力として、動画トラックで restrictTo() を呼び出します。最後の Promise が解決されると、後続のすべてのフレームが制限されます。

// Start restricting the self-capture video track using the RestrictionTarget.
await track.restrictTo(restrictionTarget);

// Enjoy! Transmit remotely.

詳細情報

機能検出

RestrictionTarget.fromElement() がサポートされているかどうかを確認するには、次のコマンドを使用します。

if ("RestrictionTarget" in self && "fromElement" in RestrictionTarget) {
  // Deriving a restriction target is supported.
}

RestrictionTarget を導き出す

captureTarget という名前の要素にフォーカスします。そこから RestrictionTarget を導出するには、RestrictionTarget.fromElement(captureTarget) を呼び出します。成功した場合は、返される Promise は新しい RestrictionTarget オブジェクトで解決されます。そうしないと、不当な数の RestrictionTarget オブジェクトを作成した場合は拒否されます。

const captureTarget = document.querySelector("#captureTarget");
const restrictionTarget = await RestrictionTarget.fromElement(captureTarget);

Element とは異なり、RestrictionTarget オブジェクトはシリアル化可能です。たとえば、Window.postMessage() を使用して別のドキュメントに渡すことができます。

制限

タブをキャプチャするときに、動画トラックが restrictTo() を露出します。現在のタブをキャプチャする際は、null または現在のタブ内の要素から派生した RestrictionTarget を指定して restrictTo() を呼び出すことができます。

restrictTo(restrictionTarget) を呼び出すと、動画トラックが captureTarget のキャプチャに変更されます。これは、DOM の他の部分とは独立して単独で描画されたかのように実行されます。captureTarget の子孫もキャプチャされ、captureTarget の兄弟要素はキャプチャから除外されます。その結果、トラックで配信されたすべてのフレームが captureTarget の輪郭に合わせて切り抜かれたかのように表示され、遮蔽されたコンテンツや遮断されたコンテンツはすべて削除されます。

// Start restricting the self-capture video track using the RestrictionTarget.
await track.restrictTo(restrictionTarget);

restrictTo(null) を呼び出すと、トラックが元の状態に戻ります。

// Stop restricting.
await track.restrictTo(null);

restrictTo() の呼び出しが成功すると、後続のすべての動画フレームが captureTarget に制限されることが保証されると、返される Promise は解決されます。

失敗した場合、Promise は拒否されます。restrictTo() への呼び出しが失敗するのは、次のいずれかの理由です。

  • restrictionTarget がキャプチャされていないタブに作成された場合。([代わりにこのタブを共有] ボタンを使用すると、キャプチャされるタブをユーザーがいつでも変更できます)。
  • 存在しなくなった要素から restrictionTarget が導出された場合。
  • トラックにクローンがあるかどうか。(問題 1509418 をご覧ください)。
  • 現在のトラックがセルフキャプチャ動画トラックでない場合。
  • restrictionTarget の派生元の要素が制限の対象でない場合。

セルフ キャプチャに関する考慮事項

アプリが getDisplayMedia() を呼び出し、ユーザーがアプリのタブのキャプチャを選択した場合、これを「セルフ キャプチャ」と呼びます。

restrictTo() メソッドは、セルフ キャプチャだけでなく、すべてのタブキャプチャ動画トラックで公開されます。ただし、現時点では、要素のキャプチャはセルフ キャプチャでのみ利用可能です。そのため、トラックを制限する前に、ユーザーが現在のタブを選択したかどうかを確認することをおすすめします。そのためには、[Capture Handle] を使用します。preferCurrentTab を使用して、ユーザーをセルフキャプチャに誘導することもできます。

透明性

アプリが getDisplayMedia() を通じて取得する動画フレームにアルファ チャンネルが含まれていません。アプリで半透明のキャプチャ ターゲットを設定している場合、アルファ チャンネルを削除すると、次のような結果が生じる可能性があります。

  • 色が変更される場合があります。明るい背景の上に描画されたターゲット要素は部分的に透明になり、アルファ チャンネルが削除されると暗くなり、暗い背景の上に描画されたターゲット要素はより明るく表示されます。
  • アルファ チャンネルが最大に設定されたとき、ユーザーには見えなかった、または気づかなかった色は、アルファ チャンネルが削除されると表示されます。たとえば、透明なセクションの RGBA コードが rgba(0, 0, 0, 0) の場合、キャプチャしたフレームに予期しない黒い領域が発生する可能性があります。
非長方形の透明なキャプチャ ターゲットの結果のスクリーンショット。
非長方形の透明なキャプチャ ターゲット動画ストリーム(右)は、不透明な青い円を含む黒い背景の長方形です。

不適格なキャプチャ ターゲット

トラックを有効なキャプチャ ターゲットに制限することはいつでも可能です。ただし、要素や祖先が display:none の場合など、特定の条件ではフレームは生成されません。一般的には、制限は単一のまとまりのある 2 次元の長方形の領域からなり、そのピクセルは親要素または兄弟要素から独立して論理的に決定できる要素にのみ適用されます。

要素が制限の対象であることを確認するための重要な考慮事項の 1 つは、独自のスタッキング コンテキストを形成する必要があることです。そのためには、CSS プロパティ isolation を指定し、isolate に設定します。

<div id="captureTarget" style="isolation: isolate;"></iframe>

なお、アプリが CSS プロパティを変更した場合など、ターゲット要素は任意の時点で制限の対象と対象外を切り替えることができます。適切なキャプチャ ターゲットを使用し、プロパティが予期せず変更されないようにするかどうかは、アプリによって異なります。ターゲット要素が不適格となった場合、そのターゲット要素が再び制限の対象になるまでは、単にトラック上で新しいフレームが出力されません。

要素のキャプチャを有効にする

Element Capture API は、パソコン版 Chrome の Element Capture フラグの下で使用でき、chrome://flags/#element-capture で有効にできます。

この機能は、パソコン版 Chrome 121 からオリジン トライアルを開始しており、デベロッパーはサイトにアクセスしたユーザーが実際のユーザーからデータを収集できる機能を有効にできます。オリジン トライアルの詳細については、オリジン トライアルのスタートガイドをご覧ください。

セキュリティとプライバシー

セキュリティのトレードオフについては、要素キャプチャの仕様のプライバシーとセキュリティに関する考慮事項のセクションをご覧ください。

Chrome ブラウザで、キャプチャしたタブの端の周囲に青い枠線が描画されます。

デモ

Glitch でデモを実行すると、要素キャプチャを使ってみることができます。必ずソースコードを確認してください。

フィードバック

Chrome チームとウェブ標準コミュニティでは、Element Capture の使用体験について意見を募集しています。

デザインについて教えてください

「Region Capture」について、想定どおりに機能していないものはありますか?または、アイデアを実装するために必要なメソッドやプロパティが不足しているか。セキュリティ モデルについて質問や意見がある場合は、

  • GitHub リポジトリで仕様に関する問題を報告するか、既存の問題にご意見をお寄せください。

実装に問題がある場合

Chrome の実装にバグは見つかりましたか?または、実装が仕様と異なっていますか?

  • https://new.crbug.com でバグを報告します。できるだけ詳しい情報と、再現するための簡単な手順を記載してください。Glitch は、すばやく簡単に再現を共有するのに最適です。

謝辞

写真撮影: Paul SkorupskasUnsplash より)