使用元素捕獲和區域捕獲 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() 參考頁面。

另見