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 dari platform yang diambil kepada pengguna. Misalnya, aplikasi konferensi video akan sering melakukan streaming video ini kepada pengguna jarak jauh sambil juga merendernya ke HTMLVideoElement lokal, sehingga pengguna lokal akan terus melihat pratinjau hal 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 ditangkap (demo).

Mengapa menggunakan Captured Surface Control?

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 mengalihkan mereka dari aplikasi konferensi video. Hal ini menghadirkan 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 berdampingan terpisah untuk tab konferensi video dan tab bersama. Pada layar yang lebih kecil, ini bisa jadi sulit.
  • Pengguna dibebani oleh kebutuhan untuk beralih antara aplikasi konferensi video ke platform yang direkam.
  • Pengguna akan kehilangan akses ke kontrol yang ditampilkan oleh aplikasi konferensi video saat mereka jauh dari kontrol 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.
  • Presenter tidak dapat mendelegasikan kontrol kepada peserta jarak jauh. Hal ini mengarah ke skenario yang sudah tidak asing lagi 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 berikut.

Bagaimana cara menggunakan Captured Surface Control?

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

Merekam tab browser

Mulailah dengan meminta pengguna memilih platform untuk dibagikan menggunakan getDisplayMedia(), dan dalam proses, kaitkan objek CaptureController dengan sesi pengambilan gambar. Kita akan segera menggunakan objek tersebut untuk mengontrol permukaan yang diambil.

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 membagikan jendela atau layar, tindakan tersebut berada di luar cakupan untuk saat ini—tetapi jika mereka memilih untuk membagikan tab, kami 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 akan menghasilkan prompt 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.

Perlu diketahui bahwa objek CaptureController secara unik terkait dengan sesi pengambilan tertentu, tidak dapat dikaitkan dengan sesi pengambilan lainnya, dan tidak tetap ada selama navigasi halaman tempat objek tersebut ditentukan. Namun, sesi tangkapan tetap ada selama 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 prompt perlu ditampilkan. Jika pengguna mengklik tombol zoom-in atau zoom-out di aplikasi web, gestur pengguna tersebut akan diberikan. Namun, jika aplikasi ingin menawarkan kontrol scroll terlebih dahulu, developer harus ingat bahwa men-scroll bukan merupakan gestur pengguna. Salah satu kemungkinannya adalah menawarkan terlebih dahulu tombol "mulai men-scroll", sesuai contoh berikut kepada pengguna:

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 sendWheel(), aplikasi perekam dapat mengirimkan peristiwa roda dengan besaran yang dipilihnya daripada koordinat pilihannya dalam area pandang tab. Peristiwa tidak dapat dibedakan dengan aplikasi yang ditangkap dari interaksi pengguna langsung.

Dengan asumsi aplikasi yang merekam menggunakan elemen <video> yang disebut "previewTile", kode berikut menunjukkan cara menyampaikan peristiwa roda ke tab yang ditangkap:

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

Metode sendWheel() mengambil kamus dengan dua set nilai:

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

Kemungkinan penerapan 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 diputar dalam kode sebelumnya:

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

Ukuran elemen <video> sepenuhnya 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 terhadap elemen <video> menjadi koordinat dalam ruang koordinat trek video itu sendiri. Browser juga akan menerjemahkan antara ukuran bingkai yang ditangkap dan ukuran tab, dan mengirim peristiwa scroll pada offset yang sesuai dengan harapan aplikasi web.

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

  • Jika sesi pengambilan gambar belum dimulai atau sudah 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 melampaui batas, sehingga aman menggunakannya untuk meminta izin pengguna.)

Zoom

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

  • getSupportedZoomLevels() menampilkan daftar tingkat zoom yang didukung oleh browser, yang diwakili sebagai persentase "tingkat zoom default", yang ditetapkan 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 apa pun yang ada di getSupportedZoomLevels(), dan menampilkan promise saat berhasil. Perhatikan bahwa tingkat zoom tidak direset pada akhir sesi pengambilan gambar.
  • oncapturedzoomlevelchange memungkinkan Anda memproses perubahan tingkat zoom pada tab yang ditangkap karena pengguna dapat mengubah tingkat zoom melalui aplikasi perekam atau interaksi langsung dengan tab yang ditangkap.

Panggilan ke setZoomLevel() dibatasi oleh izin; panggilan ke metode zoom hanya baca lainnya bersifat "gratis", begitu juga pemrosesan peristiwa.

Contoh berikut menunjukkan cara meningkatkan tingkat zoom tab yang ditangkap 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 reaksi terhadap perubahan tingkat zoom pada 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.
}

Aktifkan Kontrol Permukaan yang Ditangkap

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

Fitur ini juga akan memasuki uji coba origin yang dimulai dengan Chrome 122 di desktop, yang memungkinkan developer mengaktifkan fitur tersebut bagi pengunjung situs mereka untuk mengumpulkan data dari pengguna sungguhan. Lihat Mulai menggunakan 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 perekaman dan iframe pihak ketiga yang disematkan memiliki akses ke Captured Surface Control. Untuk memahami konsekuensi keamanan, lihat bagian Pertimbangan Privasi dan Keamanan dari penjelasan Captured Surface Control.

Demo

Anda dapat mencoba 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 Captured Surface Control yang harus Anda ketahui:

  • Di Chrome 124 dan yang lebih lama:
    • Izin—jika diberikan—mencakup sesi pengambilan yang terkait dengan CaptureController tersebut, bukan asal perekaman.
  • 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 mem-polyfill fitur ini menggunakan setInterval().

Masukan

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

Beri tahu kami tentang desainnya

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

Ada masalah dengan penerapan?

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