Tonton video menggunakan Picture-in-Picture

François Beaufort
François Beaufort

Picture-in-Picture (PiP) memungkinkan pengguna menonton video dalam jendela mengambang (selalu berada di atas jendela lain) sehingga mereka dapat terus melihat video yang sedang mereka tonton sambil berinteraksi dengan situs atau aplikasi lain.

Dengan Picture-in-Picture Web API, Anda dapat memulai dan mengontrol Picture-in-Picture untuk elemen video di situs Anda. Coba di contoh Picture-in-Picture resmi kami.

Latar belakang

Pada September 2016, Safari menambahkan dukungan Picture-in-Picture melalui WebKit API di macOS Sierra. Enam bulan kemudian, Chrome otomatis memutar video Picture-in-Picture di perangkat seluler dengan rilis Android O menggunakan API Android native. Enam bulan kemudian, kami mengumumkan niat kami untuk mem-build dan menstandarkan Web API, fitur yang kompatibel dengan Safari, yang akan memungkinkan developer web membuat dan mengontrol pengalaman lengkap seputar Picture-in-Picture. Dan kita sudah sampai.

Mempelajari kode

Masuk ke Mode Picture-in-Picture

Mari kita mulai dengan elemen video dan cara pengguna berinteraksi dengannya, seperti elemen tombol.

<video id="videoElement" src="https://example.com/file.mp4"></video>
<button id="pipButtonElement"></button>

Hanya minta Picture-in-Picture sebagai respons terhadap gestur pengguna, dan jangan pernah dalam promise yang ditampilkan oleh videoElement.play(). Hal ini karena promise belum menyebarkan gestur pengguna. Sebagai gantinya, panggil requestPictureInPicture() dalam pengendali klik di pipButtonElement seperti yang ditunjukkan di bawah. Anda bertanggung jawab untuk menangani apa yang terjadi jika pengguna mengklik dua kali.

pipButtonElement.addEventListener('click', async function () {
  pipButtonElement.disabled = true;

  await videoElement.requestPictureInPicture();

  pipButtonElement.disabled = false;
});

Saat promise diselesaikan, Chrome akan mengecilkan video menjadi jendela kecil yang dapat dipindahkan dan diposisikan oleh pengguna di atas jendela lain.

Selesai. Bagus! Anda dapat berhenti membaca dan pergi berlibur yang pantas Anda dapatkan. Sayangnya, hal ini tidak selalu terjadi. Promise dapat ditolak karena salah satu alasan berikut:

  • Picture-in-Picture tidak didukung oleh sistem.
  • Dokumen tidak diizinkan untuk menggunakan Picture-in-Picture karena kebijakan izin yang membatasi.
  • Metadata video belum dimuat (videoElement.readyState === 0).
  • File video hanya berisi audio.
  • Atribut disablePictureInPicture baru ada di elemen video.
  • Panggilan tidak dilakukan di pengendali peristiwa gestur pengguna (misalnya, klik tombol). Mulai Chrome 74, hal ini hanya berlaku jika belum ada elemen di Picture-in-Picture.

Bagian Dukungan fitur di bawah menunjukkan cara mengaktifkan/menonaktifkan tombol berdasarkan batasan ini.

Mari kita tambahkan blok try...catch untuk menangkap potensi error ini dan memberi tahu pengguna apa yang terjadi.

pipButtonElement.addEventListener('click', async function () {
  pipButtonElement.disabled = true;

  try {
    await videoElement.requestPictureInPicture();
  } catch (error) {
    // TODO: Show error message to user.
  } finally {
    pipButtonElement.disabled = false;
  }
});

Elemen video berperilaku sama, baik dalam Picture-in-Picture maupun tidak: peristiwa diaktifkan dan metode panggilan berfungsi. Ini mencerminkan perubahan status di jendela Picture-in-Picture (seperti putar, jeda, cari, dll.) dan juga dapat mengubah status secara terprogram di JavaScript.

Keluar dari Picture-in-Picture

