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 membagikan tab, jendela, atau layar ke aplikasi web sebagai video MediaStreamTrack.

Banyak aplikasi web yang menggunakan getDisplayMedia() menampilkan pratinjau video permukaan yang diambil 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 dari apa yang mereka bagikan.

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

Pengguna men-scroll dan memperbesar tab yang diambil (demo).

Mengapa menggunakan Kontrol Permukaan yang Diambil?

Semua aplikasi konferensi video memiliki kekurangan yang sama: jika pengguna ingin berinteraksi dengan tab atau jendela yang direkam, pengguna harus beralih ke platform tersebut, sehingga 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 jika mereka menggunakan Picture-in-Picture atau jendela terpisah berdampingan untuk tab konferensi video dan tab bersama. Pada layar yang lebih kecil, hal ini mungkin sulit dilakukan.
  • Pengguna terbebani dengan kebutuhan untuk beralih antara aplikasi konferensi video dan platform yang direkam.
  • Pengguna kehilangan akses ke kontrol yang ditampilkan oleh aplikasi konferensi video saat mereka tidak berada di aplikasi tersebut; misalnya, aplikasi chat tersemat, reaksi emoji, notifikasi tentang pengguna yang meminta untuk bergabung ke panggilan, kontrol multimedia dan tata letak, serta fitur konferensi video berguna lainnya.
  • Presentator tidak dapat mendelegasikan kontrol kepada peserta jarak jauh. Hal ini menyebabkan skenario yang sudah terlalu sering terjadi, yaitu 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 Ditangkap?

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

Mengambil gambar tab browser

Mulai dengan meminta pengguna untuk memilih platform yang akan dibagikan menggunakan getDisplayMedia(), dan dalam prosesnya, kaitkan objek CaptureController dengan sesi pengambilan. 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 platform yang diambil dalam bentuk elemen <video>:

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

Jika pengguna memilih untuk membagikan jendela atau layar, hal itu di luar cakupan untuk saat ini—tetapi jika mereka memilih untuk membagikan 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;
}

Permintaan izin

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

Perhatikan bahwa objek CaptureController secara unik dikaitkan dengan capture-session tertentu, tidak dapat dikaitkan dengan capture-session lain, dan tidak bertahan saat halaman tempat objek tersebut ditentukan dinavigasi. Namun, sesi pengambilan tetap ada setelah navigasi halaman yang diambil.

Gestur pengguna diperlukan untuk menampilkan perintah izin kepada pengguna. Hanya panggilan sendWheel() dan setZoomLevel() yang memerlukan gestur pengguna, dan hanya jika perintah perlu ditampilkan. Jika pengguna mengklik tombol zoom in atau zoom out di aplikasi web, gestur pengguna tersebut sudah pasti; tetapi jika aplikasi ingin menawarkan kontrol scroll terlebih dahulu, developer harus ingat bahwa scroll tidak merupakan gestur pengguna. Salah satu kemungkinannya adalah menawarkan tombol "mulai scroll" terlebih dahulu kepada pengguna, sesuai 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 tidak dapat dibedakan oleh aplikasi yang direkam dari interaksi pengguna langsung.

Dengan asumsi aplikasi pengambilan menggunakan elemen <video> yang disebut "previewTile", kode berikut menunjukkan cara meneruskan peristiwa roda pengiriman ke tab yang diambil:

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 dikirim.
  • wheelDeltaX dan wheelDeltaY: besarnya scroll, dalam piksel, 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 yang berbeda 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 pengambilan, 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. Browser juga akan menerjemahkan antara ukuran frame 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 aplikasi untuk menggunakan sendWheel().
  • Jika aplikasi pengambilan 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

Interaksi dengan tingkat zoom tab yang diambil dilakukan melalui platform CaptureController berikut:

  • getSupportedZoomLevels() menampilkan daftar tingkat zoom yang didukung oleh browser, yang direpresentasikan sebagai persentase dari "tingkat zoom default", yang ditentukan 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.
  • 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 oleh izin; panggilan ke metode zoom hanya baca lainnya "bebas", seperti memproses peristiwa.

Contoh berikut menunjukkan cara meningkatkan tingkat zoom tab yang diambil dalam sesi pengambilan 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 pengiriman peristiwa roda didukung, gunakan:

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

Untuk memeriksa apakah kontrol zoom didukung, gunakan:

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

Mengaktifkan Kontrol Permukaan yang Diambil

Captured Surface Control API tersedia di Chrome di desktop di balik tanda Captured Surface Control, 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 ini bagi pengunjung situs mereka untuk mengumpulkan data dari pengguna sebenarnya. Lihat Memulai uji coba origin untuk mengetahui informasi selengkapnya 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 kompromi keamanan, lihat bagian Pertimbangan Privasi dan Keamanan dalam penjelasan Kontrol Permukaan yang Direkam.

Demo

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

Perubahan dari versi Chrome sebelumnya

Berikut beberapa perbedaan perilaku utama tentang Captured Surface Control yang harus Anda ketahui:

  • Di Chrome 124 dan yang lebih lama:
    • Izin—jika diberikan—dibatasi untuk sesi pengambilan yang terkait dengan CaptureController tersebut, bukan untuk 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 penggunaan aplikasi. Jenis error-nya adalah "NotAllowedError" di Chrome 123 dan yang lebih baru.
    • oncapturedzoomlevelchange tidak tersedia. Anda dapat melakukan polyfill pada fitur ini menggunakan setInterval().

Masukan

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

Beri tahu kami tentang desain

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

Ada masalah dengan implementasinya?

Apakah Anda menemukan bug pada penerapan 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.