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 na wierzchu innych okien), dzięki czemu mogą oni mieć podgląd oglądanych treści, gdy korzystają z innych witryn lub aplikacji.

Za pomocą interfejsu Picture-in-Picture Web API możesz uruchamiać i sterować trybem obrazu w obrazie dla elementów wideo w swojej witrynie. Wypróbuj to na oficjalnym przykładzie 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. Sześć miesięcy później, po wydaniu Androida O, Chrome automatycznie odtwarzał filmy w trybie Picture-in-Picture na urządzeniach mobilnych, korzystając z 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 Picture-in-Picture oraz zarządzanie nimi. Gotowe!

Poznaj kod

Włączanie obrazu w obrazie

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

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

Żądanie uruchomienia funkcji obraz w obrazie należy wysyłać tylko w odpowiedzi na gest użytkownika, a nigdy w obietnicy zwracanej 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. Obietnica może zostać odrzucona 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.
  • wywołanie nie zostało wykonane w obiekcie obsługującym zdarzenie gestu użytkownika (np. kliknięcie przycisku). Od wersji 74 Chrome ta funkcja jest dostępna tylko, jeśli w trybie obrazu w obrazie nie ma ż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, przewijanie itp.). Stan można też zmienić programowo w JavaScript.

Wyłączanie trybu obrazu w obrazie

Teraz skonfigurujmy przycisk, który będzie przełączać się między włączaniem i wyłączaniem obrazu w obrazie. Najpierw musimy sprawdzić, czy obiekt tylko do odczytu document.pictureInPictureElementjest elementem wideo. Jeśli nie, wyślemy prośbę o włączenie trybu Picture-in-Picture, jak opisano powyżej. W przeciwnym razie prosimy o opuszczenie pokoju, naciskając document.exitPictureInPicture(). Wtedy film pojawi się na pierwotnej karcie. Ta metoda zwraca też obietnicę.

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

Nasłuchiwanie zdarzeń obrazu w obrazie

Systemy operacyjne zwykle ograniczają tryb obrazu w obrazie do jednego okna, więc implementacja w Chrome podąża za 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 mieć pewność, że przycisk odtwarzania/wstrzymywania jest zawsze widoczny, ustaw obie metody obsługi zdarzeń multimediów „Odtwórz” i „Wstrzymaj”, 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 wyświetlenie ich w oknie obrazu w obrazie, dzięki czemu będzie można je obsługiwać.

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

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

Aby zobaczyć, jak to działa, wypróbuj oficjalny plik z sesją multimediów.

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, dlatego musisz wykryć tę sytuację, aby zapewnić stopniowe ulepszanie. 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 przypadku konkretnego elementu przycisku w filmie możesz w ten sposób zarządzać widocznością przycisku obrazu w obrazie.

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 wyświetlacza w oknie 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, demonstracje i ćwiczenia z programowania

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

Później pojawią 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.

W najbliższej przyszłości możesz się spodziewać tych zmian:

Obsługa przeglądarek

Interfejs API Picture-in-Picture Web 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. Dziękujemy wszystkim zaangażowanym w proces standaryzacji.