Videowiedergabe im mobilen Web

François Beaufort
François Beaufort

Wie schaffen Sie die beste mobile Medienerfahrung im Web? So einfach ist das. Dies hängt von der Nutzerinteraktion und der Bedeutung der Medien auf einer Webseite ab. Wenn ein Video der Hauptgrund für den Besuch eines Nutzers ist, muss das Erlebnis immersiv und wiederholbar sein.

Videowiedergabe im mobilen Web

In diesem Artikel zeige ich Ihnen, wie Sie Ihre Medienerfahrung mithilfe einer Fülle von Web-APIs schrittweise verbessern und immersiver gestalten können. Deshalb entwickeln wir einen einfachen mobilen Player mit benutzerdefinierten Steuerelementen, Vollbildwiedergabe und Hintergrundwiedergabe. Sie können das Beispiel jetzt ausprobieren und den Code in unserem GitHub-Repository finden.

Benutzerdefinierte Steuerelemente

HTML-Layout
Abbildung 1: HTML-Layout

Wie Sie sehen, ist das HTML-Layout, das wir für unseren Mediaplayer verwenden, ziemlich einfach: Ein <div>-Stammelement enthält ein <video>-Medienelement und ein <div>-untergeordnetes Element für die Videosteuerung.

Zu den Videosteuerungen, die wir später behandeln, gehören: eine Schaltfläche für die Wiedergabe/Pause, eine Schaltfläche für den Vollbildmodus, Schaltflächen zum Zurück- und Vorspulen sowie einige Elemente für die aktuelle Zeit, die Dauer und die Zeiterfassung.

<div id="videoContainer">
  <video id="video" src="file.mp4"></video>
  <div id="videoControls"></div>
</div>

Videometadaten lesen

Warten wir zuerst, bis die Videometadaten geladen sind, um die Videodauer, die aktuelle Zeit und die Fortschrittsanzeige zu initialisieren. Die Funktion secondsToTimeCode() ist eine benutzerdefinierte Dienstprogrammfunktion, die ich geschrieben habe. Sie wandelt eine Anzahl von Sekunden in einen String im Format „hh:mm:ss“ um, was in unserem Fall besser geeignet ist.

<div id="videoContainer">
  <video id="video" src="file.mp4"></video>
  <div id="videoControls">
    <strong>
      <div id="videoCurrentTime"></div>
      <div id="videoDuration"></div>
      <div id="videoProgressBar"></div>
    </strong>
  </div>
</div>
video.addEventListener('loadedmetadata', function () {
  videoDuration.textContent = secondsToTimeCode(video.duration);
  videoCurrentTime.textContent = secondsToTimeCode(video.currentTime);
  videoProgressBar.style.transform = `scaleX(${
    video.currentTime / video.duration
  })`;
});
Nur Videometadaten
Abbildung 2. Mediaplayer mit Videometadaten

Video abspielen/anhalten

Nachdem die Videometadaten geladen wurden, fügen wir die erste Schaltfläche hinzu, mit der Nutzer das Video je nach Wiedergabestatus mit video.play() und video.pause() wiedergeben und pausieren können.

<div id="videoContainer">
  <video id="video" src="file.mp4"></video>
  <div id="videoControls">
    <strong><button id="playPauseButton"></button></strong>
    <div id="videoCurrentTime"></div>
    <div id="videoDuration"></div>
    <div id="videoProgressBar"></div>
  </div>
</div>
playPauseButton.addEventListener('click', function (event) {
  event.stopPropagation();
  if (video.paused) {
    video.play();
  } else {
    video.pause();
  }
});

Anstatt die Videosteuerung im click-Ereignis-Listener anzupassen, verwenden wir die Videoereignisse play und pause. Wenn wir unsere Steuerelemente ereignisbasiert gestalten, können wir sie flexibler gestalten (wie wir später bei der Media Session API sehen werden) und sie synchron halten, wenn der Browser in die Wiedergabe eingreift. Sobald das Video abgespielt wird, wird der Status der Schaltfläche in "Pause" geändert. Wenn das Video angehalten wird, ändern wir einfach den Schaltflächenstatus in „Wiedergabe“ und zeigen die Videosteuerelemente an.

