滚动和缩放已录制的标签页

François Beaufort
François Beaufort

借助 Screen Capture API,您已经可以在 Web 平台上共享标签页、窗口和屏幕。当 Web 应用调用 getDisplayMedia() 时,Chrome 会提示用户将标签页、窗口或屏幕作为 MediaStreamTrack 视频与 Web 应用分享。

许多使用 getDisplayMedia() 的 Web 应用都会向用户显示所捕获 Surface 的视频预览。例如,视频会议应用通常会将此视频流式传输给远程用户,同时将其渲染到本地 HTMLVideoElement,以便本地用户不断预览自己分享的内容。

本文档介绍了 Chrome 中新的 Captured Surface Control API,该 API 可让您的 Web 应用滚动已捕获的标签页,以及读取和写入已捕获标签页的缩放级别。

用户滚动和缩放已截取的标签页(演示)。

为何使用 Captured Surface Control?

所有视频会议应用都存在同样的缺点。如果用户希望与已截取的标签页或窗口互动,则必须切换到该界面,从而离开视频会议应用。这会带来一些挑战:

  • 用户无法同时查看所截取的应用和远程用户的视频画面,除非他们使用画中画功能,或者为视频会议标签页和共享标签页分别使用并排窗口。在较小的屏幕上,这可能会很困难。
  • 用户需要在视频会议应用和截取的界面之间跳转,这给用户带来了负担。
  • 用户离开视频会议应用时,无法再使用视频会议应用提供的控件;例如,嵌入式聊天应用、表情符号回应、有关用户请求加入通话的通知、多媒体和布局控件,以及其他实用的视频会议功能。
  • 演示者无法将控制权委托给远程参与者。这会导致一个我们太熟悉的场景:远程用户要求演示者更改幻灯片、上下滚动一点或调整缩放级别。

Captured Surface Control API 解决了这些问题。

如何使用 Captured Surface Control?

若要成功使用 Captured Surface Control,需要完成几个步骤,例如明确捕获浏览器标签页并获得用户授权,然后才能滚动和缩放所捕获的标签页。

截取浏览器标签页

首先,提示用户使用 getDisplayMedia() 选择要共享的 Surface,并在此过程中将 CaptureController 对象与捕获会话相关联。我们很快就会使用该对象来控制捕获的 Surface。

const controller = new CaptureController();
const stream = await navigator.mediaDevices.getDisplayMedia({ controller });

接下来,以 <video> 元素的形式生成所捕获 Surface 的本地预览:

const previewTile = document.querySelector('video');
previewTile.srcObject = stream;

如果用户选择共享窗口或屏幕,则目前不在我们的服务范围之内;但如果他们选择共享标签页,我们可以继续。

const [track] = stream.getVideoTracks();

if (track.getSettings().displaySurface !== 'browser') {
  // Bail out early if the user didn't pick a tab.
  return;
}

权限提示

对给定 CaptureController 对象首次调用 forwardWheel()increaseZoomLevel()decreaseZoomLevel()resetZoomLevel() 会产生权限提示。如果用户授予权限,系统会允许进一步调用这些方法。

必须有用户手势才能向用户显示权限提示,因此,应用应仅在已获得相应权限或响应用户手势(例如在 Web 应用中对相关按钮执行 click)时调用上述方法。

滚动

使用 forwardWheel(),捕获应用可以将捕获应用本身内的源元素中的轮滚动事件转发到所捕获标签页的视口。这些事件与直接用户互动对被捕获的应用而言是无法区分的。

假设捕获应用使用名为 "previewTile"<video> 元素,以下代码展示了如何将轮滚事件转发到所捕获的标签页:

const previewTile = document.querySelector('video');
try {
  // Relay the user's action to the captured tab.
  await controller.forwardWheel(previewTile);
} catch (error) {
  // Inspect the error.
  // ...
}

方法 forwardWheel() 接受单个输入,该输入可以是以下任一内容:

  • 一个 HTML 元素,系统会将滚轮事件转发到被捕获的标签页。
  • null,表示应停止转发。

成功调用 forwardWheel() 会替换之前的调用。

在以下情况下,forwardWheel() 返回的 promise 可能会被拒绝:

  • 如果捕获会话尚未开始或已停止。
  • 如果用户未授予相关权限。

缩放

与所捕获标签页的缩放级别进行交互是通过以下 CaptureController API 接口完成的:

getSupportedZoomLevels()

此方法会针对要拍摄的 surface 类型返回浏览器支持的缩放级别列表。此列表中的值以相对于“默认缩放级别”(定义为 100%)的百分比表示。该列表是单调递增的,并且包含值 100。

