Pencere içinde pencere özelliğini kullanarak video izleme

François Beaufort
François Beaufort

Pencere içinde pencere (PiP), kullanıcıların videoları kayan bir pencerede (her zaman diğer pencerelerin üzerinde) izlemesine olanak tanır. Böylece kullanıcılar, diğer sitelerle veya uygulamalarla etkileşimde bulunurken izledikleri videoyu gözden kaçırmaz.

Resim İçinde Resim Web API'si ile web sitenizdeki video öğeleri için Resim İçinde Resim özelliğini başlatabilir ve kontrol edebilirsiniz. Resmi Pencere içinde pencere örneğimizde deneyin.

Arka plan

Safari, Eylül 2016'da macOS Sierra'da WebKit API aracılığıyla Resim İçinde Resim desteği ekledi. Altı ay sonra, Android O'nun kullanıma sunulmasıyla Chrome, yerel bir Android API'si kullanarak mobil cihazlarda otomatik olarak Pencere İçinde Pencere videosu oynatmaya başladı. Altı ay sonra, web geliştiricilerin pencere içinde pencere özelliğiyle ilgili tüm deneyimi oluşturup kontrol etmesine olanak tanıyacak, Safari ile uyumlu bir Web API'si oluşturma ve standartlaştırma niyetimizi duyurduk. İşte buradayız.

Kodu inceleme

Pencere içinde pencere moduna girme

Basit bir video öğesi ve kullanıcının bu öğeyle etkileşime geçebileceği bir yol (ör. düğme öğesi) ile başlayalım.

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

Pencere içinde pencere özelliğini yalnızca kullanıcı hareketine yanıt olarak isteyin ve hiçbir zaman videoElement.play() tarafından döndürülen promise içinde istemeyin. Bunun nedeni, kullanıcı hareketlerinin henüz söz vermeler aracılığıyla iletilmemesidir. Bunun yerine, aşağıdaki gibi pipButtonElement üzerinde bir tıklama işleyicisinde requestPictureInPicture()'ü çağırın. Kullanıcının iki kez tıklaması durumunda ne olacağını belirlemek sizin sorumluluğunuzdadır.

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

  await videoElement.requestPictureInPicture();

  pipButtonElement.disabled = false;
});

Sözleşme çözüldüğünde Chrome, videoyu kullanıcının taşıyabileceği ve diğer pencerelerin üzerine yerleştirebileceği küçük bir pencereye dönüştürür.

İşlem tamamlandı. Tebrikler! Okumanızı durdurup hak ettiğiniz tatile çıkabilirsiniz. Maalesef bu her zaman geçerli değildir. Taahhüt aşağıdaki nedenlerden herhangi biri nedeniyle reddedilebilir:

  • Pencere içinde pencere özelliği sistem tarafından desteklenmiyor.
  • Kısıtlayıcı izin politikası nedeniyle dokümanın Pencere İçinde Pencere özelliğini kullanmasına izin verilmiyor.
  • Video meta verileri henüz yüklenmedi (videoElement.readyState === 0).
  • Video dosyası yalnızca ses içeriyor.
  • Video öğesinde yeni disablePictureInPicture özelliği bulunur.
  • Çağırma, kullanıcı hareketi etkinlik işleyicisinde (ör. düğme tıklaması) yapılmadı. Chrome 74'ten itibaren bu özellik, Pencere İçinde Pencere'de henüz bir öğe yoksa yalnızca geçerlidir.

Aşağıdaki Özellik desteği bölümünde, bu kısıtlamalara göre bir düğmenin nasıl etkinleştirileceği/devre dışı bırakılacağı gösterilmektedir.

Bu olası hataları yakalamak ve kullanıcıya neler olduğunu bildirmek için bir try...catch bloğu ekleyelim.

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

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

Video öğesi, pencere içinde pencere modunda olup olmadığına bakılmaksızın aynı şekilde çalışır: Etkinlikler tetiklenir ve çağrı yöntemleri çalışır. Pencere içinde pencere penceresindeki durum değişikliklerini (ör. oynatma, duraklatma, ilerleme vb.) yansıtır. Ayrıca durumu JavaScript'te programatik olarak değiştirmek de mümkündür.

Pencere içinde pencere modundan çıkma

Şimdi düğmemizi Pencere İçinde Pencere'ye girme ve çıkma düğmesi haline getirelim. Öncelikle, salt okunur document.pictureInPictureElement nesnesinin video öğemiz olup olmadığını kontrol etmemiz gerekir. Aksi takdirde, yukarıda belirtildiği gibi Pencere İçinde Pencere moduna girme isteği göndeririz. Aksi takdirde, document.exitPictureInPicture() çağrısı yaparak ayrılmasını isteriz. Bu durumda video, orijinal sekmede tekrar gösterilir. Bu yöntemin de bir promise döndürdüğünü unutmayın.

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

Pencere içinde pencere etkinliklerini dinleme

İşletim sistemleri genellikle pencere içinde pencere özelliğini tek bir pencereyle kısıtlar. Bu nedenle Chrome'un uygulaması da bu kalıbı izler. Bu, kullanıcıların aynı anda yalnızca bir pencere içinde pencere videosu oynatabileceği anlamına gelir. Kullanıcıların, istemediğiniz halde Pencere İçinde Pencere özelliğinden çıkmasını bekleyebilirsiniz.

Yeni enterpictureinpicture ve leavepictureinpicture etkinlik işleyicileri, deneyimi kullanıcılara göre uyarlamamıza olanak tanır. Bu, video kataloğuna göz atmaktan canlı yayın sohbetlerini öne çıkarmaya kadar her şey olabilir.

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

