使用元素捕获和区域捕获 API

本指南提供了元素捕获和区域捕获 API 典型用法的演练,展示了如何使用它们以及它们解决的问题。

Background

默认情况下,屏幕捕获 API 捕获整个屏幕、窗口或选项卡。元素捕获和区域捕获 API 分别允许您将捕获的流限制到特定的渲染 DOM 树,或限制到由特定 DOM 树的边界框定义的屏幕部分。

当您只想共享有限区域以减少不必要的带宽或显示捕获所需的屏幕空间,或出于隐私原因(您可能不想向其他参与者显示您的消息通知,或运行您正在共享的演示所需的背景设置)时,这会很有用。

此外,当捕获您的网络摄像头输出时,您可能会遇到那些不合时宜的“无限虫洞”或“镜厅”之类的效果。元素捕获和区域捕获 API 也可以帮助您避免此类问题。

何时使用每个 API

元素捕获 API 捕获元素本身(及其后代),而区域捕获 API 捕获由目标元素的边界框定义的浏览器选项卡区域。元素捕获将始终只显示被捕获的元素,即使其他 DOM 内容与其重叠,而区域捕获可能会导致重叠内容显示在您打算共享的内容之上。

两者都有合法的用例

  • 如果您需要将捕获限制在单个 DOM 树内,并排除其之外的任何内容,那么元素捕获 API 是更好的选择。例如,您不希望私人内容(例如一组消息通知或演讲者备注 UI)出现在捕获中。
  • 但是,如果您确实想要捕获浏览器选项卡的一个区域,无论其中显示什么,区域捕获 API 都能很好地为您服务。

在下一节中,我们将从一个基本的屏幕捕获 API 演示开始,以说明元素捕获和区域捕获 API 旨在解决的问题。

屏幕捕获 API 演示

此演示使用屏幕捕获 API 捕获窗口、屏幕或选项卡,并通过同一页面上的 <video> 元素广播流。您可以在 屏幕捕获 API 示例 上查看其运行情况(另请参阅源代码)。

HTML

HTML 以主标题和介绍性文本开头,然后包含两个 <button> 元素来开始和停止捕获

html
<h1>Screen Capture API example</h1>
<p>
  This example shows you the contents of the selected part of your display.
  Click the Start Capture button to begin.
</p>

<p>
  <button id="start">Start Capture</button>&nbsp;
  <button id="stop">Stop Capture</button>
</p>

接下来,我们包含主应用程序容器,其中包含用于广播捕获的 <video> 元素,以及一个演示占位符 <div>

html
<div id="main-app">
  <video autoplay></video>
  <div id="demo">
    <h2>Some kind of demo</h2>
    <p>
      This container is a placeholder for some kind of demo that you might want
      to share with other participants.
    </p>
  </div>
</div>

CSS

此演示的 CSS 大多数不值得一提,但以下几条规则值得解释。为简洁起见,我们已隐藏了其余的 CSS。

我们将 main-app <div>display 值设置为 flex,以将视频和演示占位符并排布置成两列,并在它们之间设置 5%gap。我们还将容器的 min-width 设置为 980px,基本上将演示应用程序限制为桌面布局。这是因为元素捕获和区域捕获仅在桌面浏览器上受支持,并且不捕获屏幕外内容。

css
#main-app {
  display: flex;
  gap: 5%;
  min-width: 980px;
}

我们还将 <video> 元素和 demo <div>flex 值设置为 1,以便它们占用相同的水平空间。

css
video,
#demo {
  flex: 1;
}

最后,我们给 <video> 元素设置 max-width50%,并设置固定的 aspect-ratio4/3。这是为了使视频保持一致的大小,并避免在屏幕捕获开始广播时出现太大的布局变化。如果我们不这样做,<video> 元素将增长到与整个捕获区域(窗口或屏幕)相同的宽度,这将影响布局。毕竟,它是一个替换元素,因此其固有大小取决于其内容的大小。

