从任何元素捕获视频流

François Beaufort
François Beaufort

借助 Screen Capture API,您可以截取整个当前标签页。借助 Element Capture API,您可以捕获并记录特定的 HTML 元素。它将整个标签页的捕获转换成特定 DOM 子树的捕获,仅捕获 target 元素的直接后代。也就是说,它会剪裁并移除被遮挡和被遮挡的内容。

为什么要使用元素捕获?

考虑视频会议应用的要求有助于您了解元素捕获的用处。如果您有一款视频会议应用,且允许您在 iframe 中嵌入第三方应用,那么有时您可能希望将该 iframe 捕获为视频,并将其传输给远程参与者。

Chrome 中视频会议通话的屏幕截图。
Elad 在与 François 的视频会议通话中使用第三方应用。

调用 getDisplayMedia() 并让用户选择当前标签页会传输整个当前标签页。这样很可能会将人们自己的视频传输回他们。您可以使用区域捕获功能将其剪裁掉。

但是,如果演示者使用视频会议应用,并且某些内容(例如下拉列表)恰好在要用于拍摄的内容之上绘制,该怎么办?

一个下拉列表的屏幕截图,其中列出了要拍摄的内容。
在要拍摄的内容上方会显示一个下拉列表。

“区域捕获”对您没有帮助。下拉列表的部分内容可能会显示在远程参与者的屏幕上。

已截取下拉列表的屏幕截图。
Elad 的下拉列表显示在 François 收到的内容上方。

区域捕获以这种方式捕获部分元素(称为“遮挡内容”)这一事实会产生多个问题:

  • 遮挡内容可能会妨碍用户查看用户原本打算分享的内容。
  • 遮挡内容可能是私密内容(例如聊天通知)。
  • 遮挡内容可能会让用户感到困惑。(例如,应用的重新布局可能会暂时让远程参与者自己的视频覆盖捕获的目标。)

Element Capture API 可让您定位要分享的元素,从而解决了所有这些问题。

视图中没有下拉列表的目标元素的屏幕截图。
François 没有看到 Elad 的下拉列表。

如何使用元素捕获?

captureTarget 是网页上的一个元素,其中包含用户希望捕获的内容。您希望视频会议 Web 应用截取captureTarget的视频并将其分享给远程参与者。因此,您从 captureTarget 派生了 RestrictionTarget。使用此 RestrictionTarget 限制视频轨道后,该视频轨道上的帧现在仅包含属于 captureTarget 及其直接 DOM 后代的像素。

如果 captureTarget 更改了大小、形状或位置,视频轨道会跟上,而无需从任一 Web 应用执行任何其他输入。遮蔽出现、消失或移动的内容同样不需要特殊处理。

请再次查看以下步骤:

首先允许用户截取当前标签页。

// 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 或派生自当前标签页中某个 Element 的任何 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() 方法会在所有标签页拍摄视频轨道中公开,而不仅仅是用于自拍。不过,元素捕获目前仅针对自捕获启用。因此,在尝试限制跟踪之前,建议检查用户是否选择了当前标签页。这可以使用捕获句柄来实现。您还可以使用 preferCurrentTab 请求浏览器引导用户进行自拍。

透明度

应用通过 getDisplayMedia() 获取的视频帧不包含 alpha 通道。如果应用设置了部分透明的捕获目标,则剥离 alpha 通道可能会产生以下后果:

  • 颜色可能会发生变化。移除 Alpha 通道后,在浅色背景上绘制的部分透明的目标元素可能会颜色较深,而在深色背景上绘制的目标元素可能会颜色较浅。
  • 当 alpha 通道设置为最大值时,用户看不到或察觉不到的颜色将在 alpha 通道移除后显示。例如,如果透明部分包含 RGBA 代码 rgba(0, 0, 0, 0),这可能会导致捕获的帧中出现意外的黑色区域。
非矩形透明拍摄目标的结果的屏幕截图。
非矩形透明拍摄目标视频流(右)是一个黑色背景矩形,其中包含一个不透明的蓝色圆圈。

不符合条件的拍摄目标

您始终可以开始将音轨限制为任何有效的捕获目标。不过,在某些条件下不会生成帧,例如,当元素或祖先实体为 display:none 时。一般理由是,该限制只适用于包含单个连贯性二维矩形区域的元素,该区域的像素可在逻辑上独立于任何父元素或同级元素而确定。

确保元素符合限制的一个重要考虑因素是,它必须形成自己的堆叠上下文。为此,您可以指定 isolation CSS 属性,并将其设置为 isolate

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

请注意,目标元素在任何时候(例如,当应用更改其 CSS 属性时)都可以在符合限制条件和不符合限制条件之间进行切换。应用应采用合理的捕获目标并避免意外更改其属性。如果目标元素不再符合条件,那么在目标元素再次符合限制条件之前,系统不会在轨道上发出新帧。

启用元素捕获

在桌面设备上,Chrome 中提供了 Element Capture API(位于 Element Capture 标记后面),该 API 可通过 chrome://flags/#element-capture 启用。

此功能还即将进入桌面版 Chrome 121 的源试用,开发者可借助此功能让自己网站的访问者从真实用户那里收集数据。如需详细了解源试用,请参阅开始试用

安全和隐私设置

如需了解安全方面的权衡取舍,请参阅元素捕获规范的隐私权和安全注意事项部分。

Chrome 浏览器会在捕获的标签页的边缘绘制蓝色边框。

演示

您可以通过在 Glitch 上运行演示来体验 Element Capture。请务必查看源代码

反馈

Chrome 团队和网络标准社区希望了解您使用 Element Capture 的体验。

向我们介绍设计

“区域捕获”是否有什么无法按预期运行?或者,您是否缺少实现自己的想法所需的方法或属性?对安全模型有疑问或意见?

  • GitHub 代码库中提交规范问题,或者添加您对现有问题的看法。

实施时遇到问题?

您在 Chrome 的实现过程中是否发现了错误?或者,实现是否与规范不同?

  • 请前往 https://new.crbug.com 提交错误。请务必提供尽可能多的细节信息,并提供关于重现错误的简单说明。Glitch 非常适用于分享轻松快速的重现问题。

致谢

照片由 Paul Skorupskas 拍摄于 Unsplash 网站