Sekarang, mari kita buat tombol untuk beralih masuk dan keluar dari Picture-in-Picture. Kita harus memeriksa terlebih dahulu apakah objek hanya baca document.pictureInPictureElement adalah elemen video kita. Jika tidak, kami akan mengirim permintaan untuk masuk ke Picture-in-Picture seperti di atas. Jika tidak, kita meminta untuk keluar dengan memanggil document.exitPictureInPicture(), yang berarti video akan muncul kembali di tab asli. Perhatikan bahwa metode ini juga menampilkan promise.

    ...
    try {
      if (videoElement !== document.pictureInPictureElement) {
        await videoElement.requestPictureInPicture();
      } else {
        await document.exitPictureInPicture();
      }
    }
    ...

Memproses peristiwa Picture-in-Picture

Sistem operasi biasanya membatasi Picture-in-Picture ke satu jendela, sehingga penerapan Chrome mengikuti pola ini. Artinya, pengguna hanya dapat memutar satu video Picture-in-Picture dalam satu waktu. Anda harus mengharapkan pengguna keluar dari Picture-in-Picture meskipun Anda tidak memintanya.

Pengendali peristiwa enterpictureinpicture dan leavepictureinpicture baru memungkinkan kita menyesuaikan pengalaman untuk pengguna. Hal ini bisa berupa apa saja, mulai dari menjelajahi katalog video, hingga menampilkan chat live stream.

videoElement.addEventListener('enterpictureinpicture', function (event) {
  // Video entered Picture-in-Picture.
});

videoElement.addEventListener('leavepictureinpicture', function (event) {
  // Video left Picture-in-Picture.
  // User may have played a Picture-in-Picture video from a different page.
});

Menyesuaikan jendela Picture-in-Picture

Chrome 74 mendukung tombol putar/jeda, trek sebelumnya, dan trek berikutnya di jendela Picture-in-Picture yang dapat Anda kontrol menggunakan Media Session API.

Kontrol pemutaran media di jendela Picture-in-Picture
Gambar 1. Kontrol pemutaran media di jendela Picture-in-Picture

Secara default, tombol putar/jeda selalu ditampilkan di jendela Picture-in-Picture kecuali jika video memutar objek MediaStream (misalnya, getUserMedia(), getDisplayMedia(), canvas.captureStream()) atau video memiliki durasi MediaSource yang disetel ke +Infinity (misalnya, feed live). Untuk memastikan tombol putar/jeda selalu terlihat, tetapkan pengendali tindakan Sesi Media untuk peristiwa media "Putar" dan "Jeda" seperti di bawah.

// Show a play/pause button in the Picture-in-Picture window
navigator.mediaSession.setActionHandler('play', function () {
  // User clicked "Play" button.
});
navigator.mediaSession.setActionHandler('pause', function () {
  // User clicked "Pause" button.
});

Cara menampilkan kontrol jendela "Lagu Sebelumnya" dan "Lagu Berikutnya" serupa. Menetapkan pengendali tindakan Sesi Media untuk tindakan tersebut akan menampilkannya di jendela Picture-in-Picture dan Anda akan dapat menangani tindakan ini.

navigator.mediaSession.setActionHandler('previoustrack', function () {
  // User clicked "Previous Track" button.
});

navigator.mediaSession.setActionHandler('nexttrack', function () {
  // User clicked "Next Track" button.
});

Untuk melihat cara kerjanya, coba contoh Sesi Media resmi.

Mendapatkan ukuran jendela Picture-in-Picture

Jika ingin menyesuaikan kualitas video saat video masuk dan keluar dari Picture-in-Picture, Anda perlu mengetahui ukuran jendela Picture-in-Picture dan diberi tahu jika pengguna mengubah ukuran jendela secara manual.

Contoh di bawah menunjukkan cara mendapatkan lebar dan tinggi jendela Picture-in-Picture saat dibuat atau diubah ukurannya.

let pipWindow;

videoElement.addEventListener('enterpictureinpicture', function (event) {
  pipWindow = event.pictureInPictureWindow;
  console.log(`> Window size is ${pipWindow.width}x${pipWindow.height}`);
  pipWindow.addEventListener('resize', onPipWindowResize);
});

videoElement.addEventListener('leavepictureinpicture', function (event) {
  pipWindow.removeEventListener('resize', onPipWindowResize);
});

function onPipWindowResize(event) {
  console.log(
    `> Window size changed to ${pipWindow.width}x${pipWindow.height}`
  );
  // TODO: Change video quality based on Picture-in-Picture window size.
}

