Men-scroll dan memperbesar tab yang ditangkap

François Beaufort
François Beaufort

Berbagi tab, jendela, dan layar sudah dapat dilakukan di platform web dengan Screen Capture API. Saat aplikasi web memanggil getDisplayMedia(), Chrome akan meminta pengguna untuk berbagi tab, jendela, atau layar dengan aplikasi web sebagai video MediaStreamTrack.

Banyak aplikasi web yang menggunakan getDisplayMedia() menampilkan pratinjau video dari platform yang direkam kepada pengguna. Misalnya, aplikasi konferensi video akan sering melakukan streaming video ini ke pengguna jarak jauh sekaligus merendernya ke HTMLVideoElement lokal, sehingga pengguna lokal akan terus melihat pratinjau video yang dibagikan.

Dokumentasi ini memperkenalkan Captured Surface Control API baru di Chrome, yang memungkinkan aplikasi web Anda men-scroll tab yang direkam, serta membaca dan menulis tingkat zoom tab yang direkam.

Pengguna men-scroll dan memperbesar/memperkecil tab yang direkam (demo).

Mengapa menggunakan Kontrol Permukaan yang Diambil?

Semua aplikasi konferensi video memiliki kelemahan yang sama: jika pengguna ingin berinteraksi dengan tab atau jendela yang direkam, pengguna harus beralih ke platform tersebut, yang akan membawa mereka keluar dari aplikasi konferensi video. Hal ini menimbulkan beberapa tantangan:

  • Pengguna tidak dapat melihat aplikasi yang direkam dan video pengguna jarak jauh secara bersamaan kecuali mereka menggunakan Picture-in-Picture atau jendela terpisah berdampingan untuk tab konferensi video dan tab bersama. Pada layar yang lebih kecil, hal ini bisa jadi sulit.
  • Pengguna harus beralih antara aplikasi konferensi video dan platform yang direkam.
  • Pengguna kehilangan akses ke kontrol yang diekspos oleh aplikasi konferensi video saat mereka tidak menggunakannya; misalnya, aplikasi chat tersemat, reaksi emoji, notifikasi tentang pengguna yang meminta untuk bergabung dalam panggilan, kontrol multimedia dan tata letak, serta fitur konferensi video lainnya yang berguna.
  • Presenter tidak dapat mendelegasikan kontrol kepada peserta jarak jauh. Hal ini mengarah pada skenario yang terlalu familier di mana pengguna jarak jauh meminta presenter untuk mengubah slide, men-scroll sedikit ke atas dan ke bawah, atau menyesuaikan tingkat zoom.

Captured Surface Control API mengatasi masalah ini.

Bagaimana cara menggunakan Kontrol Permukaan yang Diambil?

Diperlukan beberapa langkah untuk berhasil menggunakan Captured Surface Control, seperti merekam tab browser secara eksplisit dan mendapatkan izin dari pengguna sebelum dapat men-scroll dan memperbesar/memperkecil tab yang direkam.

Mengambil gambar tab browser

Mulailah dengan meminta pengguna memilih platform yang akan dibagikan menggunakan getDisplayMedia(). Dalam prosesnya, kaitkan objek CaptureController dengan sesi pengambilan gambar. Kita akan menggunakan objek tersebut untuk mengontrol permukaan yang diambil sebentar lagi.

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

Selanjutnya, buat pratinjau lokal dari platform yang diambil dalam bentuk elemen <video>:

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

Jika pengguna memilih untuk berbagi jendela atau layar, itu di luar cakupan untuk saat ini—tetapi jika mereka memilih untuk berbagi tab, kita dapat melanjutkan.

const [track] = stream.getVideoTracks();

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

Dialog izin

Pemanggilan pertama sendWheel() atau setZoomLevel() pada objek CaptureController tertentu menghasilkan dialog izin. Jika pengguna memberikan izin, pemanggilan metode ini lebih lanjut pada objek CaptureController tersebut akan diizinkan. Jika pengguna menolak izin, promise yang ditampilkan akan ditolak.

