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

François Beaufort
François Beaufort

Bạn có thể chia sẻ thẻ, cửa sổ và màn hình trên nền tảng web bằng API Screen Capture (API Chụp ảnh màn hình). 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() sẽ cho người dùng xem trước video của khu vực được quay. Ví dụ: các ứng dụng hội nghị truyền hình thường phát trực tuyến video này cho người dùng từ xa, đồng thời cũng kết xuất video đến HTMLVideoElement cục bộ. Nhờ đó, người dùng trên máy sẽ liên tục thấy được bản xem trước của 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 bề mặt đã chụp) mới trong Chrome, cho phép ứng dụng web của bạn cuộn 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ẻ đã ghi lại (bản minh hoạ).

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

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

  • Người dùng không thể xem ứng dụng đã ghi hình 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ửa sổ cạnh nhau riêng biệt cho thẻ hội nghị truyền hình và thẻ được chia sẻ. Trên một màn hình nhỏ, điều này có thể khó khăn.
  • Người dùng khó chịu khi phải chuyển đổi giữa ứng dụng hội nghị truyền hình và nền tảng được ghi.
  • Khi không sử dụng ứng dụng hội nghị truyền hình, người dùng sẽ mất quyền sử dụng các chế độ điều khiển mà ứng dụng hội nghị truyền hình cung cấp, chẳng hạn như ứng dụng trò chuyện được nhúng, phản ứng bằ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, các chế độ điều khiển nội dung đa phương tiện và bố cục 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 kiểm soát cho những người tham gia từ xa. Điều này dẫn đến một tình huống quá quen thuộc, đó là 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.

Captured Surface Control API 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 Captured Surface Control, bạn cần thực hiện một vài bước, chẳng hạn như chụp một thẻ trình duyệt một cách rõ ràng và xin người dùng cấp quyền trước khi có thể cuộn và thu phóng thẻ đã chụp.

Chụp một thẻ trình duyệt

Hãy 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(). 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ử dụng vật thể đó để điều khiển khu vực được chụp đủ sớm.

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

Tiếp theo, hãy tạo một 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ẻ cửa sổ hoặc màn hình, thì hiện tại thì điều đó nằm ngoài phạm vi, nhưng nếu họ chọn chia sẻ một 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, các lệnh gọi khác 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, 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 biệt 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 di chuyển trên trang mà các đối tượng đó được xác định. Tuy nhiên, phiên chụp vẫn tồn tại quá trình điều hướng của trang đã chụp.

Cần có cử chỉ của người dùng để hiển thị lời nhắc cấp quyền cho người dùng. Các lệnh gọi sendWheel()setZoomLevel() chỉ yêu cầu cử chỉ của người dùng và chỉ khi lời nhắc xuất hiện. Nếu người dùng nhấp vào nút phóng to hoặc thu nhỏ trong ứng dụng web, thì cử chỉ đó của người dùng là nhất định; nhưng nếu ứng dụng muốn cung cấp quyền kiểm soát thao tác cuộn trước tiên, thì nhà phát triển cần lưu ý rằng thao tác cuộn không tạo thành cử chỉ của 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ể truyền các sự kiện bánh xe ở cường độ đã chọn trên toạ độ mà ứng dụng chọn trong khung nhìn của thẻ. Không thể phân biệt được sự kiện với ứng dụng đã ghi lại với hoạt động tương tác trực tiếp của người dùng.

Giả sử ứng dụng chụp ảnh sử dụng phần tử <video> có tên là "previewTile", thì đoạn mã sau đây sẽ cho biết cách chuyển tiếp sự kiện gửi bánh xe đế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 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.
    // ...
  }
});

Phương thức sendWheel() lấy từ điển có 2 tập hợp giá trị:

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

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

Lưu ý rằng có ba kích thước khác nhau đang được sử dụng 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 đã 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> hoàn toàn nằm trong miền của ứng dụng chụp và trình duyệt không xác định được. Kích thước của thẻ hoàn toàn nằm trong miền của trình duyệt và ứng dụng web không xác định được.

Ứng dụng web sử dụng translateCoordinates() để chuyển đổi độ lệch tương ứng với phần tử <video> thành toạ độ trong không gian toạ độ riêng của bản video. Tương tự, trình duyệt sẽ chuyển đổi giữa kích thước của 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 tại một độ bù tương ứng với kỳ vọng của ứng dụng web.

Lời hứa được 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 cho ứng dụng quyền sử dụng sendWheel().
  • Nếu ứng dụng chụp ảnh cố gắng cung cấp một sự kiện cuộn ở các toạ độ nằm ngoài [trackSettings.width, trackSettings.height]. 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 đó. (Xin lưu ý rằng 0, 0 thường không vượt quá giới hạn. Vì vậy, bạn có thể sử dụng những thành phầ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 khu vực 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 đang tăng đơn điệu 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 giá trị hứa hẹn khi thành công. Lưu ý rằng mức thu phóng không được đặt lại vào cuối phiên chụp.
  • oncapturedzoomlevelchange cho phép bạn nghe các thay đổi về mức thu phóng của một thẻ đã ghi 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 quyền; các lệnh gọi đến các phương thức thu phóng khác, chỉ đọc là "miễn phí", cũng như đang nghe sự kiện.

Ví dụ sau cho bạn thấy cách tăng mức thu phóng của thẻ đã chụp trong 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 thấy bạn phản ứng với những 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 bánh xe 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 mức 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 bề mặt đã chụp

Captured Surface Control API (API Điều khiển bề mặt được thu thập) có trong Chrome trên máy tính 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 sẽ bước vào bản dùng thử theo nguyên gốc bắt đầu từ Chrome 122 trên máy tính, nhằm cho phép nhà phát triển bật tính năng này cho khách truy cập vào trang web của họ để thu thập dữ liệu từ người dùng thực. Xem bài viết Bắt đầu sử dụng bản dùng thử theo nguyên gốc để biết thêm thông tin về bản dùng thử theo nguyên gốc và cách hoạt động.

Mức độ bảo mật và quyền riêng tư

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

Bản minh hoạ

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

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

Dưới đây là một số khác biệt chính về hành vi liên quan đến quá trình Xử lý bề mặt mà bạn cần lưu ý:

  • Trong Chrome 124 trở về trước:
    • Quyền này (nếu được cấp) sẽ thuộc phạm vi của phiên chụp được liên kết với CaptureController đó, chứ không phải nguồn gốc thu thập.
  • Trong Chrome 122:
    • getZoomLevel() trả về một kết quả hứa hẹn 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.
    • oncapturedzoomlevelchange không dùng được. Bạn có thể chèn chữ cho tính năng này bằng cách sử dụng setInterval().

Ý kiến phản hồi

Nhóm Chrome và cộng đồng tiêu chuẩn web muốn biết về trải nghiệm của bạn khi sử dụng tính năng Kiểm soát bề mặt bị thu thập.

Cho chúng tôi biết về thiết kế

Có vấn đề nào về Captured Surface Capture không hoạt động như bạn mong đợi không? Hay có thiếu phương thức hoặc thuộc tính nào mà bạn cần triển khai 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 vào vấn đề hiện tại.

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

Bạn có phát hiện thấy lỗi khi triển khai Chrome không? Hay cách triển khai có khác với quy cách không? Báo cáo 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 với hướng dẫn tái tạo. Sự cố rất hữu ích trong việc chia sẻ lỗi có thể tái tạo.