Bekijk video met Picture-in-Picture

François Beaufort
François Beaufort

Met Picture-in-Picture (PiP) kunnen gebruikers video's bekijken in een zwevend venster (altijd bovenop andere vensters), zodat ze in de gaten kunnen houden wat ze bekijken terwijl ze communiceren met andere sites of applicaties.

Met de Picture-in-Picture Web API kunt u Picture-in-Picture starten en beheren voor video-elementen op uw website. Probeer het uit met ons officiële Picture-in-Picture-voorbeeld .

Achtergrond

In september 2016 voegde Safari Picture-in-Picture-ondersteuning toe via een WebKit API in macOS Sierra. Zes maanden later speelde Chrome automatisch Picture-in-Picture-video af op mobiel met de release van Android O met behulp van een native Android API . Zes maanden later kondigden we onze intentie aan om een ​​Web API te bouwen en te standaardiseren, een functie die compatibel is met die van Safari, waarmee webontwikkelaars de volledige ervaring rond Picture-in-Picture kunnen creëren en beheren. En hier zijn we dan!

Verdiep je in de code

Voer Picture-in-Picture in

Laten we eenvoudig beginnen met een video-element en een manier waarop de gebruiker ermee kan communiceren, zoals een knopelement.

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

Vraag Picture-in-Picture alleen aan als reactie op een gebruikersgebaar, en nooit in de belofte die wordt geretourneerd door videoElement.play() . Dit komt omdat beloften gebruikersgebaren nog niet propageren. Roep in plaats daarvan requestPictureInPicture() aan in een klikhandler op pipButtonElement , zoals hieronder weergegeven. Het is uw verantwoordelijkheid om af te handelen wat er gebeurt als een gebruiker twee keer klikt.

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

  await videoElement.requestPictureInPicture();

  pipButtonElement.disabled = false;
});

Wanneer de belofte wordt waargemaakt, verkleint Chrome de video tot een klein venster dat de gebruiker kan verplaatsen en over andere vensters kan plaatsen.

Je bent klaar. Geweldig werk! U kunt stoppen met lezen en gaan genieten van uw welverdiende vakantie. Helaas is dat niet altijd het geval. De belofte kan om een ​​van de volgende redenen worden afgewezen :

  • Picture-in-Picture wordt niet ondersteund door het systeem.
  • Het document mag Picture-in-Picture niet gebruiken vanwege een restrictief machtigingsbeleid .
  • Videometagegevens zijn nog niet geladen ( videoElement.readyState === 0 ).
  • Videobestand is alleen audio.
  • Het nieuwe kenmerk disablePictureInPicture is aanwezig op het video-element.
  • De oproep is niet gedaan in een gebeurtenishandler voor gebruikersgebaren (bijvoorbeeld een klik op een knop). Vanaf Chrome 74 is dit alleen van toepassing als er nog geen element in Picture-in-Picture aanwezig is.

In het gedeelte Functieondersteuning hieronder ziet u hoe u een knop kunt in-/uitschakelen op basis van deze beperkingen.

Laten we een try...catch -blok toevoegen om deze potentiële fouten vast te leggen en de gebruiker te laten weten wat er aan de hand is.

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

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

Het video-element gedraagt ​​zich hetzelfde, ongeacht of het zich in Picture-in-Picture bevindt of niet: gebeurtenissen worden geactiveerd en aanroepmethoden werken. Het weerspiegelt statusveranderingen in het Picture-in-Picture-venster (zoals afspelen, pauzeren, zoeken, enz.) en het is ook mogelijk om de status programmatisch te wijzigen in JavaScript.

Sluit Beeld-in-Beeld af

Laten we nu onze knop gebruiken om Picture-in-Picture in en uit te schakelen. We moeten eerst controleren of het alleen-lezen object document.pictureInPictureElement ons video-element is. Als dit niet het geval is, sturen we een verzoek om Picture-in-Picture in te voeren, zoals hierboven. Anders vragen we om te vertrekken door document.exitPictureInPicture() aan te roepen, wat betekent dat de video weer op het oorspronkelijke tabblad verschijnt. Merk op dat deze methode ook een belofte retourneert.

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

Luister naar Picture-in-Picture-evenementen

Besturingssystemen beperken Picture-in-Picture doorgaans tot één venster, dus de implementatie van Chrome volgt dit patroon. Dit betekent dat gebruikers slechts één Picture-in-Picture-video tegelijk kunnen afspelen. U kunt van gebruikers verwachten dat ze Picture-in-Picture afsluiten, zelfs als u er niet om hebt gevraagd.

Met de nieuwe gebeurtenishandlers enterpictureinpicture en leavepictureinpicture kunnen we de ervaring op maat maken voor gebruikers. Het kan van alles zijn, van het bladeren door een catalogus met video's tot het verschijnen van een livestream-chat.

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

Pas het Picture-in-Picture-venster aan