video.addEventListener('play', function () {
  playPauseButton.classList.add('playing');
});

video.addEventListener('pause', function () {
  playPauseButton.classList.remove('playing');
});

Wenn sich die Zeit, die durch das Videoattribut currentTime über das Videoereignis timeupdate geändert hat, aktualisieren wir auch unsere benutzerdefinierten Steuerelemente, sofern sie sichtbar sind.

video.addEventListener('timeupdate', function () {
  if (videoControls.classList.contains('visible')) {
    videoCurrentTime.textContent = secondsToTimeCode(video.currentTime);
    videoProgressBar.style.transform = `scaleX(${
      video.currentTime / video.duration
    })`;
  }
});

Wenn das Video zu Ende ist, ändern wir einfach den Schaltflächenstatus in „Wiedergabe“, setzen videocurrentTime wieder auf 0 und zeigen die Videosteuerelemente an. Wenn der Nutzer eine automatische Wiedergabefunktion aktiviert hat, kann auch ein anderes Video automatisch geladen werden.

video.addEventListener('ended', function () {
  playPauseButton.classList.remove('playing');
  video.currentTime = 0;
});

Zurück- und Vorspulen

Jetzt fügen wir die Schaltflächen „Zurückspringen“ und „Vorwärts springen“ hinzu, damit Nutzer Inhalte ganz einfach überspringen können.

<div id="videoContainer">
  <video id="video" src="file.mp4"></video>
  <div id="videoControls">
    <button id="playPauseButton"></button>
    <strong
      ><button id="seekForwardButton"></button>
      <button id="seekBackwardButton"></button
    ></strong>
    <div id="videoCurrentTime"></div>
    <div id="videoDuration"></div>
    <div id="videoProgressBar"></div>
  </div>
</div>
var skipTime = 10; // Time to skip in seconds

seekForwardButton.addEventListener('click', function (event) {
  event.stopPropagation();
  video.currentTime = Math.min(video.currentTime + skipTime, video.duration);
});

seekBackwardButton.addEventListener('click', function (event) {
  event.stopPropagation();
  video.currentTime = Math.max(video.currentTime - skipTime, 0);
});

Wie bereits zuvor passen wir das Video-Styling nicht in den click-Ereignis-Listenern dieser Schaltflächen an, sondern verwenden die ausgelösten seeking- und seeked-Videoereignisse, um die Videohelligkeit anzupassen. Meine benutzerdefinierte CSS-Klasse seeking ist so einfach wie filter: brightness(0);.

video.addEventListener('seeking', function () {
  video.classList.add('seeking');
});

video.addEventListener('seeked', function () {
  video.classList.remove('seeking');
});

Unten sehen Sie, was wir bisher erstellt haben. Im nächsten Abschnitt implementieren wir die Schaltfläche für den Vollbildmodus.

Vollbild

Hier nutzen wir mehrere Web-APIs, um ein perfektes und nahtloses Vollbilderlebnis zu schaffen. In diesem Beispiel sehen Sie, wie das funktioniert.

Sie müssen natürlich nicht alle verwenden. Wählen Sie einfach die aus, die für Sie sinnvoll sind, und kombinieren Sie sie, um Ihren benutzerdefinierten Ablauf zu erstellen.

Automatischen Vollbildmodus verhindern

Auf iOS-Geräten wechseln video-Elemente automatisch in den Vollbildmodus, wenn die Medienwiedergabe beginnt. Wir versuchen, die Medienwiedergabe in mobilen Browsern so weit wie möglich anzupassen und zu steuern. Daher empfehle ich, das playsinline-Attribut des video-Elements so festzulegen, dass die Wiedergabe auf dem iPhone gezwungen wird, im Inline-Modus zu laufen und nicht in den Vollbildmodus zu wechseln, wenn die Wiedergabe beginnt. Das hat keine Auswirkungen auf andere Browser.

