使用螢幕捕獲 API

可用性有限

此特性不是基線特性,因為它在一些最廣泛使用的瀏覽器中不起作用。

在本文中,我們將探討如何使用螢幕捕獲 API 及其 getDisplayMedia() 方法來捕獲螢幕的部分或全部內容,以便在 WebRTC 會議期間進行流式傳輸、錄製或共享。

注意:值得注意的是,最新版本的 WebRTC adapter.js shim 包含了 getDisplayMedia() 的實現,以便在支援螢幕共享但未實現當前標準 API 的瀏覽器上啟用螢幕共享。這至少適用於 Chrome、Edge 和 Firefox。

捕獲螢幕內容

透過呼叫 navigator.mediaDevices.getDisplayMedia() 來啟動將螢幕內容捕獲為即時 MediaStream 的過程,該方法返回一個 Promise,該 Promise 解析為包含即時螢幕內容的流。下面示例中引用的 displayMediaOptions 物件可能如下所示:

js
const displayMediaOptions = {
  video: {
    displaySurface: "browser",
  },
  audio: {
    suppressLocalAudioPlayback: false,
  },
  preferCurrentTab: false,
  selfBrowserSurface: "exclude",
  systemAudio: "include",
  surfaceSwitching: "include",
  monitorTypeSurfaces: "include",
};

啟動螢幕捕獲:async/await 方式

js
async function startCapture(displayMediaOptions) {
  let captureStream = null;

  try {
    captureStream =
      await navigator.mediaDevices.getDisplayMedia(displayMediaOptions);
  } catch (err) {
    console.error(`Error: ${err}`);
  }
  return captureStream;
}

您可以使用非同步函式和 await 運算子(如上所示)編寫此程式碼,也可以直接使用 Promise(如下所示)。

啟動螢幕捕獲:Promise 方式

js
function startCapture(displayMediaOptions) {
  return navigator.mediaDevices
    .getDisplayMedia(displayMediaOptions)
    .catch((err) => {
      console.error(err);
      return null;
    });
}

無論哪種方式,使用者代理都會透過顯示使用者介面來響應,提示使用者選擇要共享的螢幕區域。這兩個 startCapture() 的實現都返回包含捕獲的顯示影像的 MediaStream

有關如何指定所需表面型別以及調整結果流的其他方法,請參閱下面的選項和約束

允許使用者選擇要捕獲的顯示介面的視窗示例

Screenshot of Chrome's window for picking a source surface

然後,您可以將捕獲的流 captureStream 用於任何接受流作為輸入的場景。下面的示例展示了幾種使用流的方法。

可見顯示錶面與邏輯顯示錶面

就螢幕捕獲 API 而言,顯示錶面是可以透過 API 選擇用於共享目的的任何內容物件。共享表面包括瀏覽器標籤頁的內容、完整的視窗以及顯示器(或組合成一個表面的顯示器組)的內容。

顯示錶面有兩種型別。可見顯示錶面是螢幕上完全可見的表面,例如最前面的視窗或標籤頁,或整個螢幕。

邏輯顯示錶面是部分或完全被遮擋的表面,可能是被其他物件部分覆蓋,也可能是完全隱藏或超出螢幕範圍。螢幕捕獲 API 對它們的處理方式有所不同。通常,瀏覽器會提供一個影像,以某種方式遮擋邏輯顯示錶面的隱藏部分,例如透過模糊處理或替換為某種顏色或圖案。這樣做是出於安全原因,因為使用者無法看到的內容可能包含他們不希望共享的資料。

使用者代理可能會在獲得使用者許可後捕獲被遮擋視窗的全部內容。在這種情況下,使用者代理可能會包含被遮擋的內容,方法是獲取視窗隱藏部分的當前內容,或者在當前內容不可用時顯示最近可見的內容。

選項和約束

傳遞給 getDisplayMedia() 的選項物件用於設定結果流的選項。

傳遞給選項物件的 videoaudio 物件還可以包含這些媒體軌道特有的額外約束。有關配置螢幕捕獲流的額外約束(新增到 MediaTrackConstraintsMediaTrackSupportedConstraintsMediaTrackSettings)的詳細資訊,請參閱共享螢幕軌道的屬性

在選擇要捕獲的內容之前,不會以任何方式應用任何約束。這些約束會改變您在結果流中看到的內容。例如,如果您為影片指定 width 約束,它將在使用者選擇要共享的區域後透過縮放影片來應用。它不會對源本身的大小施加限制。

注意:約束絕不會導致螢幕共享 API 可用於捕獲的源列表發生更改。這確保了 Web 應用程式不能透過限制源列表直到只剩一個專案來強制使用者共享特定內容。

當顯示捕獲生效時,共享螢幕內容的機器將顯示某種形式的指示器,以便使用者知道正在進行共享。

