Oglądanie filmu w trybie obrazu w obrazie

François Beaufort
François Beaufort

Funkcja obraz w obrazie (PIP) umożliwia użytkownikom oglądanie filmów w pływającym oknie (zawsze nad innymi oknami), dzięki czemu mogą oni mieć oko na to, co oglądają podczas interakcji z innymi witrynami lub aplikacjami.

Za pomocą interfejsu Picture-in-Picture Web API w witrynie możesz uruchamiać i kontrolować elementy wideo w obrazie. Wypróbuj tę funkcję z oficjalną próbką obrazu w obrazie.

Tło

We wrześniu 2016 r. Safari dodało obsługę obrazu w oknie Picture-in-Picture za pomocą interfejsu WebKit API w systemie macOS Sierra. Pół roku później Chrome automatycznie odtwarzał obraz w obrazie na urządzeniach mobilnych wraz z wydaniem Androida O za pomocą natywnego interfejsu API Androida. Sześć miesięcy później ogłosiliśmy zamiar stworzenia i ujednolicenia interfejsu Web API, który będzie zgodny z Safari i umożliwi deweloperom tworzenie pełnych funkcji obrazu w obrazie oraz zarządzanie nimi. I już jesteśmy.

Poznaj kod

Włączanie trybu obrazu w obrazie

Zacznijmy od elementu wideo i sposobu interakcji z nim, np. przycisku.

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

Żądaj funkcji obraz w obrazie tylko w odpowiedzi na gest użytkownika, a nie w ramach obietnicy zwróconej przez videoElement.play(). Dzieje się tak, ponieważ obietnice nie jeszcze nie przekazują gestów użytkownika. Zamiast tego wywołaj funkcję requestPictureInPicture() w obiekcie pipButtonElement, jak pokazano poniżej. To Twoja odpowiedzialność, co się stanie, jeśli użytkownik kliknie dwukrotnie.

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

  await videoElement.requestPictureInPicture();

  pipButtonElement.disabled = false;
});

Gdy obietnica zostanie spełniona, Chrome zminimalizuje film do małego okna, które użytkownik może przesuwać i umieszczać nad innymi oknami.

Gotowe. Brawo! Możesz już przestać czytać i udać się na zasłużony urlop. Niestety nie zawsze tak jest. Obietnice mogą zostać odrzucone z dowolnego z tych powodów:

  • System nie obsługuje obrazu w obrazie.
  • Dokument nie może korzystać z trybu obrazu w obrazie ze względu na restrykcyjne zasady dotyczące uprawnień.
  • Metadane filmu nie zostały jeszcze załadowane (videoElement.readyState === 0).
  • Plik wideo zawiera tylko dźwięk.
  • Nowy atrybut disablePictureInPicture jest obecny w elemencie wideo.
  • Połączenie nie zostało wykonane w ramach modułu obsługi zdarzeń gestów (np. kliknięcia przycisku). Od Chrome 74 dotyczy to tylko wtedy, gdy obraz w obrazie nie zawiera jeszcze żadnego elementu.

W sekcji Obsługa funkcji poniżej znajdziesz instrukcje włączania i wyłączania przycisku na podstawie tych ograniczeń.

Dodajmy blok try...catch, aby rejestrować te potencjalne błędy i informować użytkownika o ich występowaniu.

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

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

Element wideo zachowuje się tak samo, gdy jest w trybie obrazu w obrazie lub nie: zdarzenia są wywoływane, a metody wywoływania działają. Odzwierciedla ona zmiany stanu w oknie obrazu w obrazie (np. odtwarzanie, wstrzymywanie, przeskakiwanie itp.). Stan można też zmienić programowo w JavaScript.

Wyłączanie trybu obrazu w obrazie

Teraz zajmijmy się włączaniem i wyłączaniem obrazu w obrazie za pomocą przełącznika. Najpierw musimy sprawdzić, czy obiekt tylko do odczytu document.pictureInPictureElementjest elementem wideo. W przeciwnym razie wyślemy W przeciwnym razie prosimy o kontakt przez telefondocument.exitPictureInPicture(), co spowoduje, że film pojawi się na pierwotnej karcie. Pamiętaj, że ta metoda zwraca też obietnicę.

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

Nasłuchiwanie zdarzeń obrazu w obrazie

W systemach operacyjnych obraz w obrazie jest zwykle ograniczony do jednego okna, więc implementacja Chrome działa zgodnie z tym wzorcem. Oznacza to, że użytkownicy mogą odtwarzać tylko jeden obraz w obrazie naraz. Użytkownicy mogą zamknąć okno Picture-in-Picture nawet wtedy, gdy nie poprosisz o to.

Nowe przetwarzacze zdarzeń enterpictureinpictureleavepictureinpicture pozwalają nam dostosować działanie aplikacji do potrzeb użytkowników. Może to być przeglądanie katalogu filmów lub wyświetlanie czatu z transmisji na żywo.

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

Dostosowywanie okna obrazu w obrazie

Chrome 74 obsługuje przyciski odtwarzania/wstrzymywania oraz poprzedniego i następnego utworu w oknie obrazu w obrazie, którym można sterować za pomocą interfejsu Media Session API.

