Videos mit der Funktion „Bild im Bild“ ansehen

François Beaufort
François Beaufort

Mit der Funktion „Bild im Bild“ können sich Nutzer Videos in einem schwebenden Fenster ansehen, das immer über anderen Fenstern angezeigt wird. So können sie sich das Video ansehen, während sie mit anderen Websites oder Apps interagieren.

Mit der Picture-in-Picture Web API kannst du Picture-in-Picture für Videoelemente auf deiner Website starten und steuern. Probieren Sie es mit unserem offiziellen Bild-im-Bild-Beispiel aus.

Hintergrund

Im September 2016 wurde in Safari in macOS Sierra die Unterstützung für Picture-in-Picture über eine WebKit API hinzugefügt. Sechs Monate später wurde in Chrome mit der Veröffentlichung von Android O mithilfe einer nativen Android API automatisch ein Bild-im-Bild-Video auf Mobilgeräten abgespielt. Sechs Monate später kündigten wir an, eine Web API zu entwickeln und zu standardisieren, die mit der Funktion von Safari kompatibel ist und es Webentwicklern ermöglicht, die gesamte Funktion „Bild-im-Bild“ zu erstellen und zu steuern. Fertig!

Code

Bild im Bild aktivieren

Beginnen wir mit einem Videoelement und einer Möglichkeit für den Nutzer, damit zu interagieren, z. B. einer Schaltfläche.

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

Fordere „Bild im Bild“ nur als Reaktion auf eine Nutzergeste an und niemals im Versprechen, das von videoElement.play() zurückgegeben wird. Das liegt daran, dass über Promises noch nicht Nutzergesten weitergegeben werden. Rufen Sie stattdessen requestPictureInPicture() in einem Klick-Handler für pipButtonElement auf, wie unten gezeigt. Es liegt in Ihrer Verantwortung, zu entscheiden, was passiert, wenn ein Nutzer zweimal klickt.

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

  await videoElement.requestPictureInPicture();

  pipButtonElement.disabled = false;
});

Wenn das Versprechen erfüllt ist, wird das Video in Chrome in ein kleines Fenster verkleinert, das der Nutzer verschieben und über andere Fenster positionieren kann.

Fertig. Gut gemacht! Sie können jetzt aufhören zu lesen und sich in den wohlverdienten Urlaub verabschieden. Leider ist das nicht immer der Fall. Das Versprechen kann aus einem der folgenden Gründe abgelehnt werden:

  • Der Bild-im-Bild-Modus wird vom System nicht unterstützt.
  • Aufgrund einer restriktiven Berechtigungsrichtlinie ist die Funktion „Bild-im-Bild“ für das Dokument nicht zulässig.
  • Die Videometadaten wurden noch nicht geladen (videoElement.readyState === 0).
  • Die Videodatei enthält nur Audioinhalte.
  • Das neue disablePictureInPicture-Attribut ist für das Videoelement vorhanden.
  • Der Aufruf wurde nicht in einem Ereignishandler für Nutzergesten (z.B. einem Klick auf eine Schaltfläche) ausgeführt. Ab Chrome 74 gilt das nur, wenn sich in der Bild-im-Bild-Ansicht noch kein Element befindet.

Im Abschnitt Funktionsunterstützung unten wird beschrieben, wie Sie eine Schaltfläche basierend auf diesen Einschränkungen aktivieren oder deaktivieren.

Fügen wir einen try...catch-Block hinzu, um diese potenziellen Fehler zu erfassen und den Nutzer darüber zu informieren, was passiert.

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

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

Das Videoelement verhält sich gleich, unabhängig davon, ob es sich im Bild-im-Bild-Modus befindet oder nicht: Ereignisse werden ausgelöst und Methodenaufrufe funktionieren. Er spiegelt Statusänderungen im Bild-im-Bild-Fenster wider (z. B. Wiedergabe, Pause, Suche). Außerdem ist es möglich, den Status programmatisch in JavaScript zu ändern.

Bild im Bild beenden

Jetzt sorgen wir dafür, dass unsere Schaltfläche den Modus „Bild im Bild“ aktiviert und deaktiviert. Wir müssen zuerst prüfen, ob das schreibgeschützte Objekt document.pictureInPictureElement unser Videoelement ist. Ist dies nicht der Fall, senden wir eine Anfrage, um wie oben beschrieben in den Bild-in-Bild-Modus zu wechseln. Andernfalls können Sie den Tab verlassen, indem Sie document.exitPictureInPicture() drücken. Das Video wird dann auf dem ursprünglichen Tab angezeigt. Beachten Sie, dass diese Methode auch ein Versprechen zurückgibt.

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

Auf „Bild im Bild“-Ereignisse warten

Betriebssysteme beschränken die Funktion „Bild-im-Bild“ in der Regel auf ein Fenster. Daher folgt die Implementierung in Chrome diesem Muster. Das bedeutet, dass Nutzer jeweils nur ein Bild-im-Bild-Video abspielen können. Du solltest damit rechnen, dass Nutzer die Funktion „Bild-im-Bild“ auch dann beenden, wenn du sie nicht explizit angefordert hast.

Mit den neuen Ereignishandlern enterpictureinpicture und leavepictureinpicture können wir die Nutzung für Nutzer anpassen. Das kann zum Beispiel das Durchsuchen eines Videokatalogs oder das Aufrufen eines Livestream-Chats sein.

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

Bild-im-Bild-Fenster anpassen

