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 mengawasi apa yang mereka tonton saat 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. Cobalah 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 membuat dan menstandarkan Web API, yakni fitur yang kompatibel dengan Safari, yang memungkinkan developer web membuat dan mengontrol pengalaman penuh seputar Picture-in-Picture. Dan inilah dia!

Memahami kode

Masuk ke mode Picture-in-Picture

Mari kita mulai dengan elemen video dan cara bagi pengguna untuk 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 belum menyebarkan gestur pengguna. Sebagai gantinya, panggil requestPictureInPicture() dalam pengendali klik pada pipButtonElement seperti yang ditunjukkan di bawah ini. 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;
});

Setelah promise ini selesai, Chrome akan memperkecil video menjadi jendela kecil yang dapat dipindahkan dan diletakkan pengguna di atas jendela lain.

Tugas Anda sudah selesai! Selamat! Anda bisa berhenti membaca dan menikmati liburan yang pantas. Sayangnya, hal itu 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 membatasi.
  • Metadata video belum dimuat (videoElement.readyState === 0).
  • File video hanya berupa audio.
  • Atribut disablePictureInPicture baru ada di elemen video.
  • Panggilan tidak dibuat dalam pengendali peristiwa gestur pengguna (mis., klik tombol). Mulai Chrome 74, hal ini hanya berlaku jika belum ada elemen dalam Picture-in-Picture.

Bagian Dukungan fitur di bawah ini 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 atau tidak: peristiwa diaktifkan dan metode panggilan berfungsi. Ini mencerminkan perubahan status di jendela Picture-in-Picture (seperti putar, jeda, cari, dll.) dan juga memungkinkan untuk mengubah status secara terprogram dalam JavaScript.

Keluar dari Picture-in-Picture

Sekarang, mari kita buat tombol beralih masuk dan keluar dari Picture-in-Picture. Pertama-tama, kita harus memeriksa apakah objek hanya baca document.pictureInPictureElement adalah elemen video kita. Jika tidak, kita akan mengirim permintaan untuk memasukkan Picture-in-Picture seperti di atas. Jika tidak, kita akan meminta untuk keluar dengan memanggil document.exitPictureInPicture(), yang berarti video akan muncul kembali di tab awal. Perhatikan, 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 implementasi Chrome mengikuti pola ini. Artinya, pengguna hanya dapat memutar satu video Picture-in-Picture dalam satu waktu. Anda harus memperkirakan pengguna akan keluar dari Picture-in-Picture meskipun Anda tidak memintanya.

Pengendali peristiwa enterpictureinpicture dan leavepictureinpicture baru memungkinkan kami menyesuaikan pengalaman untuk pengguna. Hal itu bisa berupa apa pun, mulai dari menelusuri katalog video hingga menampilkan chat livestream.

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 ditetapkan ke +Infinity (misalnya, feed live). Untuk memastikan tombol putar/jeda selalu terlihat, tetapkan pengendali tindakan Sesi Media somesee untuk peristiwa media "Putar" dan "Jeda" seperti di bawah ini.

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

Menampilkan kontrol jendela "Jalur Sebelumnya" dan "Jalur berikutnya" serupa. Menyetel pengendali tindakan Sesi Media untuk pengendali 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 penerapannya, 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 mengaitkan langsung ke peristiwa perubahan 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 mahal pada setiap perubahan ukuran. Dengan kata lain, operasi pengubahan ukuran akan mengaktifkan 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, sehingga Anda harus mendeteksinya untuk memberikan progressive enhancement. Meskipun didukung, aplikasi dapat dinonaktifkan oleh pengguna atau dinonaktifkan oleh kebijakan izin. Untungnya, Anda dapat menggunakan boolean document.pictureInPictureEnabled 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, beginilah cara Anda 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 masuk ke Picture-in-Picture seperti yang ditunjukkan di bawah.

Tampilkan 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();

Tampilkan 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();

Tampilkan 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 membuat jendela playlist audio di Chrome 74, misalnya. 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 mengikuti.

Langkah selanjutnya

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

Berikut ini 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.

Referensi

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