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ć obrazem w obrazie w przypadku elementów wideo w swojej witrynie. Wypróbuj to na naszym 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, wraz z wydaniem 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 ustandaryzowania interfejsu Web API, funkcji kompatybilnej z Safari, która pozwoliłaby deweloperom tworzyć i kontrolować pełne wrażenia związane z trybem obrazu w obrazie. 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 trybu 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 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, niezależnie od tego, czy jest w trybie obrazu w obrazie, czy 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 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.pictureInPictureElement
jest 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 kontakt przez telefondocument.exitPictureInPicture()
, co spowoduje, że 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ń enterpictureinpicture
i leavepictureinpicture
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 Picture-in-Picture, którymi można sterować za pomocą interfejsu Media Session API.

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 ustawiony w MediaSource jest ustawiony na +Infinity
(np. transmisja na żywo). Aby mieć pewność, że przycisk odtwarzania/wstrzymywania jest zawsze widoczny, ustaw uchwyty mediaSessionAction dla zdarzeń „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, ż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 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 wideo 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()
z interfejsem Media Session API umożliwia na przykład tworzenie okien playlist audio w Chrome 74. Zapoznaj się z oficjalną przykładową playlistą audio.

Przykłady, demonstracje i ćwiczenia z programowania
Aby wypróbować interfejs Picture-in-Picture Web API, zapoznaj się z oficjalnym przykładem obraz w obrazie.
Później pojawią się prezentacje i ćwiczenia z programowania.
Co dalej?
Najpierw sprawdź stan 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:
- Deweloperzy internetowi będą mogli dodawać niestandardowe elementy sterujące PiP.
- Udostępnimy nowy interfejs Web API, który umożliwi wyświetlanie dowolnych obiektów
HTMLElement
w oknie wyłaniającym.
Obsługa przeglądarek
Interfejs API Picture-in-Picture jest obsługiwany w przeglądarkach Chrome, Edge, Opera i Safari. Więcej informacji znajdziesz w MDN.
Zasoby
- Stan funkcji Chrome: https://www.chromestatus.com/feature/5729206566649856
- Błędy implementacji w Chrome: https://crbug.com/?q=component:Blink>Media>PictureInPicture
- Specyfikacja interfejsu Picture-in-Picture Web API: https://wicg.github.io/picture-in-picture
- Spec Issues: https://github.com/WICG/picture-in-picture/issues
- Przykład: https://googlechrome.github.io/samples/picture-in-picture/
- Nieoficjalna usługa polyfill dla funkcji Obraz w obrazie: https://github.com/gbentaieb/pip-polyfill/
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.