Chrome 74 ondersteunt knoppen voor afspelen/pauzeren, vorige track en volgende track in het Picture-in-Picture-venster dat u kunt bedienen met behulp van de Media Session API .

Bedieningselementen voor het afspelen van media in een Picture-in-Picture-venster
Figuur 1. Bedieningselementen voor het afspelen van media in een Picture-in-Picture-venster

Standaard wordt er altijd een afspeel-/pauzeknop weergegeven in het Picture-in-Picture-venster, tenzij de video MediaStream-objecten afspeelt (bijv. getUserMedia() , getDisplayMedia() , canvas.captureStream() ) of de video een MediaSource-duur heeft ingesteld tot +Infinity (bijv. livefeed). Om ervoor te zorgen dat een afspeel-/pauzeknop altijd zichtbaar is, stelt u een aantal mediasessie-actiehandlers in voor zowel de mediagebeurtenissen 'Afspelen' als 'Pauze', zoals hieronder.

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

Het weergeven van de vensterbedieningen voor "Vorige track" en "Volgende track" is vergelijkbaar. Als u actiehandlers voor mediasessies instelt, worden deze weergegeven in het Picture-in-Picture-venster en kunt u deze acties afhandelen.

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

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

Probeer het officiële voorbeeld van een mediasessie om dit in actie te zien.

Verkrijg de Picture-in-Picture-venstergrootte

Als u de videokwaliteit wilt aanpassen wanneer de video Picture-in-Picture binnenkomt en verlaat, moet u de Picture-in-Picture-venstergrootte kennen en een melding krijgen als een gebruiker het formaat van het venster handmatig aanpast.

Het onderstaande voorbeeld laat zien hoe u de breedte en hoogte van het Picture-in-Picture-venster kunt bepalen wanneer het wordt gemaakt of waarvan het formaat wordt gewijzigd.

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

Ik raad aan om niet rechtstreeks aan de resize-gebeurtenis te koppelen, omdat elke kleine wijziging in de Picture-in-Picture-venstergrootte een afzonderlijke gebeurtenis zal activeren die prestatieproblemen kan veroorzaken als je bij elke resize een dure bewerking uitvoert. Met andere woorden, de bewerking voor het wijzigen van de grootte zal de gebeurtenissen zeer snel steeds opnieuw activeren. Ik raad aan om algemene technieken zoals throttling en debouncing te gebruiken om dit probleem aan te pakken.

Ondersteuning van functies

De Picture-in-Picture Web API wordt mogelijk niet ondersteund, dus u moet dit detecteren om progressieve verbeteringen te kunnen bieden. Zelfs als het wordt ondersteund, kan het door de gebruiker worden uitgeschakeld of via een machtigingsbeleid worden uitgeschakeld . Gelukkig kun je het nieuwe booleaanse document.pictureInPictureEnabled gebruiken om dit te bepalen.

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

Toegepast op een specifiek knopelement voor een video, is dit de manier waarop u mogelijk wilt omgaan met de zichtbaarheid van uw Picture-in-Picture-knop.

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-ondersteuning

Video afspelende MediaStream-objecten (bijv. getUserMedia() , getDisplayMedia() , canvas.captureStream() ) ondersteunen ook Picture-in-Picture in Chrome 71. Dit betekent dat u een Picture-in-Picture-venster kunt weergeven dat de webcamvideostream van de gebruiker bevat, videostream of zelfs een canvaselement weergeven. Houd er rekening mee dat het video-element niet aan de DOM hoeft te worden gekoppeld om Picture-in-Picture te openen, zoals hieronder weergegeven.

Toon de webcam van de gebruiker in het Picture-in-Picture-venster

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

// Later on, video.requestPictureInPicture();

Toon weergave in Picture-in-Picture-venster

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

// Later on, video.requestPictureInPicture();

Toon het canvaselement in het Picture-in-Picture-venster

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

Door canvas.captureStream() te combineren met de Media Session API , kunt u bijvoorbeeld een venster voor een audioafspeellijst maken in Chrome 74. Bekijk het officiële voorbeeld van een audioafspeellijst .

Audioafspeellijst in een Picture-in-Picture-venster
Figuur 2. Audioafspeellijst in een Picture-in-Picture-venster

Voorbeelden, demo's en codelabs

Bekijk ons ​​officiële Picture-in-Picture-voorbeeld om de Picture-in-Picture Web API uit te proberen.

Demo's en codelabs volgen.

Wat is het volgende

Bekijk eerst de implementatiestatuspagina om te weten welke delen van de API momenteel zijn geïmplementeerd in Chrome en andere browsers.

Dit is wat je in de nabije toekomst kunt verwachten:

Browser-ondersteuning

De Picture-in-Picture Web API wordt ondersteund in Chrome, Edge, Opera en Safari. Zie MDN voor details.

Bronnen

Veel dank aan Mounir Lamouri en Jennifer Apacible voor hun werk aan Picture-in-Picture en hulp bij dit artikel. En hartelijk dank aan iedereen die betrokken is bij de standaardisatie-inspanningen .