css
video {
  max-width: 50%;
  aspect-ratio: 4/3;
}

布局偏移在使用区域和元素捕获 API 时也可能导致问题,因此此代码包含在所有三个演示中。

JavaScript

此示例的 JavaScript 改编自我们的“使用屏幕捕获 API”指南中的流式屏幕捕获示例。我们在此不重复完整的代码解释;我们只解释最相关的捕获代码。

在调用 getDisplayMedia() 时传递给它的选项对象中,我们设置了 preferCurrentTab: true。此提示建议浏览器应在询问用户要共享什么的对话框中,将用户的当前选项卡作为最主要的捕获源。例如,Chrome 只有在设置 preferCurrentTab: true 时才提供此选项。

js
const displayMediaOptions = {
  video: {
    displaySurface: "window",
  },
  preferCurrentTab: true,
};

如果您正在构建一个带有内置“共享屏幕”选项的应用程序,此选项非常有用——您不希望允许用户共享不同的选项卡或窗口。

当按下“开始捕获”按钮时,startCapture() 函数运行,该函数调用 MediaDevices.getDisplayMedia()。这会使浏览器提示用户选择一个要共享的表面(窗口、选项卡等)。一旦做出选择,生成的 MediaStream 将设置为 <video> 元素的 HTMLMediaElement.srcObject 属性的值以进行广播

js
async function startCapture() {
  try {
    videoElem.srcObject =
      await navigator.mediaDevices.getDisplayMedia(displayMediaOptions);
  } catch (err) {
    console.error(err);
  }
}

屏幕捕获 API 问题

支持的浏览器中运行上面的演示,单击“开始捕获”,并选择运行演示的同一选项卡。您将看到前面提到的“镜厅效应”

A browser window containing a video capture of that same browser window, meaning that it shows infinite captures inside captures, getting smaller and smaller

这显然不理想,并且会在任何具有内置“共享屏幕”选项的会议应用程序中引起问题。

元素捕获 API

元素捕获 API 将捕获区域限制到指定的渲染 DOM 树(选定的元素及其后代)。在本节中,我们将探讨第二个与上述演示相同的演示,只是它在基本屏幕捕获之上使用了元素捕获。请访问 元素捕获 API 示例 查看此演示的实时运行(另请参阅源代码)。

HTML 与上一个示例相同,CSS 也_几乎_相同。我们现在将解释 JavaScript 的差异,然后稍后在元素捕获 API 限制部分查看 CSS 差异。

要使用元素捕获 API,我们还需要获取对一个 DOM 元素的引用,我们稍后将该元素用作**限制目标**——流中显示的屏幕区域将仅限于该渲染元素及其后代

js
const demoElem = document.querySelector("#demo");

其他代码差异都存在于修改后的 startCapture() 函数中

js
async function startCapture() {
  try {
    const stream =
      await navigator.mediaDevices.getDisplayMedia(displayMediaOptions);
    const [track] = stream.getVideoTracks();
    const restrictionTarget = await RestrictionTarget.fromElement(demoElem);
    await track.restrictTo(restrictionTarget);
    videoElem.srcObject = stream;
  } catch (err) {
    console.error(err);
  }
}
  1. 在这里,我们首先像以前一样,使用 mediaDevices.getDisplayMedia() 获取媒体流。
  2. 然后我们使用 MediaStream.getVideoTracks() 从流中分离视频轨道。
  3. 我们通过运行 RestrictionTarget.fromElement(),并向其传递我们之前获取的 DOM 元素引用,来创建必要的 restrictionTarget 对象,以将限制应用于视频轨道。
  4. 我们通过在其上调用 BrowserCaptureMediaStreamTrack.restrictTo() 并向其传递 restrictionTarget 对象来将限制目标应用于轨道。
  5. 完成上述所有操作后,我们将 <video> 元素的 srcObject 属性值设置为流,以开始广播它。

现在尝试在支持的浏览器中运行元素捕获 API 示例。您应该会看到流中只包含演示占位符,从而解决了“镜厅效应”问题。

