捲動及縮放已擷取的分頁

François Beaufort
François Beaufort

您現在可以使用 Screen Capture API,在網頁平台上分享分頁、視窗和螢幕畫面。當網頁應用程式呼叫 getDisplayMedia() 時,Chrome 會提示使用者將分頁、視窗或畫面以 MediaStreamTrack 影片的形式分享給網頁應用程式。

許多使用 getDisplayMedia() 的網路應用程式會向使用者顯示擷取畫面的影片預覽畫面。舉例來說,視訊會議應用程式通常會將這部影片串流傳送至遠端使用者,同時將影片轉譯至本機 HTMLVideoElement,讓本機使用者持續看到分享內容的預覽畫面。

本文件說明 Chrome 中的新 Captured Surface Control API,可讓網頁應用程式捲動已擷取的分頁,以及讀取及寫入已擷取分頁的縮放等級。

使用者捲動及縮放已擷取的分頁 (示範)。

為什麼要使用已擷取的表面控制項?

所有視訊會議應用程式都會遇到相同的缺點:如果使用者想要與擷取的程式碼分頁或視窗互動,就必須切換至該途徑,離開視訊會議應用程式。這會帶來一些挑戰:

  • 除非使用者使用子母畫面功能,或將視訊會議分頁和共用分頁分開顯示,否則無法同時查看擷取的應用程式和遠端使用者的影片。在較小的螢幕上,這可能會很困難。
  • 使用者必須在視訊會議應用程式和擷取畫面之間切換,這會造成負擔。
  • 使用者離開視訊會議應用程式時,將無法存取該應用程式提供的控制項,例如內嵌的即時通訊應用程式、表情符號回應、要求加入通話的使用者通知、多媒體和版面配置控制項,以及其他實用的視訊會議功能。
  • 主持人無法將控制權委派給遠端參與者。這會導致常見的情況:遠端使用者要求講者變更投影片、上下捲動畫面或調整縮放等級。

Captured Surface Control API 可解決這些問題。

如何使用已擷取的表面控制項?

要成功使用「Captured Surface Control」,您必須先完成幾個步驟,例如明確擷取瀏覽器分頁,並取得使用者的權限,才能捲動及縮放已擷取的分頁。

擷取瀏覽器分頁

首先,請使用 getDisplayMedia() 提示使用者選擇要分享的途徑,並在過程中將 CaptureController 物件與擷取工作階段建立關聯。我們很快就會使用該物件控制已擷取的表面。

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

接下來,請以 <video> 元素的形式產生擷取表面的本機預覽畫面:

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 物件上首次呼叫 sendWheel()setZoomLevel() 時,系統會顯示權限提示。如果使用者授予權限,系統就會允許在該 CaptureController 物件上進一步叫用這些方法。如果使用者拒絕權限,系統會拒絕傳回的承諾。

請注意,CaptureController 物件會與特定的擷取工作階段建立唯一關聯,無法與其他擷取工作階段建立關聯,且不會保留在定義該物件的頁面中。不過,擷取工作階段在擷取的網頁導覽後保留。

您必須使用手勢,才能向使用者顯示權限提示。只有 sendWheel()setZoomLevel() 呼叫需要使用者手勢,且只有在需要顯示提示時才需要。如果使用者在網頁應用程式中點選放大或縮小按鈕,系統會視為使用者動作;但如果應用程式想先提供捲動控制項,開發人員應記住,捲動動作「並非」使用者動作。其中一個方法是先向使用者提供「開始捲動」按鈕,如下例所示:

const startScrollingButton = document.querySelector('button');

startScrollingButton.addEventListener('click', async () => {
  try {
    const noOpWheelAction = {};

    await controller.sendWheel(noOpWheelAction);
    // The user approved the permission prompt.
    // You can now scroll and zoom the captured tab as shown later in the article.
  } catch (error) {
    return; // Permission denied. Bail.
  }
});

捲動

使用 sendWheel() 時,擷取應用程式可在分頁檢視區內,以所選座標提供所選大小的輪詢事件。事件無法讓擷取的應用程式區分直接使用者互動。

假設擷取應用程式採用名為 "previewTile"<video> 元素,以下程式碼說明如何將傳送輪盤事件轉送至擷取的分頁:

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

previewTile.addEventListener('wheel', async (event) => {
  // Translate the offsets into coordinates which sendWheel() can understand.
  // The implementation of this translation is explained further below.
  const [x, y] = translateCoordinates(event.offsetX, event.offsetY);
  const [wheelDeltaX, wheelDeltaY] = [-event.deltaX, -event.deltaY];

  try {
    // Relay the user's action to the captured tab.
    await controller.sendWheel({ x, y, wheelDeltaX, wheelDeltaY });
  } catch (error) {
    // Inspect the error.
    // ...
  }
});

sendWheel() 方法會使用含有兩組值的字典:

  • xy:輪盤事件的座標。
  • wheelDeltaXwheelDeltaY:水平和垂直捲動的幅度,以像素為單位。請注意,這些值與原始輪盤事件相反。

translateCoordinates() 的可能實作方式如下:

function translateCoordinates(offsetX, offsetY) {
  const previewDimensions = previewTile.getBoundingClientRect();
  const trackSettings = previewTile.srcObject.getVideoTracks()[0].getSettings();

  const x = trackSettings.width * offsetX / previewDimensions.width;
  const y = trackSettings.height * offsetY / previewDimensions.height;

  return [Math.floor(x), Math.floor(y)];
}