Elementy sterujące odtwarzaniem multimediów w oknie obrazu w obrazie
Rysunek 1. Elementy sterujące odtwarzaniem multimediów w oknie obrazu w obrazie

Domyślnie w oknie obrazu w oknie zawsze jest widoczny przycisk odtwarzania/wstrzymywania, chyba że film odtwarza obiekty MediaStream (np. getUserMedia(), getDisplayMedia(), canvas.captureStream()) lub jego czas trwania MediaSource jest ustawiony na +Infinity (np. transmisja na żywo). Aby przycisk odtwarzania/wstrzymywania był zawsze widoczny, ustaw dla zdarzeń multimedialnych „Odtwarzanie” i „Wstrzymaj” moduły obsługi zdarzeń dotyczących sesji multimedialnych, jak pokazano poniżej.

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

Wyświetlanie elementów sterujących „Poprzedni utwór” i „Następny utwór” w oknie jest podobne. Ustawienie dla nich obsługiwanych działań sesji multimediów spowoduje, że będą one widoczne w oknie obrazu w oknie. Będziesz mieć możliwość obsługi tych działań.

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

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

Aby przekonać się, jak to działa, wypróbuj oficjalną próbkę sesji multimedialnej.

Rozmiar okna obrazu w obrazie

Jeśli chcesz dostosowywać jakość filmu, gdy włączasz i wyłączasz tryb obrazu w obrazie, musisz znać rozmiar okna obrazu w obrazie i otrzymywać powiadomienia, gdy użytkownik ręcznie zmienia jego rozmiar.

Przykład poniżej pokazuje, jak uzyskać szerokość i wysokość okna obrazu w obrazie po jego utworzeniu lub zmianie rozmiaru.

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

Nie zalecam bezpośredniego podłączania do zdarzenia zmiany rozmiaru, ponieważ każda niewielka zmiana rozmiaru okna Picture-in-Picture powoduje wygenerowanie osobnego zdarzenia, które może spowodować problemy z wydajnością, jeśli przy każdej zmianie rozmiaru wykonujesz kosztowną operację. Innymi słowy, operacja zmiany rozmiaru będzie bardzo szybko wywoływać zdarzenia wielokrotnie. Aby rozwiązać ten problem, zalecam użycie typowych technik, takich jak throttle i debouncing.

Obsługa funkcji

Interfejs Picture-in-Picture Web API może nie być obsługiwany, więc musisz go wykryć, aby zapewnić stopniowe ulepszenie. Nawet jeśli jest obsługiwana, może zostać wyłączona przez użytkownika lub wyłączona przez zasady dotyczące uprawnień. Na szczęście możesz użyć nowej wartości logicznej document.pictureInPictureEnabled, aby to sprawdzić.

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

W ten sposób możesz określić widoczność przycisku obrazu w obrazie, który jest stosowany do konkretnego elementu przycisku filmu.

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

Obsługa filmów MediaStream

Filmy odtwarzające obiekty MediaStream (np. getUserMedia(), getDisplayMedia(), canvas.captureStream()) obsługują też tryb obrazu w obrazie w Chrome 71. Oznacza to, że możesz wyświetlić okno obrazu w obrazie zawierające strumień wideo z kamery internetowej użytkownika, strumień wideo z wyświetlacza lub nawet element kanwy. Pamiętaj, że element wideo nie musi być dołączony do DOM, aby włączyć tryb obrazu w obrazie, jak pokazano poniżej.

Pokaż kamerę internetową użytkownika w oknie obrazu w obrazie

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

// Later on, video.requestPictureInPicture();

Wyświetlanie okna obrazu w obrazie

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

// Later on, video.requestPictureInPicture();

Pokaż element canvas w oknie obrazu w obrazie

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

Połączenie canvas.captureStream()interfejsem Media Session API umożliwia na przykład tworzenie okien playlist audio w Chrome 74. Zapoznaj się z oficjalną przykładową playlistą audio.

Playlista audio w oknie obrazu w obrazie
Rysunek 2. Playlista audio w oknie obrazu w obrazie

Przykłady, wersje demonstracyjne i ćwiczenia z programowania

Aby wypróbować interfejs Picture-in-Picture Web API, zapoznaj się z oficjalnym przykładem obraz w obrazie.

Dalej będą pojawiać się prezentacje i ćwiczenia z programowania.

Co dalej?

Najpierw sprawdź stronę ze stanem implementacji, aby dowiedzieć się, które części interfejsu API są obecnie implementowane w Chrome i innych przeglądarkach.

Oto, czego możesz się spodziewać w najbliższej przyszłości:

Obsługa przeglądarek

Interfejs Picture-in-Picture Web API jest obsługiwany w przeglądarkach Chrome, Edge, Opera i Safari. Więcej informacji znajdziesz w MDN.

Zasoby

Dziękujemy Mounirowi Lamouri i Jennifer Apacible za pracę nad trybem obrazu w obrazie oraz pomoc w przygotowaniu tego artykułu. Bardzo dziękujemy wszystkim zaangażowanym w działania na rzecz standaryzacji.