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

François Beaufort
François Beaufort

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

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

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

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

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

ただし、プレゼンターがビデオ会議アプリを操作しているときに、プルダウン リストなどのコンテンツが、キャプチャ対象のコンテンツの上に描画された場合はどうなりますか?

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

地域の撮影では、プルダウン リストの一部がリモート参加者の画面に表示される場合があります。

キャプチャされたプルダウン リストのスクリーンショット。
Elad のプルダウン リストが、François が受け取ったコンテンツの上に表示されます。

このように要素の一部をキャプチャする(コンテンツを遮る)ことで、次のような問題が発生します。

  • コンテンツを隠すと、ユーザーが共有しようとしたコンテンツが見えなくなる可能性があります。
  • 遮蔽されたコンテンツが非公開である可能性がある(チャット通知など)。
  • コンテンツを隠すと混乱を招く可能性があります。(たとえば、アプリの再レイアウトにより、キャプチャされたターゲットの上にリモート参加者自身の動画が短時間表示される場合があります)。

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

プルダウン リストが表示されていないターゲット要素のスクリーンショット。
フランソワには、エラドからのプルダウン リストが表示されません。

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

captureTarget は、ユーザーがキャプチャするコンテンツを含むページ上の要素です。ビデオ会議ウェブアプリで 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);

要素とは異なり、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 が、存在しなくなった要素から派生している場合。
  • トラックにクローンがある場合。(問題 1509418 を参照)。
  • 現在のトラックがセルフキャプチャ動画トラックではない場合。
  • restrictionTarget が派生した要素が制限の対象外である場合。

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

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

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

透明性

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

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

対象外の回収ターゲット

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

要素を制限の対象にするには、その要素が独自のスタッキング コンテキストを形成している必要があります。これを実現するには、isolation CSS プロパティを指定して isolate に設定します。

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

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

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

Element Capture API は、デスクトップ版 Chrome で Element Capture フラグを使用して利用できます。このフラグは chrome://flags/#element-capture で有効にできます。

この機能は、パソコン版 Chrome 121 からオリジン トライアルに移行します。これにより、デベロッパーはサイト訪問者向けにこの機能を有効にして、実際のユーザーからデータを収集できるようになります。オリジン トライアルの詳細については、オリジン トライアルの開始をご覧ください。

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

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

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

デモ

Element Capture を試すには、Glitch でデモを実行します。ソースコードを確認してください。

フィードバック

Chrome チームとウェブ標準コミュニティは、要素キャプチャに関するご意見をお待ちしております。

デザインについて

領域キャプチャが想定どおりに機能しない点はありますか?または、アイデアを実装するために必要なメソッドやプロパティが不足している場合は、セキュリティ モデルについてご質問やご意見がございましたら、

  • GitHub リポジトリで仕様に関する問題を報告するか、既存の問題にコメントを追加してください。

実装に関する問題

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

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

謝辞

写真提供: Paul SkorupskasUnsplash