<div id="videoContainer"></div>
  <video id="video" src="file.mp4"></video><strong>playsinline</strong></video>
  <div id="videoControls">...</div>
</div>

Vollbild bei Klick auf Schaltfläche aktivieren/deaktivieren

Da wir den automatischen Vollbildmodus verhindern, müssen wir den Vollbildmodus für das Video mit der Vollbild-API selbst verwalten. Wenn der Nutzer auf die Schaltfläche für den Vollbildmodus klickt, wird der Vollbildmodus mit document.exitFullscreen() beendet, sofern er derzeit vom Dokument verwendet wird. Andernfalls fordern Sie im Videocontainer mit der Methode requestFullscreen() (falls verfügbar) den Vollbildmodus an oder verwenden Sie für das Videoelement nur unter iOS webkitEnterFullscreen() als Fallback.

<div id="videoContainer">
  <video id="video" src="file.mp4"></video>
  <div id="videoControls">
    <button id="playPauseButton"></button>
    <button id="seekForwardButton"></button>
    <button id="seekBackwardButton"></button>
    <strong><button id="fullscreenButton"></button></strong>
    <div id="videoCurrentTime"></div>
    <div id="videoDuration"></div>
    <div id="videoProgressBar"></div>
  </div>
</div>
fullscreenButton.addEventListener('click', function (event) {
  event.stopPropagation();
  if (document.fullscreenElement) {
    document.exitFullscreen();
  } else {
    requestFullscreenVideo();
  }
});

function requestFullscreenVideo() {
  if (videoContainer.requestFullscreen) {
    videoContainer.requestFullscreen();
  } else {
    video.webkitEnterFullscreen();
  }
}

document.addEventListener('fullscreenchange', function () {
  fullscreenButton.classList.toggle('active', document.fullscreenElement);
});

Vollbildmodus bei Änderung der Bildschirmausrichtung umschalten

Wenn der Nutzer das Gerät in den Querformatmodus dreht, sollten wir intelligent vorgehen und automatisch den Vollbildmodus anfordern, um ein immersives Erlebnis zu schaffen. Dazu benötigen wir die Screen Orientation API, die noch nicht überall unterstützt wird und zu diesem Zeitpunkt in einigen Browsern noch vorangestellt ist. Das ist also unsere erste progressive Verbesserung.

So funktioniert's Sobald wir eine Änderung der Bildschirmausrichtung erkennen, fordern wir den Vollbildmodus an, wenn sich das Browserfenster im Querformat befindet, d. h., wenn seine Breite größer als seine Höhe ist. Andernfalls beenden wir den Vollbildmodus. Das ist alles.

if ('orientation' in screen) {
  screen.orientation.addEventListener('change', function () {
    // Let's request fullscreen if user switches device in landscape mode.
    if (screen.orientation.type.startsWith('landscape')) {
      requestFullscreenVideo();
    } else if (document.fullscreenElement) {
      document.exitFullscreen();
    }
  });
}

Sperrbildschirm im Querformat durch Klicken auf die Schaltfläche sperren

Da sich Videos möglicherweise besser im Querformat ansehen lassen, sollten wir den Bildschirm im Querformat sperren, wenn der Nutzer auf die Schaltfläche „Vollbild“ klickt. Wir kombinieren die bisher verwendete Screen Orientation API mit einigen Medienabfragen, um die Nutzerfreundlichkeit zu optimieren.

Der Sperrbildschirm im Querformat lässt sich genauso einfach wie screen.orientation.lock('landscape') aufrufen. Wir sollten dies jedoch nur tun, wenn sich das Gerät mit matchMedia('(orientation: portrait)') im Hochformat befindet und mit matchMedia('(max-device-width: 768px)') in einer Hand gehalten werden kann, da dies für Tabletnutzer nicht optimal wäre.

