捲動及縮放已擷取的分頁

François Beaufort
François Beaufort

透過 Screen Capture API,網路平台已支援分享分頁、視窗和畫面。當網頁應用程式呼叫 getDisplayMedia() 時,Chrome 會提示使用者以 MediaStreamTrack 影片的形式,將分頁、視窗或螢幕畫面與網頁應用程式分享。

許多使用 getDisplayMedia() 的網頁應用程式都會向使用者顯示所擷取介面的影片預覽畫面,舉例來說,視訊會議應用程式通常會向遠端使用者串流播放這部影片,同時發布到本機 HTMLVideoElement,讓本機使用者持續預覽自己分享的內容。

本文件介紹 Chrome 新推出的 Captured Surface Control API,這個 API 可讓網頁應用程式捲動擷取的分頁,以及讀取及寫入已擷取分頁的縮放等級。

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

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

所有視訊會議應用程式都面臨同樣的缺點:如果使用者想使用擷取的分頁或視窗,就必須切換至該介面,而不使用視訊會議應用程式。這種情況下會遇到以下問題:

  • 使用者無法同時觀看拍攝的應用程式和影片,除非他們使用子母畫面或視訊會議分頁和共用分頁並排顯示視窗。在較小的螢幕上,這可能非常困難。
  • 使用者不費吹灰之力,就能在視訊會議應用程式和擷取的介面之間進行切換。
  • 不在視訊會議期間,使用者無法再存取視訊會議應用程式提供的控制選項,例如嵌入式即時通訊應用程式、表情符號回應、要求加入通話的通知、多媒體和版面配置控制項,以及其他實用的視訊會議功能。
  • 簡報者無法將控制權委派給遠端參與者。如此一來,遠端使用者就會要求簡報者變更投影片、上下捲動畫面,或是調整縮放等級。

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

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

如要成功使用擷取的介面控制項,您必須完成幾個步驟,例如明確擷取瀏覽器分頁並取得使用者授權,才能捲動及縮放已擷取的分頁。

擷取瀏覽器分頁

首先,請提示使用者利用 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 further explained 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 通常不會超過邊界,因此可以放心透過此方式提示使用者授予權限)。

Zoom

系統會透過下列 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 的存取權。如要瞭解安全性取捨,請參閱「擷取途徑控制」說明的「隱私權和安全性注意事項」一節。

操作示範

你可以在 Glitch 上執行示範,藉此使用 Captured Surface Control 進行遊戲。請務必查看原始碼

舊版 Chrome 的變更內容

以下是有關擷取途徑控制項的一些重要行為差異,請注意:

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

意見回饋:

Chrome 團隊和網路標準社群想瞭解 Captured Surface Control 的體驗。

請向我們介紹設計

拍攝的途徑擷取功能有沒有正常運作的問題?或者您需要實現想法的方法或屬性嗎?如果您對安全性模型有任何疑問或意見,前往 GitHub 存放區提交規格問題,或是新增想法至現有的問題。

實作時遇到問題嗎?

您在執行 Chrome 時發現錯誤了嗎?或者實作與規格不同?前往 https://new.crbug.com 回報錯誤。請盡可能提供詳細資訊及重現指示。Glitch 適合用來分享可重現的錯誤,