請注意,先前的程式碼中包含三種不同的大小:

  • <video> 元素的大小。
  • 擷取的畫面大小 (此處以 trackSettings.widthtrackSettings.height 表示)。
  • 分頁大小。

<video> 元素的大小完全在擷取應用程式的網域中,瀏覽器無法得知。分頁大小完全在瀏覽器的網域中,網頁應用程式無法得知。

網路應用程式會使用 translateCoordinates(),將相對於 <video> 元素的偏移量轉譯為影片軌道本身座標空間中的座標。瀏覽器也會在擷取的框架大小和分頁大小之間轉換,並以與網頁應用程式預期相符的偏移量傳送捲動事件。

sendWheel() 傳回的承諾可能會在下列情況下遭到拒絕:

  • 如果擷取工作階段尚未開始或已停止,包括在瀏覽器處理 sendWheel() 動作時異步停止。
  • 如果使用者未授予應用程式使用 sendWheel() 的權限。
  • 如果擷取應用程式嘗試在 [trackSettings.width, trackSettings.height] 以外的座標中傳送捲動事件,請注意,這些值可能會非同步變更,因此建議您擷取錯誤並加以忽略。(請注意,0, 0 通常不會超出範圍,因此可以安全地使用這些元素,向使用者提示權限要求)。

縮放

您可以透過下列 CaptureController 途徑,與擷取的分頁的縮放等級互動:

  • getSupportedZoomLevels() 會傳回瀏覽器支援的縮放等級清單,以「預設縮放等級」的百分比表示,該等級的定義為 100%。這個清單是單調遞增的,且包含值 100。
  • getZoomLevel() 會傳回分頁的目前縮放等級。
  • setZoomLevel() 會將分頁的縮放等級設為 getSupportedZoomLevels() 中的任何整數值,並在成功時傳回承諾。請注意,在擷取工作階段結束時,系統不會重設縮放等級。
  • oncapturedzoomlevelchange 可讓您監聽已擷取的分頁縮放等級變更,因為使用者可能會透過擷取應用程式,或直接與已擷取的網頁互動來變更縮放等級。

setZoomLevel() 的呼叫會受到權限限制;對其他唯讀縮放方法的呼叫則是「免費」,事件監聽也是如此。

以下範例說明如何在現有擷取工作階段中,增加已擷取分頁的縮放等級:

const zoomIncreaseButton = document.getElementById('zoomInButton');

zoomIncreaseButton.addEventListener('click', async (event) => {
  const levels = CaptureController.getSupportedZoomLevels();
  const index = levels.indexOf(controller.getZoomLevel());
  const newZoomLevel = levels[Math.min(index + 1, levels.length - 1)];

  try {
    await controller.setZoomLevel(newZoomLevel);
  } catch (error) {
    // Inspect the error.
    // ...
  }
});

以下範例說明如何回應擷取的分頁縮放等級變更:

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

特徵偵測

如要確認系統是否支援傳送輪盤事件,請使用:

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

如要檢查是否支援控制縮放功能,請使用:

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

啟用已擷取的表面控制項

Captured Surface Control API 可在 Chrome 桌面版的 Captured Surface Control 旗標後方使用,並可在 chrome://flags/#captured-surface-control 啟用。

這項功能也在 Chrome 122 版開始在電腦上進行來源測試,開發人員可以為網站訪客啟用這項功能,收集真實使用者的資料。如要進一步瞭解原始試播功能及其運作方式,請參閱「開始使用原始試播」。

安全性和隱私權

"captured-surface-control" 權限政策可讓您管理擷取應用程式和嵌入的第三方 iframe 如何存取「Captured Surface Control」。如要瞭解安全性取捨,請參閱「Captured Surface Control」說明中的「隱私權和安全性考量事項」一節。

示範

您可以在 Glitch 上執行示範,體驗 Captured Surface Control 的運作方式。請務必查看原始碼

與先前 Chrome 版本的差異

以下是您應留意的幾個關於 Captured Surface Control 的重要行為差異:

  • 在 Chrome 124 以下版本中:
    • 如果授予權限,則範圍會限於與該 CaptureController 相關聯的擷取工作階段,而非擷取來源。
  • 在 Chrome 122 中:
    • getZoomLevel() 會傳回含有分頁目前縮放等級的承諾。
    • 如果使用者未授予應用程式使用權限,sendWheel() 會傳回拒絕的承諾,並附上錯誤訊息 "No permission."。在 Chrome 123 以上版本中,錯誤類型為 "NotAllowedError"
    • 無法使用 oncapturedzoomlevelchange,您可以使用 setInterval() 為這項功能提供 polyfill。

意見回饋

Chrome 團隊和網路標準社群希望瞭解您使用 Captured Surface Control 的體驗。

請提供設計相關資訊

Captured Surface Capture 是否無法正常運作?或者,您是否缺少實作想法所需的方法或屬性?如對安全性模型有任何問題或意見,在 GitHub 存放區中提出規格問題,或在現有問題中加入您的想法。

導入時發生問題?

你是否發現 Chrome 實作項目有錯誤?或者實作方式與規格不同?請前往 https://new.crbug.com 提交錯誤。請務必提供盡可能多的詳細資訊,以及重現問題的操作說明。Glitch 非常適合用於分享可重現的錯誤。