此方法只能针对受支持的显示屏 Surface 类型调用,目前仅限标签页。

如果满足以下条件,系统可能会调用 controller.getSupportedZoomLevels()

  • controller 与有效的拍摄相关联。
  • 屏幕截图显示的是标签页。

否则,系统会引发错误。

调用此方法不需要 "captured-surface-control" 权限。

zoomLevel

此只读属性用于存储所捕获标签页的当前缩放级别。这是一个可为 null 的属性,如果所捕获的 Surface 类型没有有意义的缩放级别定义,则会保留 null。目前,zoom-level 仅适用于标签页,而不适用于窗口或屏幕。

拍摄结束后,该属性将保留上次的缩放级别值。

读取此属性不需要 "captured-surface-control" 权限。

onzoomlevelchange

此事件处理脚本有助于监听所捕获标签页的缩放级别的更改。这些情况可能发生在:

  • 当用户与浏览器互动以手动更改所捕获标签页的缩放级别时。
  • 响应拍摄应用对缩放设置方法的调用(如下所述)。

读取此属性不需要 "captured-surface-control" 权限。

increaseZoomLevel()decreaseZoomLevel()resetZoomLevel()

这些方法可用于操控所捕获标签页的缩放级别。

increaseZoomLevel()decreaseZoomLevel() 会分别将缩放级别更改为下一个或上一个缩放级别,具体取决于 getSupportedZoomLevels() 返回的顺序。resetZoomLevel() 将值设置为 100。

调用这些方法需要 "captured-surface-control" 权限。如果捕获应用没有此权限,系统会提示用户授予或拒绝此权限。

这些方法都返回一个 promise,如果调用成功,则解析该 promise;否则,该 promise 会被拒绝。拒绝的原因可能包括:

  • 缺少权限。
  • 在开始捕获之前调用。
  • 在截取结束后调用。
  • 对与不受支持的显示屏 Surface 类型的截图相关联的 controller 调用。(也就是说,除标签页截图之外的任何操作)。
  • 尝试超过最大值或最小值。

值得注意的是,建议在 controller.zoomLevel == controller.getSupportedZoomLevels().at(0) 时避免调用 decreaseZoomLevel(),并以类似于 .at(-1) 的方式保护对 increaseZoomLevel() 的调用。

以下示例展示了如何让用户直接从截图应用中放大所截取标签页的缩放级别:

const zoomIncreaseButton = document.getElementById('zoomInButton');
zoomIncreaseButton.addEventListener('click', async (event) => {
  if (controller.zoomLevel >= controller.getSupportedZoomLevels().at(-1)) {
    return;
  }
  try {
    await controller.increaseZoomLevel();
  } catch (error) {
    // Inspect the error.
    // ...
  }
});

以下示例展示了如何对所捕获标签页的缩放级别变化做出响应:

controller.addEventListener('zoomlevelchange', (event) => {
  const zoomLevelLabel = document.querySelector('#zoomLevelLabel');
  zoomLevelLabel.textContent = `${controller.zoomLevel}%`;
});

功能检测

如需检查是否支持 Captured Surface Control API,请使用以下命令:

if (!!window.CaptureController?.prototype.forwardWheel) {
  // CaptureController forwardWheel() is supported.
}

您同样可以使用任何其他 Captured Surface Control API Surface(例如 increaseZoomLeveldecreaseZoomLevel),甚至可以检查所有 Surface。

浏览器支持

从 Chrome 136 开始,仅桌面设备支持 Captured Surface Control。

安全和隐私设置

借助 "captured-surface-control" 权限政策,您可以管理捕获应用和嵌入的第三方 iframe 对 Captured Surface Control 的访问权限。如需了解安全权衡,请参阅“捕获 Surface 控件”说明文档的隐私和安全注意事项部分。

演示

您可以在 Glitch 上运行演示版,试用 Captured Surface Control。请务必查看源代码

反馈

Chrome 团队和 Web 标准社区希望了解您使用 Captured Surface Control 的体验。

请说明设计

Captured Surface Capture 是否存在某些方面未按预期运行?或者,您是否缺少实现想法所需的方法或属性?对安全模型有疑问或意见?在 GitHub 代码库中提交规范问题,或在现有问题中添加您的想法。

实现方面存在问题?

您是否发现了 Chrome 实现中的 bug?或者实现与规范不同?请访问 https://new.crbug.com 提交 bug。请务必提供尽可能详细的信息,以及重现问题的说明。故障非常适合分享可重现的 bug。