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

François Beaufort
François Beaufort

Bạn hiện đã có thể chia sẻ thẻ, cửa sổ và màn hình trên nền tảng web bằng API Chụp ảnh màn hình. Khi một ứng dụng web gọi lệnh 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 dùng getDisplayMedia() sẽ cho người dùng xem trước video của giao diện đã 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 cho người dùng từ xa, đồng thời hiển thị video đó lên một HTMLVideoElement cục bộ. Nhờ đó, người dùng cục bộ sẽ liên tục 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 bề mặt đã chụp) 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ẻ được chụp (bản minh hoạ).

Tại sao bạn nên sử dụng tính năng Kiểm soát bề mặt được chụp?

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ổ đã chụp, thì người dùng phải chuyển sang nền tảng đó và rời khỏi ứng dụng hội nghị truyền hình. Điều này đặt ra một số thách thức:

  • Người dùng không thể xem cùng lúc cả ứng dụng đã quay và video của người dùng từ xa, trừ phi họ sử dụng chế độ Hình trong hình hoặc các cửa sổ riêng biệt cạnh nhau cho thẻ hội nghị truyền hình và thẻ được chia sẻ. Trên một màn hình nhỏ hơn, điều này có thể khó khăn.
  • Người dùng không muốn chuyển đổi giữa ứng dụng hội nghị truyền hình và nền tảng ghi hình.
  • 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, lượt thể hiện cảm xúc bằng biểu tượng, thông báo về việc 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 nghe nhìn cùng 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 tình huống quá quen thuộc khi 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 (API Điều khiển bề mặt đã thu thập) giải quyết các vấn đề này.

Làm cách nào để sử dụng tính năng Kiểm soát bề mặt đã chụp?

Để sử dụng thành công tính năng kiểm soát bề mặt đã chụp, bạn cần thực hiện một vài bước, chẳng hạn như ghi lại rõ ràng một thẻ trình duyệt và yêu cầu người dùng cho phép thì mới có thể cuộn và thu phóng thẻ đã chụp.

Ghi lại 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 một đối tượng CaptureController với phiên chụp ảnh. Chúng tôi sẽ sớm sử dụng vật thể đó để kiểm soát bề mặt được 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 giao diện được chụp ở dạng phần tử <video>:

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

Hiện tại, nếu người dùng chọn chia sẻ một cửa sổ hoặc màn hình thì thao tác đó nằm ngoài phạm vi của chính sách này — nhưng nếu họ đã chọn chia sẻ một thẻ, thì chúng tôi 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 sendWheel() hoặc setZoomLevel() đầu tiên trên một đối tượng CaptureController nhất định sẽ tạo ra một lời nhắc cấp quyền. Nếu người dùng cấp quyền, người dùng sẽ được phép thực hiện thêm các lệnh gọi phương thức này trên đối tượng CaptureController đó. 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.

Lưu ý rằng các đối tượng CaptureController được liên kết duy nhất với một capture-session 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 trong phần điều hướng của trang mà chú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 của trang được thu thập.

Cần 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 cuộc gọi sendWheel()setZoomLevel() mới yêu cầu cử chỉ của người dùng và chỉ khi lời nhắc cần hiển thị. 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ỉ người dùng đó được gán cho trước; nhưng nếu ứng dụng muốn cung cấp tính năng kiểm soát cuộn trước, nhà phát triển cần lưu ý rằng thao tác cuộn không cấu thành một cử chỉ của người dùng. Một khả năng là trước tiên cung cấp cho người dùng tính năng "bắt đầu cuộn" , theo 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

Bằng cách sử dụng sendWheel(), một ứng dụng chụp ảnh có thể cung cấp các sự kiện bánh xe có cường độ đã chọn trên các toạ độ mà ứng dụng đó chọn trong khung nhìn của một thẻ. Không thể phân biệt sự kiện này với ứng dụng đã thu thập khi 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 con lăn chuột đến thẻ đã ghi hình:

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() lấy một từ điển có hai bộ giá trị:

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

