Cuộn và thu phóng thẻ đã chụp

François Beaufort
François Beaufort

Bạn có thể chia sẻ các thẻ, cửa sổ và màn hình trên nền tảng web bằng Screen Capture API. Khi một ứng dụng web gọi getDisplayMedia(), Chrome sẽ nhắc người dùng chia sẻ một thẻ, cửa sổ hoặc màn hình với ứng dụng web dưới dạng video MediaStreamTrack.

Nhiều ứng dụng web sử dụng getDisplayMedia() hiển thị cho người dùng bản xem trước video của nền tảng đã chụp. Ví dụ: các ứng dụng hội nghị truyền hình thường sẽ truyền trực tuyến video này đến người dùng từ xa, đồng thời hiển thị video đó cho một HTMLVideoElement cục bộ để người dùng cục bộ liên tục xem bản xem trước nội dung họ đang chia sẻ.

Tài liệu này giới thiệu Captured Surface Control API (API điều khiển giao diện đã chụp) mới trong Chrome. API này cho phép ứng dụng web của bạn cuộn một thẻ đã chụp, cũng như đọc và ghi mức thu phóng của thẻ đã chụp.

Người dùng cuộn và thu phóng một thẻ đã chụp (minh hoạ).

Tại sao nên sử dụng Captured Surface Control?

Tất cả ứng dụng hội nghị truyền hình đều gặp phải cùng một hạn chế: nếu người dùng muốn tương tác với một thẻ hoặc cửa sổ đã chụp, thì người dùng phải chuyển sang nền tảng đó, rời khỏi ứng dụng hội nghị truyền hình. Điều này gây ra một số thách thức:

  • Người dùng không thể xem ứng dụng đã chụp và video của người dùng từ xa cùng một lúc, trừ phi họ sử dụng chế độ Hình trong hình hoặc các cửa sổ cạnh nhau riêng biệt cho thẻ hội nghị video và thẻ được chia sẻ. Trên màn hình nhỏ hơn, việc này có thể khó khăn.
  • Người dùng phải chuyển đổi giữa ứng dụng hội nghị truyền hình và giao diện được chụp.
  • Người dùng mất quyền truy cập vào các chế độ điều khiển mà ứng dụng hội nghị truyền hình hiển thị khi họ rời khỏi ứng dụng đó; ví dụ: ứng dụng trò chuyện được nhúng, biểu tượng cảm xúc, thông báo về người dùng yêu cầu tham gia cuộc gọi, chế độ điều khiển bố cục và nội dung đa phương tiện cũng như các tính năng hội nghị truyền hình hữu ích khác.
  • Người trình bày không thể uỷ quyền quyền kiểm soát cho người tham gia từ xa. Điều này dẫn đến một tình huống quá quen thuộc, trong đó người dùng từ xa yêu cầu người trình bày thay đổi trang trình bày, cuộn lên và xuống một chút hoặc điều chỉnh mức thu phóng.

API Điều khiển giao diện đã chụp giải quyết những vấn đề này.

Làm cách nào để sử dụng Captured Surface Control?

Để sử dụng thành công tính năng Kiểm soát giao diện đã chụp, bạn cần thực hiện một vài bước, chẳng hạn như chụp rõ ràng một thẻ trình duyệt và nhận quyền của người dùng trước khi có thể cuộn và thu phóng thẻ đã chụp.

Ghi lại thẻ trình duyệt

Bắt đầu bằng cách nhắc người dùng chọn một nền tảng để chia sẻ bằng getDisplayMedia() và trong quá trình này, hãy liên kết đối tượng CaptureController với phiên chụp. Chúng ta sẽ sớm sử dụng đối tượng đó để kiểm soát bề mặt đã chụp.

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

Tiếp theo, hãy tạo bản xem trước cục bộ của nền tảng đã chụp ở dạng phần tử <video>:

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

Nếu người dùng chọn chia sẻ một cửa sổ hoặc màn hình, thì hiện tại, chúng ta không thể thực hiện việc này. Tuy nhiên, nếu họ chọn chia sẻ một thẻ, thì chúng ta có thể tiếp tục.

const [track] = stream.getVideoTracks();

if (track.getSettings().displaySurface !== 'browser') {
  // Bail out early if the user didn't pick a tab.
  return;
}

Lời nhắc cấp quyền

Lệnh gọi đầu tiên của sendWheel() hoặc setZoomLevel() trên một đối tượng CaptureController nhất định sẽ tạo ra lời nhắc cấp quyền. Nếu người dùng cấp quyền, thì các lệnh gọi tiếp theo của các phương thức này trên đối tượng CaptureController đó sẽ được cho phép. Nếu người dùng từ chối cấp quyền, thì lời hứa được trả về sẽ bị từ chối.