Perhatikan bahwa objek CaptureController secara unik terkait dengan capture-session tertentu, tidak dapat dikaitkan dengan sesi tangkapan lainnya, dan tidak dapat bertahan saat ada navigasi halaman tempat objek tersebut ditentukan. Namun, sesi tangkapan tetap bertahan dari navigasi halaman yang diambil.

Gestur pengguna diperlukan untuk menampilkan prompt izin kepada pengguna. Hanya panggilan sendWheel() dan setZoomLevel() yang memerlukan gestur pengguna, dan hanya jika perintah perlu ditampilkan. Jika pengguna mengeklik tombol {i>zoom-in<i} atau {i>zoom-out<i} di aplikasi web, {i>gesture <i}itu akan diberikan; tetapi jika aplikasi ingin menawarkan kontrol scroll terlebih dahulu, developer harus ingat bahwa men-scroll bukan merupakan gestur pengguna. Salah satu kemungkinannya adalah menawarkan "mulai men-scroll" terlebih dahulu kepada pengguna seperti pada contoh berikut:

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

Scroll

Dengan menggunakan sendWheel(), aplikasi yang merekam dapat mengirimkan peristiwa roda dengan magnitudo yang dipilih di atas koordinat yang dipilih dalam area pandang tab. Peristiwa tersebut tidak dapat dibedakan dengan aplikasi yang diambil dari interaksi pengguna langsung.

Dengan asumsi aplikasi yang melakukan perekaman menggunakan elemen <video> yang disebut "previewTile", kode berikut menunjukkan cara menyampaikan peristiwa pengiriman roda ke tab yang direkam:

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

Metode sendWheel() menggunakan kamus dengan dua kumpulan nilai:

  • x dan y: koordinat tempat peristiwa roda akan ditayangkan.
  • wheelDeltaX dan wheelDeltaY: besaran scroll, dalam piksel, masing-masing untuk scroll horizontal dan vertikal. Perhatikan bahwa nilai ini dibalik dibandingkan dengan peristiwa roda asli.

Kemungkinan implementasi translateCoordinates() adalah:

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

Perhatikan bahwa ada tiga ukuran berbeda yang dimainkan dalam kode sebelumnya:

  • Ukuran elemen <video>.
  • Ukuran frame yang diambil (diwakili di sini sebagai trackSettings.width dan trackSettings.height).
  • Ukuran tab.

Ukuran elemen <video> sepenuhnya berada dalam domain aplikasi yang merekam, dan tidak diketahui oleh browser. Ukuran tab sepenuhnya berada dalam domain browser, dan tidak diketahui oleh aplikasi web.

Aplikasi web menggunakan translateCoordinates() untuk menerjemahkan offset relatif terhadap elemen <video> menjadi koordinat dalam ruang koordinat trek video itu sendiri. Selain itu, browser akan menerjemahkan antara ukuran bingkai yang diambil dan ukuran tab, serta mengirimkan peristiwa scroll pada offset yang sesuai dengan ekspektasi aplikasi web.

Promise yang ditampilkan oleh sendWheel() dapat ditolak dalam kasus berikut:

  • Jika sesi pengambilan belum dimulai atau telah berhenti, termasuk berhenti secara asinkron saat tindakan sendWheel() ditangani oleh browser.
  • Jika pengguna tidak memberikan izin kepada aplikasi untuk menggunakan sendWheel().
  • Jika aplikasi yang merekam mencoba mengirimkan peristiwa scroll dalam koordinat yang berada di luar [trackSettings.width, trackSettings.height]. Perhatikan bahwa nilai ini dapat berubah secara asinkron, jadi sebaiknya tangkap error dan abaikan. (Perhatikan bahwa 0, 0 biasanya tidak akan berada di luar batas, jadi aman menggunakannya untuk meminta izin pengguna.)

Zoom