注意:您可以通过再次在同一轨道上调用 restrictTo() 并向其传递参数 null 来停止限制

js
await track.restrictTo(null);

元素捕获 API 的限制

为了确保元素**符合限制条件**,即当它被选为限制目标元素时将被捕获,它必须形成一个堆叠上下文并在 3D 空间中展平。

为了处理这些限制,我们设置了以下针对演示容器元素的附加 CSS 规则

css
#demo {
  /* Forms a stacking context */
  isolation: isolate;
  /* Flattened */
  transform-style: flat;
  /* Explicit background color to stop the capture being transparent */
  background-color: white;
}

我们将 isolation 属性设置为 isolate 以使元素形成一个堆叠上下文,并将 transform-style 属性设置为 flat 以将其展平。此外,由于我们设置的隔离的性质,该元素将不再继承页面的默认白色。因此,我们将 background-color 设置为 white,以防止捕获透明。

有关可用作限制目标的元素的完整限制列表,请参阅 RestrictionTarget.fromElement() 参考页面。

区域捕获 API

区域捕获 API 与元素捕获 API 的效果非常相似,只是它不是将捕获区域限制到特定的渲染 DOM 树,而是将流裁剪到由目标元素的边界框定义的当前浏览器选项卡区域。让我们先看一个演示,稍后将更详细地探讨两者之间的差异。

在本节中,我们将探讨第三个演示,它与前两个演示相同,只是它在基本屏幕捕获之上使用了区域捕获。请访问 区域捕获 API 示例 查看此演示的实时运行(另请参阅源代码)。

HTML 和 CSS 与前面的示例相同。JavaScript 与元素捕获的 JavaScript 几乎相同,只有一些显著差异,我们现在将解释这些差异。

要使用区域捕获 API,我们首先获取对一个 DOM 元素的引用,我们稍后将该元素用作**裁剪目标**——流中显示的区域将仅裁剪到该元素渲染的区域

js
const demoElem = document.querySelector("#demo");

现在让我们检查区域捕获演示的 startCapture() 函数

js
async function startCapture() {
  try {
    const stream =
      await navigator.mediaDevices.getDisplayMedia(displayMediaOptions);
    const [track] = stream.getVideoTracks();
    const cropTarget = await CropTarget.fromElement(demoElem);
    await track.cropTo(cropTarget);
    videoElem.srcObject = stream;
  } catch (err) {
    console.error(err);
  }
}
  1. 和以前一样,我们首先使用 mediaDevices.getDisplayMedia() 获取媒体流,然后使用 MediaStream.getVideoTracks() 从流中分离视频轨道。
  2. 我们通过运行 fromElement(),并向其传递我们之前获取的 DOM 元素引用,来创建必要的 cropTarget 对象,以将裁剪应用于视频轨道。
  3. 我们通过在其上调用 BrowserCaptureMediaStreamTrack.cropTo() 并向其传递 cropTarget 对象来将裁剪目标应用于轨道。
  4. 完成上述所有操作后,我们将 <video> 元素的 srcObject 属性值设置为流,以开始广播它。

现在尝试在支持的浏览器中运行区域捕获 API 示例。您应该会看到流中只包含演示占位符,这也解决了“镜厅效应”问题。

注意:您可以通过再次在同一轨道上调用 cropTo() 并向其传递参数 null 来停止裁剪

js
await track.cropTo(null);

区域捕获 API 的限制

区域捕获没有与元素捕获相同级别的限制——它只是将流裁剪到特定大小,而不是广播特定的渲染 DOM 树,因此它不需要此规则

css
#demo {
  /* Forms a stacking context */
  isolation: isolate;
  /* Flattened */
  transform-style: flat;
  /* Explicit background color to stop the capture being transparent */
  background-color: white;
}

但是,可用作裁剪目标的元素仍然存在限制。有关完整列表,请参阅 CropTarget.fromElement() 参考页面。

另见