Xin lưu ý rằng các đối tượng CaptureController được liên kết riêng với một phiên chụp cụ thể, không thể liên kết với một phiên chụp khác và không tồn tại sau khi điều hướng trang nơi các đối tượng đó được xác định. Tuy nhiên, các phiên chụp vẫn tồn tại sau khi điều hướng trang đã chụp.

Bạn phải có một cử chỉ của người dùng để hiển thị lời nhắc cấp quyền cho người dùng. Chỉ các lệnh gọi sendWheel()setZoomLevel() mới yêu cầu cử chỉ của người dùng và chỉ khi cần hiển thị lời nhắc. Nếu người dùng nhấp vào nút thu phóng hoặc thu nhỏ trong ứng dụng web, thì đó là một cử chỉ người dùng nhất định; nhưng nếu ứng dụng muốn cung cấp tính năng điều khiển cuộn trước, thì nhà phát triển cần lưu ý rằng thao tác cuộn không phải là một cử chỉ người dùng. Một khả năng là trước tiên, hãy cung cấp cho người dùng nút "bắt đầu cuộn", như trong ví dụ sau:

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.
  }
});

Cuộn

Khi sử dụng sendWheel(), ứng dụng chụp ảnh có thể phân phối các sự kiện xoay theo cường độ đã chọn trên các toạ độ mà ứng dụng chọn trong khung nhìn của thẻ. Ứng dụng đã ghi lại không thể phân biệt sự kiện này với lượt tương tác trực tiếp của người dùng.

Giả sử ứng dụng chụp sử dụng phần tử <video> có tên là "previewTile", mã sau đây cho biết cách chuyển tiếp các sự kiện gửi con lăn đến thẻ đã chụp:

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.
    // ...
  }
});

Phương thức sendWheel() nhận một từ điển có hai tập hợp giá trị:

  • xy: toạ độ nơi phân phối sự kiện con lăn.
  • wheelDeltaXwheelDeltaY: độ lớn của các thao tác cuộn, tính bằng pixel, tương ứng với thao tác cuộn theo chiều ngang và chiều dọc. Xin lưu ý rằng các giá trị này được đảo ngược so với sự kiện con lăn ban đầu.

Bạn có thể triển khai translateCoordinates() như sau:

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)];
}

Xin lưu ý rằng có 3 kích thước khác nhau trong mã trước đó:

  • Kích thước của phần tử <video>.
  • Kích thước của các khung hình được chụp (được biểu thị ở đây là trackSettings.widthtrackSettings.height).
  • Kích thước của thẻ.

Kích thước của phần tử <video> nằm hoàn toàn trong phạm vi của ứng dụng chụp và trình duyệt không biết. Kích thước của thẻ nằm hoàn toàn trong phạm vi của trình duyệt và ứng dụng web không biết được.

Ứng dụng web sử dụng translateCoordinates() để dịch các độ dời tương ứng với phần tử <video> thành toạ độ trong không gian toạ độ của chính bản nhạc video. Tương tự, trình duyệt sẽ dịch giữa kích thước của các khung hình đã chụp và kích thước của thẻ, đồng thời phân phối sự kiện cuộn ở một độ dời tương ứng với kỳ vọng của ứng dụng web.

Lời hứa do sendWheel() trả về có thể bị từ chối trong các trường hợp sau:

  • Nếu phiên chụp chưa bắt đầu hoặc đã dừng, bao gồm cả việc dừng không đồng bộ trong khi trình duyệt xử lý thao tác sendWheel().
  • Nếu người dùng không cấp quyền cho ứng dụng sử dụng sendWheel().
  • Nếu ứng dụng chụp cố gắng phân phối một sự kiện cuộn ở các toạ độ nằm ngoài [trackSettings.width, trackSettings.height]. Xin lưu ý rằng các giá trị này có thể thay đổi không đồng bộ, vì vậy, bạn nên phát hiện lỗi và bỏ qua lỗi đó. (Lưu ý rằng 0, 0 thường không nằm ngoài giới hạn, vì vậy, bạn có thể yên tâm sử dụng các quyền này để nhắc người dùng cấp quyền.)

Zoom (thu phóng)