fullscreenButton.addEventListener('click', function (event) {
  event.stopPropagation();
  if (document.fullscreenElement) {
    document.exitFullscreen();
  } else {
    requestFullscreenVideo();
    <strong>lockScreenInLandscape();</strong>;
  }
});
function lockScreenInLandscape() {
  if (!('orientation' in screen)) {
    return;
  }
  // Let's force landscape mode only if device is in portrait mode and can be held in one hand.
  if (
    matchMedia('(orientation: portrait) and (max-device-width: 768px)').matches
  ) {
    screen.orientation.lock('landscape');
  }
}

Display bei Änderung der Geräteausrichtung entsperren

Möglicherweise haben Sie bemerkt, dass der gerade erstellte Sperrbildschirm nicht perfekt ist, da wir keine Änderungen der Bildschirmausrichtung erhalten, wenn das Display gesperrt ist.

Um dieses Problem zu beheben, verwenden wir die Device Orientation API, sofern verfügbar. Diese API liefert Informationen von der Hardware, die die Position und Bewegung eines Geräts im Raum misst: Gyroskop und digitaler Kompass für die Ausrichtung und Beschleunigungsmesser für die Geschwindigkeit. Wenn wir eine Änderung der Geräteausrichtung erkennen, entsperren wir den Bildschirm mit screen.orientation.unlock(), wenn der Nutzer das Gerät im Hochformat hält und der Bildschirm im Querformat gesperrt ist.

function lockScreenInLandscape() {
  if (!('orientation' in screen)) {
    return;
  }
  // Let's force landscape mode only if device is in portrait mode and can be held in one hand.
  if (matchMedia('(orientation: portrait) and (max-device-width: 768px)').matches) {
    screen.orientation.lock('landscape')
    <strong>.then(function() {
      listenToDeviceOrientationChanges();
    })</strong>;
  }
}
function listenToDeviceOrientationChanges() {
  if (!('DeviceOrientationEvent' in window)) {
    return;
  }
  var previousDeviceOrientation, currentDeviceOrientation;
  window.addEventListener(
    'deviceorientation',
    function onDeviceOrientationChange(event) {
      // event.beta represents a front to back motion of the device and
      // event.gamma a left to right motion.
      if (Math.abs(event.gamma) > 10 || Math.abs(event.beta) < 10) {
        previousDeviceOrientation = currentDeviceOrientation;
        currentDeviceOrientation = 'landscape';
        return;
      }
      if (Math.abs(event.gamma) < 10 || Math.abs(event.beta) > 10) {
        previousDeviceOrientation = currentDeviceOrientation;
        // When device is rotated back to portrait, let's unlock screen orientation.
        if (previousDeviceOrientation == 'landscape') {
          screen.orientation.unlock();
          window.removeEventListener(
            'deviceorientation',
            onDeviceOrientationChange,
          );
        }
      }
    },
  );
}

Wie Sie sehen, ist das der nahtlose Vollbildmodus, den wir gesucht haben. Sehen Sie sich das Beispiel an, um dies in Aktion zu sehen.

Hintergrundwiedergabe

Wenn Sie feststellen, dass eine Webseite oder ein Video auf der Webseite nicht mehr sichtbar ist, sollten Sie Ihre Analysen entsprechend aktualisieren. Dies kann sich auch auf die aktuelle Wiedergabe auswirken, z. B. wenn ein anderer Titel ausgewählt oder angehalten wird, oder dem Nutzer sogar benutzerdefinierte Schaltflächen angezeigt werden.

Video bei Änderung der Seitensichtbarkeit pausieren

Mit der Page Visibility API können wir die aktuelle Sichtbarkeit einer Seite ermitteln und über Änderungen der Sichtbarkeit benachrichtigt werden. Mit dem Code unten wird das Video pausiert, wenn die Seite ausgeblendet wird. Dies ist beispielsweise der Fall, wenn die Displaysperre aktiviert ist oder wenn Sie zu einem anderen Tab wechseln.

