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

フランソワ ボーフォール
François Beaufort
Elad Alon 氏
Ead Alon

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

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

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

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

getDisplayMedia() を呼び出し、ユーザーが現在のタブを選択できるようにすると、現在のタブ全体が送信されます。これによって、視聴者自身の動画が送信される可能性が高くなります。リージョン キャプチャを使用して、これを切り取ることができます。

しかし、プレゼンターがビデオ会議アプリケーションを使用しているときに、プルダウン リストなどの一部のコンテンツがキャプチャ対象のコンテンツの上に描画されることもあります。

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

この場合は、リージョン キャプチャが役立ちません。プルダウン リストの一部が、最終的にはリモートの参加者の画面に表示される可能性があります。

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

リージョン キャプチャでは、このように要素の一部をキャプチャするため(コンテンツのオクルージョンと呼ばれます)、次のような複数の問題が生じます。

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

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

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

要素キャプチャの使用方法を教えてください。

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 か、現在のタブ内の要素から派生した 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 であるなど、特定の条件ではフレームが生成されません。一般的な根拠として、制限は、1 つのまとまりのある 2 次元の長方形の領域で構成される要素にのみ適用され、そのピクセルは親要素や兄弟要素から切り離して論理的に決定できます。

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

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

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

要素のキャプチャの有効化

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

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

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

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

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

デモ

Glitch でデモを実行することで、Element Capture をお試しいただけます。必ずソースコードを確認してください。

フィードバック

Chrome チームとウェブ標準コミュニティでは、Element Capture を使用した経験についてご意見をお待ちしています。

デザインについてお聞かせください

リージョン キャプチャについて、ご期待に沿えない点はございませんか?あるいは、アイデアを実装するために必要なメソッドやプロパティが不足しているか?セキュリティ モデルについてご質問やご意見がある場合

実装に問題がある場合

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

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

謝辞

写真撮影: Paul Skorupskas(出典: Unsplash