Bạn có thể tương tác với mức thu phóng của thẻ đã chụp thông qua các nền tảng CaptureController sau:

  • getSupportedZoomLevels() trả về danh sách các mức thu phóng mà trình duyệt hỗ trợ, được biểu thị dưới dạng phần trăm của "mức thu phóng mặc định", được xác định là 100%. Danh sách này tăng dần và chứa giá trị 100.
  • getZoomLevel() trả về mức thu phóng hiện tại của thẻ.
  • setZoomLevel() đặt mức thu phóng của thẻ thành bất kỳ giá trị số nguyên nào có trong getSupportedZoomLevels() và trả về một lời hứa khi thành công. Xin lưu ý rằng mức thu phóng không được đặt lại khi kết thúc phiên chụp.
  • oncapturedzoomlevelchange cho phép bạn theo dõi các thay đổi về mức thu phóng của thẻ đã chụp vì người dùng có thể thay đổi mức thu phóng thông qua ứng dụng chụp hoặc thông qua hoạt động tương tác trực tiếp với thẻ đã chụp.

Các lệnh gọi đến setZoomLevel() được kiểm soát bằng quyền; các lệnh gọi đến các phương thức thu phóng chỉ có thể đọc khác là "miễn phí", cũng như việc nghe sự kiện.

Ví dụ sau đây cho bạn biết cách tăng mức thu phóng của một thẻ đã chụp trong một phiên chụp hiện có:

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.
    // ...
  }
});

Ví dụ sau đây cho bạn biết cách phản ứng với các thay đổi về mức thu phóng của một thẻ đã chụp:

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

Phát hiện tính năng

Để kiểm tra xem tính năng gửi sự kiện con lăn có được hỗ trợ hay không, hãy sử dụng:

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

Để kiểm tra xem tính năng điều khiển thu phóng có được hỗ trợ hay không, hãy sử dụng:

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

Bật tính năng Điều khiển giao diện đã chụp

Captured Surface Control API có trong Chrome trên máy tính đằng sau cờ Captured Surface Control và có thể được bật tại chrome://flags/#captured-surface-control.

Tính năng này cũng đang bước vào giai đoạn thử nghiệm theo nguồn gốc bắt đầu từ Chrome 122 trên máy tính. Tính năng này cho phép nhà phát triển bật tính năng này cho khách truy cập trang web của họ để thu thập dữ liệu từ người dùng thực. Hãy xem bài viết Bắt đầu sử dụng thử nghiệm theo nguồn gốc để biết thêm thông tin về thử nghiệm theo nguồn gốc và cách hoạt động của thử nghiệm này.

Bảo mật và quyền riêng tư

Chính sách quyền của "captured-surface-control" cho phép bạn quản lý cách ứng dụng chụp và iframe nhúng của bên thứ ba có quyền truy cập vào Captured Surface Control. Để hiểu rõ những đánh đổi về bảo mật, hãy xem phần Những điểm cần cân nhắc về quyền riêng tư và bảo mật trong phần giải thích về tính năng Kiểm soát giao diện đã chụp.

Bản minh hoạ

Bạn có thể chơi với Captured Surface Control bằng cách chạy bản minh hoạ trên Glitch. Hãy nhớ xem mã nguồn.

Thay đổi so với các phiên bản Chrome trước

Dưới đây là một số điểm khác biệt chính về hành vi của Captured Surface Control mà bạn cần lưu ý:

  • Trong Chrome 124 trở xuống:
    • Quyền (nếu được cấp) sẽ nằm trong phạm vi phiên chụp được liên kết với CaptureController đó, chứ không phải nguồn gốc chụp.
  • Trong Chrome 122:
    • getZoomLevel() trả về một lời hứa với mức thu phóng hiện tại của thẻ.
    • sendWheel() trả về một lời hứa bị từ chối kèm theo thông báo lỗi "No permission." nếu người dùng không cấp quyền sử dụng cho ứng dụng. Loại lỗi là "NotAllowedError" trong Chrome 123 trở lên.
    • Không dùng được oncapturedzoomlevelchange. Bạn có thể polyfill tính năng này bằng setInterval().

Phản hồi

Nhóm Chrome và cộng đồng tiêu chuẩn web muốn biết trải nghiệm của bạn với tính năng Điều khiển giao diện được chụp.

Giới thiệu về thiết kế

Có điều gì về tính năng Quay video màn hình không hoạt động như mong đợi không? Hay có phương thức hoặc thuộc tính nào bị thiếu mà bạn cần để triển khai ý tưởng của mình không? Bạn có câu hỏi hoặc nhận xét về mô hình bảo mật? Gửi vấn đề về thông số kỹ thuật trên kho lưu trữ GitHub hoặc thêm ý kiến của bạn vào một vấn đề hiện có.

Bạn gặp vấn đề khi triển khai?

Bạn có phát hiện lỗi khi triển khai Chrome không? Hay cách triển khai có khác với thông số kỹ thuật không? Gửi lỗi tại https://new.crbug.com. Hãy nhớ cung cấp càng nhiều thông tin chi tiết càng tốt, cũng như hướng dẫn cách tái tạo lỗi. Glitch rất hữu ích để chia sẻ các lỗi có thể tái hiện.