Videos mit der Funktion „Bild im Bild“ ansehen

François Beaufort
François Beaufort

Mit Bild im Bild (BiB) können Nutzer Videos in einem unverankerten Fenster ansehen, das immer über anderen Fenstern liegt. So können sie bei der Interaktion mit anderen Websites oder Anwendungen im Auge behalten, was sie sich ansehen.

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 das Bild-im-Bild-Verfahren über eine WebKit API hinzugefügt. Sechs Monate später spielte Chrome mit der Veröffentlichung von Android O mithilfe einer nativen Android API automatisch Bild-im-Bild-Videos auf Mobilgeräten ab. 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. Und hier sind wir.

Code eingeben

Bild im Bild aktivieren

Beginnen wir mit einem Videoelement und einer Möglichkeit, wie Nutzer damit interagieren können, z. B. einem Schaltflächenelement.

<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 auf 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 unabhängig davon, ob es sich im Bild-im-Bild-Modus handelt oder nicht, Ereignisse werden ausgelöst und Aufrufmethoden funktionieren. Sie spiegelt Statusänderungen im Bild-im-Bild-Fenster wider (z. B. Wiedergabe, Pause, Suche usw.) und es ist auch 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-im-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. Beachte, dass diese Methode auch ein Promise zurückgibt.

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

Auf „Bild im Bild“-Ereignisse warten

Betriebssysteme beschränken „Bild im Bild“ in der Regel auf ein Fenster. Daher folgt die Implementierung von 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 Event-Handlern enterpictureinpicture und leavepictureinpicture können wir die App individuell an die 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 Track“ 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 für Wiedergabe/Pause angezeigt, es sei denn, das Video spielt MediaStream-Objekte ab (z.B. getUserMedia(), getDisplayMedia(), canvas.captureStream()) oder die MediaSource-Dauer des Videos 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 Media Session-Beispiel können Sie sich in Aktion ansehen.

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.

Das folgende Beispiel zeigt, wie Breite und Höhe des Bild-im-Bild-Fensters abgerufen werden, wenn es erstellt oder seine Größe geändert 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 bei jeder Größenänderung ein teurer Vorgang ausgeführt wird. 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 mit dem neuen booleschen 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

Bei Videos, die MediaStream-Objekte wie getUserMedia(), getDisplayMedia() oder canvas.captureStream() wiedergeben, wird die Funktion „Bild im Bild“ in Chrome 71 ebenfalls unterstützt. Das bedeutet, dass Sie ein Bild-im-Bild-Fenster mit dem Webcam-Videostream des Nutzers, dem 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

Beispiele, Demos und Codelabs

Sieh dir unser offizielles Bild-im-Bild-Beispiel an, um die Picture-in-Picture-Web API auszuprobieren.

Demos und Codelabs folgen.

Weiteres Vorgehen

Sehen Sie sich zuerst auf der Seite „Implementierungsstatus“ an, welche Teile der API derzeit in Chrome und anderen Browsern implementiert sind.

Folgendes ist in Kürze zu erwarten:

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.