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

François Beaufort
François Beaufort

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

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

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

Chrome でのビデオ会議通話のスクリーンショット。
Elad は、François とのビデオ会議通話でサードパーティ製アプリケーションを使用しています。

getDisplayMedia() を呼び出してユーザーに現在のタブを選択させると、現在のタブ全体が送信されます。この場合、自分の動画が自分に返送される可能性があります。領域キャプチャを使用すると、この部分を切り取ることができます。

しかし、プレゼンターがビデオ会議アプリケーションを操作し、ドロップダウン リストなどのコンテンツがキャプチャ対象のコンテンツの上に描画された場合はどうなるでしょうか?

キャプチャ対象のコンテンツを覆い隠しているプルダウン リストのスクリーンショット。
キャプチャ対象のコンテンツの上にプルダウン リストが表示されます。

この場合は、領域キャプチャは役に立ちません。ドロップダウン リストの一部がリモート参加者の画面に表示されることがあります。

キャプチャされたプルダウン リストのスクリーンショット。
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 という名前の Element に注目してください。そこから RestrictionTarget を導出するには、RestrictionTarget.fromElement(captureTarget) を呼び出します。成功した場合、返された Promise は新しい RestrictionTarget オブジェクトで解決されます。そうでない場合は、不当な数の RestrictionTarget オブジェクトを生成していると、拒否されます。

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

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

制限

タブをキャプチャするとき、動画トラックは restrictTo() を公開します。現在のタブをキャプチャするとき、null または現在のタブ内の Element から派生した RestrictionTarget を使用して restrictTo() を呼び出すことは有効です。

restrictTo(restrictionTarget) への呼び出しは、DOM の他の部分とは独立して、それ自体で描画されたかのように、動画トラックを captureTarget のキャプチャに変換します。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 が、存在しなくなった Element から派生した場合。
  • トラックにクローンがある場合。(問題 1509418 を参照)。
  • 現在のトラックがセルフキャプチャ動画トラックでない場合。
  • restrictionTarget の派生元である Element が制限の対象でない場合。

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

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

restrictTo() メソッドは、自己キャプチャだけでなく、タブキャプチャのすべての動画トラックで公開されます。ただし、現時点では要素キャプチャはセルフキャプチャでのみ有効になっています。そのため、トラックを制限する前に、ユーザーが現在のタブを選択したかどうかを確認することをおすすめします。これは、キャプチャ ハンドルを使用して実現できます。preferCurrentTab を使用して、ブラウザにユーザーが自分でキャプチャするように促すこともできます。

透明性

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

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

キャプチャ ターゲットの対象外

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

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

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

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

ブラウザ サポート

要素キャプチャは、パソコンの Chrome 132 以降でのみご利用いただけます。

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

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

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

デモ

デモを実行して、要素キャプチャを試すことができます。

フィードバック

Chrome チームとウェブ標準コミュニティは、要素キャプチャの使用感について皆様のご意見をお待ちしています。

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

要素キャプチャについて、想定どおりに動作しない点はありますか?アイデアを実装するために必要なメソッドやプロパティが不足している場合はどうすればよいですか?セキュリティ モデルについて質問やコメントがある場合

  • GitHub リポジトリで仕様に関する問題を提出するか、既存の問題に意見を追加します。

実装に関する問題ですか?

Chrome の実装にバグが見つかりましたか?それとも、実装が仕様と異なるのでしょうか?

謝辞

写真: Paul SkorupskasUnsplash