Cách triển khai translateCoordinates() có thể thực hiện là:

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ó 3 kích thước khác nhau đang được phát 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> nằm hoàn toàn 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ẻ nằm hoàn toàn 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 các toạ độ trong không gian toạ độ riêng của bản nhạc video. Tương tự, trình duyệt sẽ chuyển đổi giữa kích thước của các khung hình được chụp và kích thước của thẻ, đồng thời phân phối sự kiện cuộn ở độ lệch tương ứng với yêu cầu 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 thao tác sendWheel() do trình duyệt xử lý.
  • 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 phân phối 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 và bỏ qua lỗi. (Xin lưu ý rằng 0, 0 thường sẽ không nằm ngoài giới hạn. Vì vậy, bạn có thể sử dụng các thành phần này để nhắc người dùng cấp quyền.)

Zoom (thu phóng)

Hoạt động tương tác với mức thu phóng của thẻ đã chụp được thực hiện thông qua các vùng hiển thị CaptureController sau:

  • getSupportedZoomLevels() trả về danh sách các mức thu phóng mà trình duyệt hỗ trợ, thể hiện dưới dạng tỷ lệ 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 đơ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 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 thẻ được chụp vì người dùng có thể thay đổi mức thu phóng thông qua ứng dụng chụp ảnh hoặc bằng cách 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 theo quyền; lệnh gọi đến các phương thức thu phóng chỉ đọc, chỉ đọc là "miễn phí", giống như nghe sự kiện.

Ví dụ sau đây minh hoạ cách tăng mức thu phóng của một thẻ được chụp trong phiên chụp ảnh 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 các thay đổi về mức thu phóng của một thẻ được ghi lại:

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 kiểm soát 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 kiểm soát bề mặt đã chụp

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

Tính năng này cũng sẽ bắt đầu chạy bản dùng thử theo nguyên gốc bắt đầu từ Chrome 122 trên máy tính. Theo đó, nhà phát triển có thể 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. Hãy xem bài viết Làm quen với 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 của các bản dùng thử này.

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 thu thập của bạn và iframe của bên thứ ba được nhúng có quyền truy cập vào tính năng Kiểm soát nền tảng được ghi lại. Để tìm hiểu các yếu tố đánh đổi về mặt bảo mật, hãy xem phần Các điểm 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ề Chế độ kiểm soát bề mặt đã chụp.

Bản minh hoạ

Bạn có thể sử dụng tính năng Captured Surface Control (Điều khiển bề mặt đã chụp) bằng cách chạy bản minh hoạ trên Glitch. Hãy chắc chắn kiểm tra mã nguồn.

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

Dưới đây là một số điểm khác biệt chính về hành vi liên quan đến chế độ Kiểm soát bề mặt được thu thập 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 liên kết với CaptureController đó, chứ không phải nguồn gốc chụp.
  • Trong Chrome 122:
    • getZoomLevel() trả về lời hứa với mức thu phóng hiện tại của thẻ.
    • sendWheel() trả về 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 này là "NotAllowedError" trong Chrome 123 trở lên.
    • Hiện không có miền oncapturedzoomlevelchange. Bạn có thể điền nhiều đoạn mã vào tính năng này bằng cách sử dụng setInterval().

Phản hồi

Nhóm Chrome và cộng đồng tiêu chuẩn web muốn biết ý kiến của bạn về trải nghiệm của bạn với tính năng Kiểm soát bề mặt được ghi lại.

Hãy cho chúng tôi biết về thiết kế

Có điều gì đó về tính năng Chụp ảnh bề mặt đã chụp không hoạt động như bạn mong đợi không? Hay có phương thức hay thuộc tính nào bị thiếu để triển khai ý tưởng của bạn không? Bạn có câu hỏi hoặc nhận xét về mô hình bảo mật này? Báo cáo 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ề vấn đề hiện có.

Bạn gặp vấn đề trong quá trình triển khai?

Bạn có phát hiện lỗi trong quá trình 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? Báo cáo lỗi tại https://new.crbug.com. Hãy nhớ cung cấp nhiều thông tin chi tiết nhất có thể, cùng với hướng dẫn tái sản xuất. Glitch rất hiệu quả trong việc chia sẻ lỗi có thể tái tạo.