Da die meisten mobilen Browser jetzt Steuerelemente außerhalb des Browsers bieten, mit denen ein pausiertes Video fortgesetzt werden kann, empfehlen wir, dieses Verhalten nur festzulegen, wenn der Nutzer die Wiedergabe im Hintergrund zulassen darf.

document.addEventListener('visibilitychange', function () {
  // Pause video when page is hidden.
  if (document.hidden) {
    video.pause();
  }
});

Schaltfläche zum Stummschalten bei Änderung der Sichtbarkeit des Videos anzeigen/ausblenden

Wenn Sie die neue Intersection Observer API verwenden, können Sie diese kostenlos noch detaillierter anpassen. Diese API informiert Sie, wenn ein beobachtetes Element den Darstellungsbereich des Browsers betritt oder verlässt.

Wir sollten die Schaltfläche „Stummschalten“ je nach Sichtbarkeit des Videos auf der Seite ein- oder ausblenden. Wenn ein Video wiedergegeben wird, aber derzeit nicht sichtbar ist, wird rechts unten auf der Seite eine Mini-Stummschaltungsschaltfläche angezeigt, über die Nutzer den Videoton steuern können. Mit dem Videoereignis volumechange wird der Stil der Schaltfläche zum Stummschalten aktualisiert.

<button id="muteButton"></button>
if ('IntersectionObserver' in window) {
  // Show/hide mute button based on video visibility in the page.
  function onIntersection(entries) {
    entries.forEach(function (entry) {
      muteButton.hidden = video.paused || entry.isIntersecting;
    });
  }
  var observer = new IntersectionObserver(onIntersection);
  observer.observe(video);
}

muteButton.addEventListener('click', function () {
  // Mute/unmute video on button click.
  video.muted = !video.muted;
});

video.addEventListener('volumechange', function () {
  muteButton.classList.toggle('active', video.muted);
});

Nur ein Video gleichzeitig wiedergeben

Wenn sich auf einer Seite mehr als ein Video befindet, würde ich empfehlen, nur eines der Videos abzuspielen und die anderen automatisch zu pausieren, damit Nutzer nicht mehrere Audiotracks gleichzeitig hören müssen.

// This array should be initialized once all videos have been added.
var videos = Array.from(document.querySelectorAll('video'));

videos.forEach(function (video) {
  video.addEventListener('play', pauseOtherVideosPlaying);
});

function pauseOtherVideosPlaying(event) {
  var videosToPause = videos.filter(function (video) {
    return !video.paused && video != event.target;
  });
  // Pause all other videos currently playing.
  videosToPause.forEach(function (video) {
    video.pause();
  });
}

Medienbenachrichtigungen anpassen

Mit der Media Session API kannst du Medienbenachrichtigungen auch anpassen, indem du Metadaten für das aktuell wiedergegebene Video bereitstellt. Außerdem können Sie damit medienbezogene Ereignisse verarbeiten, z. B. das Suchen oder Verfolgen von Änderungen, die von Benachrichtigungen oder Medientasten stammen können. In diesem Beispiel sehen Sie, wie das funktioniert.

Wenn in Ihrer Webanwendung Audio oder Video wiedergegeben wird, wird bereits eine Medienbenachrichtigung im Benachrichtigungs-Steuerfeld angezeigt. Unter Android versucht Chrome, geeignete Informationen anzuzeigen. Dazu verwendet es den Titel des Dokuments und das größte Symbolbild, das es finden kann.

Sehen wir uns an, wie Sie diese Medienbenachrichtigung anpassen können, indem Sie mit der Media Session API einige Metadaten der Mediensitzung festlegen, z. B. Titel, Künstler, Albumname und Artwork.