Berinteraksi dengan tingkat zoom tab yang direkam dilakukan melalui platform CaptureController berikut:

  • getSupportedZoomLevels() menampilkan daftar tingkat zoom yang didukung oleh browser, yang direpresentasikan sebagai persentase "tingkat zoom default", yang didefinisikan sebagai 100%. Daftar ini meningkat secara monoton dan berisi nilai 100.
  • getZoomLevel() menampilkan tingkat zoom tab saat ini.
  • setZoomLevel() menetapkan tingkat zoom tab ke nilai bilangan bulat yang ada di getSupportedZoomLevels(), dan menampilkan promise saat berhasil. Perhatikan bahwa tingkat zoom tidak direset di akhir sesi pengambilan gambar.
  • oncapturedzoomlevelchange memungkinkan Anda memproses perubahan tingkat zoom tab yang direkam karena pengguna dapat mengubah tingkat zoom baik melalui aplikasi yang merekam maupun melalui interaksi langsung dengan tab yang direkam.

Panggilan ke setZoomLevel() dibatasi dengan izin; panggilan ke metode zoom hanya baca yang lain bersifat "gratis", seperti halnya memproses peristiwa.

Contoh berikut menampilkan cara meningkatkan tingkat zoom tab yang diambil dalam sesi pengambilan gambar yang ada:

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

Contoh berikut menunjukkan cara Anda bereaksi terhadap perubahan tingkat zoom dari tab yang diambil:

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

Deteksi fitur

Untuk memeriksa apakah peristiwa pengiriman roda didukung, gunakan:

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

Untuk memeriksa apakah mengontrol zoom didukung, gunakan:

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

Aktifkan Kontrol Permukaan yang Diambil

Captured Surface Control API tersedia di Chrome pada desktop di balik tanda Capture Surface Control API, dan dapat diaktifkan di chrome://flags/#captured-surface-control.

Fitur ini juga memasuki uji coba origin mulai Chrome 122 di desktop, yang memungkinkan developer mengaktifkan fitur tersebut bagi pengunjung situs mereka untuk mengumpulkan data dari pengguna sungguhan. Lihat Memulai uji coba origin untuk mengetahui informasi lebih lanjut tentang uji coba origin dan cara kerjanya.

Keamanan dan privasi

Kebijakan izin "captured-surface-control" memungkinkan Anda mengelola cara aplikasi yang merekam dan iframe pihak ketiga yang disematkan memiliki akses ke Captured Surface Control. Untuk memahami konsekuensi keamanan, lihat bagian Pertimbangan Privasi dan Keamanan dalam penjelasan Kontrol Permukaan yang Diambil.

Demo

Anda dapat menggunakan Captured Surface Control dengan menjalankan demo di Glitch. Pastikan untuk memeriksa kode sumbernya.

Perubahan dari Chrome versi sebelumnya

Berikut adalah beberapa perbedaan perilaku utama tentang Kontrol Permukaan yang Diambil yang harus Anda ketahui:

  • Di Chrome 124 dan yang lebih lama:
    • Izin—jika diberikan—akan tercakup dalam sesi pengambilan yang terkait dengan CaptureController tersebut, bukan asal pengambilan.
  • Di Chrome 122:
    • getZoomLevel() menampilkan promise dengan tingkat zoom tab saat ini.
    • sendWheel() menampilkan promise yang ditolak dengan pesan error "No permission." jika pengguna tidak memberikan izin untuk menggunakannya. Jenis error adalah "NotAllowedError" di Chrome 123 dan yang lebih baru.
    • oncapturedzoomlevelchange tidak tersedia. Anda dapat mem-polyfill fitur ini menggunakan setInterval().

Masukan

Tim Chrome dan komunitas standar web ingin mengetahui pengalaman Anda saat menggunakan Captured Surface Control.

Beri tahu kami tentang desainnya

Apakah ada sesuatu pada Captured Surface Capture yang tidak berfungsi seperti yang Anda harapkan? Atau apakah ada metode atau properti yang hilang yang diperlukan untuk menerapkan ide Anda? Ada pertanyaan atau komentar tentang model keamanan? Ajukan masalah spesifikasi di repo GitHub, atau tambahkan pendapat Anda ke masalah yang sudah ada.

Ada masalah dengan implementasinya?

Apakah Anda menemukan bug pada implementasi Chrome? Atau apakah implementasinya berbeda dengan spesifikasi? Laporkan bug di https://new.crbug.com. Pastikan untuk menyertakan detail sebanyak mungkin, serta petunjuk untuk mereproduksinya. Glitch sangat cocok untuk membagikan bug yang dapat direproduksi.