注意:出於隱私和安全原因,螢幕共享源無法使用 enumerateDevices() 進行列舉。與此相關,當 getDisplayMedia() 可用的源發生變化時,永遠不會發送 devicechange 事件。

捕獲共享音訊

getDisplayMedia() 最常用於捕獲使用者螢幕(或其部分)的影片。但是,使用者代理可能允許與影片內容一起捕獲音訊。此音訊的來源可能是選定的視窗、整個計算機的音訊系統或使用者的麥克風(或以上所有項的組合)。

在開始需要共享音訊的專案之前,請務必檢查 getDisplayMedia() 的瀏覽器相容性,以檢視您希望相容的瀏覽器是否支援捕獲螢幕流中的音訊。

要請求共享螢幕幷包含音訊,傳遞給 getDisplayMedia() 的選項可能如下所示:

js
const displayMediaOptions = {
  video: true,
  audio: true,
};

這允許使用者在使用者代理支援的範圍內完全自由地選擇他們想要的任何內容。透過在 audiovideo 物件中指定額外的選項和約束,可以進一步完善這一點:

js
const displayMediaOptions = {
  video: {
    displaySurface: "window",
  },
  audio: {
    echoCancellation: true,
    noiseSuppression: true,
    sampleRate: 44100,
    suppressLocalAudioPlayback: true,
  },
  surfaceSwitching: "include",
  selfBrowserSurface: "exclude",
  systemAudio: "exclude",
};

在此示例中,捕獲的顯示錶面將是整個視窗。音訊軌道應理想地啟用降噪和回聲消除功能,以及 44.1kHz 的理想音訊取樣率,並抑制本地音訊播放。

此外,應用程式向用戶代理提示它應該:

  • 在螢幕共享期間提供一個控制元件,允許使用者動態切換共享標籤頁。
  • 在請求捕獲時,從呈現給使用者的選項列表中隱藏當前標籤頁。
  • 不將系統音訊包含在提供給使用者的可能音訊源中。

捕獲音訊始終是可選的,即使 Web 內容請求包含音訊和影片的流,返回的 MediaStream 仍然可能只有一個影片軌道,沒有音訊。

使用捕獲的流

getDisplayMedia() 返回的 promise 解析為 MediaStream,其中包含至少一個影片流,該影片流包含螢幕或螢幕區域,並根據呼叫 getDisplayMedia() 時指定的約束進行調整或過濾。

潛在風險

圍繞螢幕共享的隱私和安全問題通常並不特別嚴重,但確實存在。最大的潛在問題是使用者無意中共享了他們不希望共享的內容。

例如,如果使用者正在共享螢幕,而可見的背景視窗恰好包含個人資訊,或者他們的密碼管理器在共享流中可見,那麼很容易發生隱私和/或安全漏洞。當捕獲邏輯顯示錶面時,這種影響可能會被放大,因為邏輯顯示錶面可能包含使用者根本不知道的內容,更不用說看到了。

認真對待隱私的使用者代理應該混淆螢幕上實際不可見的內容,除非已獲得專門共享該內容的授權。

授權捕獲顯示內容

在捕獲的螢幕內容流式傳輸開始之前,使用者代理將要求使用者確認共享請求,並選擇要共享的內容。

示例

流式螢幕捕獲

在此示例中,捕獲的螢幕區域的內容流式傳輸到同一頁面上的 <video> 元素中。

JavaScript

要實現此功能所需的程式碼並不多,如果您熟悉使用 getUserMedia() 從攝像頭捕獲影片,您會發現 getDisplayMedia() 非常熟悉。

設定

首先,設定一些常量來引用頁面上我們需要訪問的元素:用於流式傳輸捕獲螢幕內容的 <video> 元素、用於繪製日誌輸出的框,以及用於開啟和關閉螢幕影像捕獲的開始和停止按鈕。

物件 displayMediaOptions 包含要傳遞給 getDisplayMedia() 的選項;這裡,displaySurface 屬性設定為 window,表示應捕獲整個視窗。

最後,建立事件監聽器以檢測使用者對開始和停止按鈕的點選。

js
const videoElem = document.getElementById("video");
const logElem = document.getElementById("log");
const startElem = document.getElementById("start");
const stopElem = document.getElementById("stop");

// Options for getDisplayMedia()

const displayMediaOptions = {
  video: {
    displaySurface: "window",
  },
  audio: false,
};

// Set event listeners for the start and stop buttons
startElem.addEventListener("click", (evt) => {
  startCapture();
});

stopElem.addEventListener("click", (evt) => {
  stopCapture();
});
記錄內容

此示例覆蓋了某些 console 方法,以將其訊息輸出到 ID 為 log<pre> 塊中。

js
console.log = (msg) => (logElem.textContent = `${logElem.textContent}\n${msg}`);
console.error = (msg) =>
  (logElem.textContent = `${logElem.textContent}\nError: ${msg}`);