playPauseButton.addEventListener('click', function(event) {
  event.stopPropagation();
  if (video.paused) {
    video.play()
    <strong>.then(function() {
      setMediaSession();
    });</strong>
  } else {
    video.pause();
  }
});
function setMediaSession() {
  if (!('mediaSession' in navigator)) {
    return;
  }
  navigator.mediaSession.metadata = new MediaMetadata({
    title: 'Never Gonna Give You Up',
    artist: 'Rick Astley',
    album: 'Whenever You Need Somebody',
    artwork: [
      {src: 'https://dummyimage.com/96x96', sizes: '96x96', type: 'image/png'},
      {
        src: 'https://dummyimage.com/128x128',
        sizes: '128x128',
        type: 'image/png',
      },
      {
        src: 'https://dummyimage.com/192x192',
        sizes: '192x192',
        type: 'image/png',
      },
      {
        src: 'https://dummyimage.com/256x256',
        sizes: '256x256',
        type: 'image/png',
      },
      {
        src: 'https://dummyimage.com/384x384',
        sizes: '384x384',
        type: 'image/png',
      },
      {
        src: 'https://dummyimage.com/512x512',
        sizes: '512x512',
        type: 'image/png',
      },
    ],
  });
}

Wenn die Wiedergabe abgeschlossen ist, müssen Sie die Mediensitzung nicht "freigeben", da die Benachrichtigung automatisch ausgeblendet wird. Beachte, dass der aktuelle navigator.mediaSession.metadata verwendet wird, wenn die Wiedergabe gestartet wird. Daher müssen Sie es aktualisieren, damit in der Medienbenachrichtigung immer relevante Informationen angezeigt werden.

Wenn Ihre Webanwendung eine Playlist enthält, können Sie den Nutzern erlauben, direkt über die Medienbenachrichtigung mithilfe der Symbole „Vorheriger Titel“ und „Nächster Titel“ durch die Playlist zu blättern.

if ('mediaSession' in navigator) {
  navigator.mediaSession.setActionHandler('previoustrack', function () {
    // User clicked "Previous Track" media notification icon.
    playPreviousVideo(); // load and play previous video
  });
  navigator.mediaSession.setActionHandler('nexttrack', function () {
    // User clicked "Next Track" media notification icon.
    playNextVideo(); // load and play next video
  });
}

Media-Aktions-Handler bleiben erhalten. Das ist dem Ereignis-Listener-Muster sehr ähnlich, mit der Ausnahme, dass beim Bearbeiten eines Ereignisses das Standardverhalten des Browsers beendet wird und dies als Signal verwendet wird, dass Ihre Webanwendung die Medienaktion unterstützt. Daher werden die Medienaktionssteuerungen nur angezeigt, wenn Sie den richtigen Action-Handler festlegen.

Übrigens: Das Rücksetzen eines Media-Action-Handlers ist genauso einfach wie die Zuweisung an null.

Mit der Media Session API können Sie Medienbenachrichtigungssymbole für „Zurückspulen“ und „Vorspulen“ anzeigen, wenn Sie die Zeitspanne steuern möchten, die übersprungen werden soll.

if ('mediaSession' in navigator) {
  let skipTime = 10; // Time to skip in seconds

  navigator.mediaSession.setActionHandler('seekbackward', function () {
    // User clicked "Seek Backward" media notification icon.
    video.currentTime = Math.max(video.currentTime - skipTime, 0);
  });
  navigator.mediaSession.setActionHandler('seekforward', function () {
    // User clicked "Seek Forward" media notification icon.
    video.currentTime = Math.min(video.currentTime + skipTime, video.duration);
  });
}

Das Symbol „Wiedergabe/Pause“ wird immer in der Medienbenachrichtigung angezeigt und die zugehörigen Ereignisse werden automatisch vom Browser verarbeitet. Sollte das Standardverhalten aus irgendeinem Grund nicht funktionieren, können Sie trotzdem die Medienereignisse „Wiedergabe“ und „Pause“ verarbeiten.

Das Tolle an der Media Session API ist, dass Medienmetadaten und -steuerelemente nicht nur im Benachrichtigungs- bzw. Infobereich angezeigt werden. Die Medienbenachrichtigung wird automatisch mit allen gekoppelten Wearables synchronisiert. Außerdem wird sie auf Sperrbildschirmen angezeigt.

Feedback