Sebaiknya jangan melakukan hook langsung ke peristiwa pengubahan ukuran karena setiap perubahan kecil yang dilakukan pada ukuran jendela Picture-in-Picture akan memicu peristiwa terpisah yang dapat menyebabkan masalah performa jika Anda melakukan operasi yang mahal pada setiap pengubahan ukuran. Dengan kata lain, operasi pengubahan ukuran akan memicu peristiwa berulang kali dengan sangat cepat. Sebaiknya gunakan teknik umum seperti throttling dan debouncing untuk mengatasi masalah ini.

Dukungan fitur

Picture-in-Picture Web API mungkin tidak didukung, jadi Anda harus mendeteksinya untuk memberikan progressive enhancement. Meskipun didukung, fitur ini dapat dinonaktifkan oleh pengguna atau dinonaktifkan oleh kebijakan izin. Untungnya, Anda dapat menggunakan document.pictureInPictureEnabled boolean baru untuk menentukannya.

if (!('pictureInPictureEnabled' in document)) {
  console.log('The Picture-in-Picture Web API is not available.');
} else if (!document.pictureInPictureEnabled) {
  console.log('The Picture-in-Picture Web API is disabled.');
}

Diterapkan ke elemen tombol tertentu untuk video, berikut cara menangani visibilitas tombol Picture-in-Picture.

if ('pictureInPictureEnabled' in document) {
  // Set button ability depending on whether Picture-in-Picture can be used.
  setPipButton();
  videoElement.addEventListener('loadedmetadata', setPipButton);
  videoElement.addEventListener('emptied', setPipButton);
} else {
  // Hide button if Picture-in-Picture is not supported.
  pipButtonElement.hidden = true;
}

function setPipButton() {
  pipButtonElement.disabled =
    videoElement.readyState === 0 ||
    !document.pictureInPictureEnabled ||
    videoElement.disablePictureInPicture;
}

Dukungan video MediaStream

Video yang memutar objek MediaStream (misalnya, getUserMedia(), getDisplayMedia(), canvas.captureStream()) juga mendukung Picture-in-Picture di Chrome 71. Artinya, Anda dapat menampilkan jendela Picture-in-Picture yang berisi streaming video webcam pengguna, streaming video tampilan, atau bahkan elemen kanvas. Perhatikan bahwa elemen video tidak harus dilampirkan ke DOM untuk memasuki Picture-in-Picture seperti yang ditunjukkan di bawah.

Menampilkan webcam pengguna di jendela Picture-in-Picture

const video = document.createElement('video');
video.muted = true;
video.srcObject = await navigator.mediaDevices.getUserMedia({video: true});
video.play();

// Later on, video.requestPictureInPicture();

Menampilkan tampilan di jendela Picture-in-Picture

const video = document.createElement('video');
video.muted = true;
video.srcObject = await navigator.mediaDevices.getDisplayMedia({video: true});
video.play();

// Later on, video.requestPictureInPicture();

Menampilkan elemen kanvas di jendela Picture-in-Picture

const canvas = document.createElement('canvas');
// Draw something to canvas.
canvas.getContext('2d').fillRect(0, 0, canvas.width, canvas.height);

const video = document.createElement('video');
video.muted = true;
video.srcObject = canvas.captureStream();
video.play();

// Later on, video.requestPictureInPicture();

Dengan menggabungkan canvas.captureStream() dengan Media Session API, Anda dapat misalnya membuat jendela playlist audio di Chrome 74. Lihat Contoh playlist audio resmi.

Playlist audio di jendela Picture-in-Picture
Gambar 2. Playlist audio di jendela Picture-in-Picture

Contoh, demo, dan codelab

Lihat contoh Picture-in-Picture resmi kami untuk mencoba Picture-in-Picture Web API.

Demo dan codelab akan menyusul.

Langkah selanjutnya

Pertama, lihat halaman status penerapan untuk mengetahui bagian API mana yang saat ini diterapkan di Chrome dan browser lainnya.

Berikut hal-hal yang akan Anda lihat dalam waktu dekat:

Dukungan browser

Picture-in-Picture Web API didukung di Chrome, Edge, Opera, dan Safari. Lihat MDN untuk mengetahui detailnya.

Resource

Terima kasih banyak kepada Mounir Lamouri dan Jennifer Apacible atas pekerjaan mereka terkait Picture-in-Picture, dan bantuan mereka dalam membuat artikel ini. Dan terima kasih banyak kepada semua orang yang terlibat dalam upaya standardisasi.