這允許我們使用 console.log()console.error() 將資訊記錄到文件中的日誌框中。

開始顯示捕獲

下面的 startCapture() 方法開始捕獲 MediaStream,其內容取自使用者選擇的螢幕區域。當點選“開始捕獲”按鈕時,會呼叫 startCapture()

js
async function startCapture() {
  logElem.textContent = "";

  try {
    videoElem.srcObject =
      await navigator.mediaDevices.getDisplayMedia(displayMediaOptions);
    dumpOptionsInfo();
  } catch (err) {
    console.error(err);
  }
}

清除日誌內容以清除上次連線嘗試留下的任何文字後,startCapture() 呼叫 getDisplayMedia(),並將由 displayMediaOptions 定義的約束物件傳遞給它。使用 await,以下程式碼行在 getDisplayMedia() 返回的 promise 解析後才會執行。解析後,Promise 返回一個 MediaStream,該流將流式傳輸使用者選擇的螢幕、視窗或其他區域的內容。

透過將返回的 MediaStream 儲存到元素的 srcObject 中,將流連線到 <video> 元素。

dumpOptionsInfo() 函式(我們稍後會看到)將有關流的資訊轉儲到日誌框中,以用於教育目的。

如果其中任何一個失敗,catch() 子句會向日志框輸出錯誤訊息。

停止顯示捕獲

當點選“停止捕獲”按鈕時,會呼叫 stopCapture() 方法。它透過使用 MediaStream.getTracks() 獲取其軌道列表,然後呼叫每個軌道的 stop() 方法來停止流。完成此操作後,srcObject 將設定為 null,以確保任何感興趣的人都明白沒有連線流。

js
function stopCapture(evt) {
  let tracks = videoElem.srcObject.getTracks();

  tracks.forEach((track) => track.stop());
  videoElem.srcObject = null;
}
轉儲配置資訊

出於資訊目的,上面顯示的 startCapture() 方法呼叫了一個名為 dumpOptions() 的方法,該方法輸出當前的軌道設定以及在建立流時施加在流上的約束。

js
function dumpOptionsInfo() {
  const videoTrack = videoElem.srcObject.getVideoTracks()[0];

  console.log("Track settings:");
  console.log(JSON.stringify(videoTrack.getSettings(), null, 2));
  console.log("Track constraints:");
  console.log(JSON.stringify(videoTrack.getConstraints(), null, 2));
}

軌道列表透過對捕獲螢幕的 MediaStream 呼叫 getVideoTracks() 獲得。當前生效的設定使用 getSettings() 獲得,已建立的約束使用 getConstraints() 獲得。

HTML

HTML 以一個介紹性段落開始,然後進入核心內容。

html
<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 id="video" autoplay></video>
<br />

<strong>Log:</strong>
<br />
<pre id="log"></pre>

HTML 的關鍵部分是:

  1. 一個標記為“開始捕獲”的 <button>,當點選時,它會呼叫 startCapture() 函式來請求訪問並開始捕獲螢幕內容。
  2. 第二個按鈕,“停止捕獲”,點選後會呼叫 stopCapture() 來終止螢幕內容的捕獲。
  3. 一個 <video>,捕獲的螢幕內容會流式傳輸到其中。
  4. 一個 <pre> 塊,攔截的 console 方法會將日誌文字放入其中。

CSS

此示例中的 CSS 完全是用於美化的。影片被賦予邊框,其寬度設定為佔據幾乎所有可用的水平空間(width: 98%)。max-width 設定為 860px 以設定影片大小的絕對上限。

css
#video {
  border: 1px solid #999999;
  width: 98%;
  max-width: 860px;
}

#log {
  width: 25rem;
  height: 15rem;
  border: 1px solid black;
  padding: 0.5rem;
  overflow: scroll;
}

結果

最終產品看起來像這樣。如果您的瀏覽器支援螢幕捕獲 API,點選“開始捕獲”將呈現使用者代理的介面,用於選擇要共享的螢幕、視窗或標籤頁。

安全

為了在啟用 Permissions Policy 時正常工作,您需要 display-capture 許可權。這可以透過 Permissions-Policy HTTP 頭實現,或者——如果您在 <iframe> 中使用螢幕捕獲 API,則透過 <iframe> 元素的 allow 屬性實現。

例如,HTTP 頭中的這一行將為文件以及從同一來源載入的任何嵌入式 <iframe> 元素啟用螢幕捕獲 API:

http
Permissions-Policy: display-capture=(self)

如果您在 <iframe> 中執行螢幕捕獲,您可以僅為該框架請求許可權,這顯然比更一般地請求許可權更安全。

html
<iframe src="https://mycode.example.net/etc" allow="display-capture"> </iframe>

瀏覽器相容性

另見