Pencere içinde pencereyi özelleştirme

Chrome 74, Media Session API'yi kullanarak kontrol edebileceğiniz Pencere İçinde Pencere penceresindeki oynat/duraklat, önceki parça ve sonraki parça düğmelerini destekler.

Pencere içinde pencere penceresindeki medya oynatma kontrolleri
Şekil 1. Pencere içinde pencere penceresindeki medya oynatma kontrolleri

Videoda MediaStream nesneleri (ör. getUserMedia(), getDisplayMedia(), canvas.captureStream()) oynatılmadığı veya videoda +Infinity olarak ayarlanmış bir MediaSource süresi olmadığı sürece (ör. canlı feed) varsayılan olarak, Pencere İçinde Pencere penceresinde her zaman bir oynat/duraklat düğmesi gösterilir. Oynat/duraklat düğmesinin her zaman görünür olduğundan emin olmak için hem "Oynat" hem de "Duraklat" medya etkinlikleri için Media Session işlem işleyicilerini aşağıdaki gibi ayarlayın.

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

"Önceki parça" ve "Sonraki parça" pencere denetimlerini gösterme işlemi benzerdir. Bunlar için medya oturumu işlem işleyicileri ayarladığınızda, bu işlemler Pencere İçinde Pencere penceresinde gösterilir ve bu işlemleri gerçekleştirebilirsiniz.

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

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

Bu özelliğin nasıl çalıştığını görmek için resmi Medya Oturumu örneğini deneyin.

Pencere içinde pencere penceresinin boyutunu alma

Video, Pencere İçinde Pencere moduna girip çıktığında video kalitesini ayarlamak istiyorsanız Pencere İçinde Pencere penceresinin boyutunu bilmeniz ve kullanıcı pencereyi manuel olarak yeniden boyutlandırdığında bilgilendirilmeniz gerekir.

Aşağıdaki örnekte, Pencere İçinde Pencere penceresi oluşturulurken veya yeniden boyutlandırılırken genişliğinin ve yüksekliğinin nasıl alınacağı gösterilmektedir.

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

Resim İçinde Resim pencere boyutunda yapılan her küçük değişiklik ayrı bir etkinlik tetikler. Bu da her yeniden boyutlandırmada pahalı bir işlem yapıyorsanız performans sorunlarına neden olabilir. Bu nedenle, doğrudan yeniden boyutlandırma etkinliğine bağlanmanızı önermeyiz. Diğer bir deyişle, yeniden boyutlandırma işlemi etkinlikleri çok hızlı bir şekilde tekrar tekrar tetikler. Bu sorunu gidermek için düşük hızlandırma ve yeniden başlatma gibi yaygın teknikleri kullanmanızı öneririm.

Özellik desteği

Resim İçinde Resim Web API'si desteklenmiyor olabilir. Bu nedenle, aşamalı geliştirme sağlamak için bunu algılamanız gerekir. Desteklense bile kullanıcı tarafından kapatılmış veya izin politikası tarafından devre dışı bırakılmış olabilir. Neyse ki bunu belirlemek için yeni boole document.pictureInPictureEnabled değerini kullanabilirsiniz.

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

Videonuzdaki belirli bir düğme öğesine uygulandığında, Pencere İçinde Pencere düğmenizin görünürlüğünü bu şekilde yönetebilirsiniz.

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

MediaStream video desteği

Video oynatan MediaStream nesneleri (ör. getUserMedia(), getDisplayMedia(), canvas.captureStream()), Chrome 71'de Pencere İçinde Pencere özelliğini de destekler. Bu, kullanıcının web kamerası video akışını, görüntülü video akışını veya hatta bir kanvas öğesini içeren bir Pencere İçinde Pencere penceresi gösterebileceğiniz anlamına gelir. Video öğesinin, aşağıdaki gibi küçük resim moduna girmek için DOM'a eklenmesinin gerekmediğini unutmayın.

Pencere içinde pencere penceresinde kullanıcının web kamerasını gösterme

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

// Later on, video.requestPictureInPicture();

Ekranı Pencere içinde pencere penceresinde gösterme

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

// Later on, video.requestPictureInPicture();

Tuval öğesini Pencere içinde pencere penceresinde gösterme

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

canvas.captureStream()'yi Media Session API ile birlikte kullanarak örneğin Chrome 74'te ses çalma listesi penceresi oluşturabilirsiniz. Resmi ses oynatma listesi örneğine göz atın.

Pencere içinde pencere penceresindeki ses çalma listesi
Şekil 2. Pencere içinde pencere penceresindeki ses çalma listesi

Örnekler, tanıtımlar ve codelab'ler

Pencere içinde pencere Web API'sini denemek için resmi Pencere içinde pencere örneğimize göz atın.

Ardından, tanıtımlar ve codelab'ler yayınlanacaktır.

Sırada ne var?

Öncelikle, API'nin hangi bölümlerinin şu anda Chrome ve diğer tarayıcılarda uygulandığını öğrenmek için uygulama durumu sayfasına göz atın.

Yakın gelecekte aşağıdaki değişiklikleri görebilirsiniz:

Tarayıcı desteği

Pencere İçinde Pencere Web API'si Chrome, Edge, Opera ve Safari'de desteklenir. Ayrıntılar için MDN'ye bakın.

Kaynaklar

Pencere İçinde Pencere özelliğiyle ilgili çalışmaları ve bu makale için verdikleri destek için Mounir Lamouri ve Jennifer Apacible'ye çok teşekkür ederiz. Standartlaştırma çalışmalarına katılan herkese çok teşekkür ederiz.