Chrome 74 unterstützt die Schaltflächen „Wiedergabe/Pause“, „Vorheriger Titel“ und „Nächster Titel“ im Bild-im-Bild-Fenster, die Sie über die Media Session API steuern können.

Steuerelemente für die Medienwiedergabe in einem Bild-im-Bild-Fenster
Abbildung 1. Steuerelemente für die Medienwiedergabe in einem Bild-im-Bild-Fenster

Standardmäßig wird im Bild-im-Bild-Fenster immer eine Schaltfläche zum Starten/Pausieren angezeigt, es sei denn, im Video werden MediaStream-Objekte (z.B. getUserMedia(), getDisplayMedia(), canvas.captureStream()) wiedergegeben oder die Dauer der MediaSource für das Video ist auf +Infinity festgelegt (z.B. Livefeed). Damit die Wiedergabe-/Pause-Schaltfläche immer sichtbar ist, legen Sie wie unten beschrieben Somesee-Media-Session-Aktions-Handler für die Medienereignisse „Wiedergabe“ und „Pause“ fest.

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

Das gilt auch für die Steuerelemente „Vorheriger Titel“ und „Nächster Titel“. Wenn Sie für diese Aktionen Media Session-Aktions-Handler festlegen, werden sie im Bild-im-Bild-Fenster angezeigt und Sie können diese Aktionen bearbeiten.

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

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

Das offizielle Beispiel für die Mediensitzung zeigt dies in der Praxis.

Größe des Bild-im-Bild-Fensters abrufen

Wenn du die Videoqualität anpassen möchtest, wenn das Video in den Bild-im-Bild-Modus wechselt oder ihn verlässt, musst du die Größe des Bild-im-Bild-Fensters kennen und benachrichtigt werden, wenn ein Nutzer die Größe des Fensters manuell ändert.

Im folgenden Beispiel wird gezeigt, wie du die Breite und Höhe des Bild-im-Bild-Fensters abrufen kannst, wenn es erstellt oder die Größe angepasst wird.

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

Ich würde dir empfehlen, nicht direkt auf das Ereignis „resize“ zu reagieren, da bei jeder kleinen Änderung an der Größe des Bild-im-Bild-Fensters ein separates Ereignis ausgelöst wird, was zu Leistungsproblemen führen kann, wenn du bei jeder Größenänderung einen ressourcenintensiven Vorgang ausführst. Mit anderen Worten: Durch die Größenänderung werden die Ereignisse sehr schnell immer wieder ausgelöst. Ich empfehle, gängige Methoden wie Drosselung und Debouncing zu verwenden, um dieses Problem zu beheben.

Funktionsunterstützung

Die Picture-in-Picture Web API wird möglicherweise nicht unterstützt. Sie müssen dies erkennen, um eine progressive Verbesserung zu ermöglichen. Auch wenn die Funktion unterstützt wird, kann sie vom Nutzer deaktiviert oder durch eine Berechtigungsrichtlinie deaktiviert werden. Glücklicherweise können Sie dies mithilfe der neuen booleschen Funktion document.pictureInPictureEnabled ermitteln.

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

So könntest du die Sichtbarkeit der Bild-im-Bild-Schaltfläche für ein bestimmtes Schaltflächenelement für ein Video festlegen.

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-Videounterstützung

MediaStream-Objekte, die Videos abspielen (z.B. getUserMedia(), getDisplayMedia(), canvas.captureStream()), unterstützen in Chrome 71 auch die Funktion „Bild-im-Bild“. Das bedeutet, dass Sie ein Bild-im-Bild-Fenster mit dem Webcam-Videostream des Nutzers, dem Display-Videostream oder sogar einem Canvas-Element anzeigen können. Das Videoelement muss nicht an das DOM angehängt sein, um den Bild-im-Bild-Modus zu aktivieren, wie unten dargestellt.

Webcam des Nutzers im Bild-im-Bild-Fenster anzeigen

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

// Later on, video.requestPictureInPicture();

Display im Bild-im-Bild-Fenster anzeigen

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

// Later on, video.requestPictureInPicture();

Canvas-Element im Bild-im-Bild-Fenster anzeigen

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

Wenn du canvas.captureStream() mit der Media Session API kombinierst, kannst du beispielsweise in Chrome 74 ein Fenster für eine Audioplaylist erstellen. Hier findest du ein offizielles Beispiel für eine Audioplaylist.

Audioplaylist in einem Bild-im-Bild-Fenster
Abbildung 2. Audioplaylist in einem Bild-im-Bild-Fenster

Samples, Demos und Codelabs

In unserem offiziellen Beispiel für die Bild-im-Bild-Funktion kannst du die Bild-im-Bild-Web API ausprobieren.

Demos und Codelabs folgen.

Weiteres Vorgehen

Sehen Sie zuerst auf der Seite zum Implementierungsstatus nach, welche Teile der API derzeit in Chrome und anderen Browsern implementiert sind.

In naher Zukunft werden folgende Änderungen eingeführt:

Unterstützte Browser

Die Picture-in-Picture Web API wird in Chrome, Edge, Opera und Safari unterstützt. Weitere Informationen finden Sie unter MDN.

Ressourcen

Vielen Dank an Mounir Lamouri und Jennifer Apacible für ihre Arbeit an der Funktion „Bild-im-Bild“ und ihre Unterstützung bei diesem Artikel. Und ein großes Dankeschön an alle, die an der Standardisierungsarbeit beteiligt waren.