Videowiedergabe im mobilen Web

François Beaufort
François Beaufort

Wie erstellen Sie die besten mobilen Medien im Web? Ganz einfach! Alles hängt von der Nutzerinteraktion und der Bedeutung ab, die Sie den Medien auf einer Webseite beimessen. Ich denke, wir sind uns alle einig, dass Videos der Grund für den Besuch eines Nutzers sein müssen, um immer wieder das Interesse der Nutzer wecken zu können.

Videowiedergabe im mobilen Web

In diesem Artikel zeige ich Ihnen, wie Sie Ihr Medienerlebnis schrittweise verbessern und dank einer Vielzahl von Web APIs noch immersiver machen können. Aus diesem Grund entwickeln wir einen einfachen mobilen Player mit benutzerdefinierten Steuerelementen sowie Vollbild- und Hintergrundwiedergabe. Sie können das Beispiel jetzt ausprobieren und den Code in unserem GitHub-Repository suchen.

Benutzerdefinierte Steuerelemente

HTML-Layout
Abbildung 1: HTML-Layout

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

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

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

Videometadaten lesen

Warten Sie, bis die Videometadaten geladen sind, um die Videodauer und die aktuelle Zeit festzulegen und die Fortschrittsanzeige zu initialisieren. Die Funktion secondsToTimeCode() ist eine von mir geschriebene benutzerdefinierte Dienstfunktion, die eine Anzahl von Sekunden in einen String im Format „hh:mm:ss“ umwandelt, 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() abspielen 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 Videosteuerelemente im Event-Listener click anzupassen, verwenden wir die Videoereignisse play und pause. Da unsere Steuerelemente auf Ereignissen basieren, sind sie flexibler, wie wir später bei der Media Session API sehen werden. Außerdem können wir die Steuerelemente synchron halten, wenn der Browser in die Wiedergabe eingreift. Zu Beginn der Wiedergabe ändern wir den Status der Schaltfläche in „Pause“ und die Videosteuerelemente werden ausgeblendet. Wenn das Video pausiert, ändert sich einfach der Schaltflächenstatus in „Wiedergabe“ und die Videosteuerelemente werden angezeigt.

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

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

Wenn sich die vom Video-currentTime-Attribut angegebene Zeit durch das timeupdate-Videoereignis geändert hat, aktualisieren wir auch unsere benutzerdefinierten Steuerelemente, falls 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 das Video-currentTime auf 0 zurück und zeigen vorerst die Videosteuerelemente an. Wir können auch festlegen, dass ein anderes Video automatisch geladen wird, wenn der Nutzer eine Art der automatischen Wiedergabe aktiviert hat.

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

Vorwärts und rückwärts springen

Wir fahren fort und fügen die Schaltflächen „Zurückspulen“ und „Vorwärts springen“ hinzu, damit Nutzer einige Inhalte 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);
});

Anstatt den Videostil in den click-Event-Listenern dieser Schaltflächen anzupassen, verwenden wir weiterhin 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 Vollbild-Schaltfläche.

Vollbild

Hier werden verschiedene Web-APIs für eine perfekte und nahtlose Vollbilddarstellung genutzt. Sehen Sie sich das Beispiel an, um es in Aktion zu sehen.

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

Automatischen Vollbildmodus verhindern

Unter iOS wechseln video-Elemente automatisch in den Vollbildmodus, wenn die Medienwiedergabe beginnt. Da wir versuchen, unsere Mediennutzung für mobile Browser so umfassend wie möglich anzupassen und zu steuern, empfehlen wir dir, das Attribut playsinline des Elements video festzulegen, um die Inline-Wiedergabe auf dem iPhone zu erzwingen und nicht in den Vollbildmodus zu wechseln, wenn die Wiedergabe beginnt. Beachten Sie, dass dies auf andere Browser keine Nebeneffekte hat.

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

Vollbild durch Klicken auf Schaltfläche aktivieren/deaktivieren

Da wir den automatischen Vollbildmodus jetzt verhindern, müssen wir uns mit der Fullscreen API selbst um den Vollbildmodus für das Video kümmern. Wenn der Nutzer auf die Schaltfläche „Vollbild“ klickt, wird der Vollbildmodus mit document.exitFullscreen() beendet, wenn der Vollbildmodus gerade vom Dokument verwendet wird. Fordern Sie andernfalls den Vollbildmodus im Videocontainer mit der Methode requestFullscreen() an, sofern verfügbar, oder mit einem Fallback auf webkitEnterFullscreen() für das Videoelement nur unter iOS.

<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 aktivieren/deaktivieren

Wenn der Nutzer das Gerät im Querformat dreht, sollten wir dies berücksichtigen 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 in einigen Browsern noch mit Präfixen versehen ist. Es handelt sich also um unsere erste Progressive Enhancement.

In diesem Artikel finden Sie ausführliche Informationen dazu. Sobald wir feststellen, dass sich die Bildschirmausrichtung ändert, fordern wir den Vollbildmodus an, wenn sich das Browserfenster im Querformat befindet (d. h., seine Breite ist größer als seine Höhe). Wenn nicht, 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 bei Klick auf Schaltfläche

Da Videos im Querformat besser dargestellt werden können, empfiehlt es sich, den Bildschirm im Querformat zu sperren, wenn der Nutzer auf die Schaltfläche für den Vollbildmodus klickt. Wir kombinieren die zuvor verwendete Screen Orientation API mit einigen Medienabfragen, um diese Funktionen optimal zu nutzen.

Der Sperrbildschirm im Querformat ist so einfach wie das Aufrufen von screen.orientation.lock('landscape'). 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 Tablet-Nutzer keine gute Erfahrung 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');
  }
}

Bildschirm bei Änderung der Geräteausrichtung entsperren

Wie Sie vielleicht bemerkt haben, ist die soeben erstellte Sperrbildschirmfunktion nicht perfekt, da bei gesperrtem Bildschirm keine Änderungen der Bildschirmausrichtung vorgenommen werden können.

Um dieses Problem zu beheben, sollten Sie die Device Orientation API verwenden, 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 seine Geschwindigkeit. Wenn wir eine Änderung der Geräteausrichtung feststellen, wird der Bildschirm mit screen.orientation.unlock() entsperrt, 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 dies die nahtlose Vollbilddarstellung, die wir uns gewünscht haben. Sehen Sie sich dieses 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, können Sie Ihre Analysen entsprechend aktualisieren. Dies kann sich auch auf die aktuelle Wiedergabe auswirken, z. B. wenn ein anderer Titel ausgewählt, die Wiedergabe pausiert oder dem Nutzer sogar benutzerdefinierte Schaltflächen angezeigt werden.

Video bei Änderung der Sichtbarkeit der Seite pausieren

Mit der Page Viewable API können wir die aktuelle Sichtbarkeit einer Seite ermitteln und Benachrichtigungen erhalten, wenn sich die Sichtbarkeit ändert. Mit dem Code unten wird das Video pausiert, wenn die Seite ausgeblendet ist. Dies geschieht, wenn die Displaysperre aktiv ist oder wenn Sie beispielsweise den 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 erlauben darf.

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

Schaltfläche „Stummschalten“ bei Änderung der Videosichtbarkeit ein-/ausblenden

Mit der neuen Intersection Observer API können Sie Ihre Kampagnen kostenlos noch detaillierter gestalten. Diese API informiert Sie, wenn ein beobachtetes Element in den Darstellungsbereich des Browsers eintritt oder diesen verlässt.

Wir blenden eine Schaltfläche zum Stummschalten basierend auf der Sichtbarkeit des Videos auf der Seite ein bzw. aus. Wenn ein Video abgespielt wird, aber derzeit nicht sichtbar ist, wird rechts unten auf der Seite eine kleine Schaltfläche zum Stummschalten angezeigt, über die der Nutzer den Videoton steuern kann. Das Videoereignis volumechange wird verwendet, um den Stil der Schaltfläche zum Stummschalten zu aktualisieren.

<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 jeweils ein Video abspielen

Wenn sich mehr als ein Video auf einer Seite befindet, solltest du nur ein Video abspielen und die anderen automatisch anhalten, damit Nutzer nicht mehrere gleichzeitig abgespielte Audiotracks 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 auch Medienbenachrichtigungen anpassen, indem du Metadaten für das aktuell wiedergegebene Video angibst. Außerdem können Sie damit medienbezogene Ereignisse wie Such- oder Trackänderungen, die von Benachrichtigungen oder Medientasten stammen können, verarbeiten. Sehen Sie sich das Beispiel an, um dies in Aktion zu sehen.

Wenn Ihre Web-App Audio oder Video wiedergibt, sehen Sie bereits eine Medienbenachrichtigung in der Benachrichtigungsleiste. Unter Android versucht Chrome, geeignete Informationen anzuzeigen. Dazu werden der Titel des Dokuments und das größte verfügbare Symbolbild verwendet.

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

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

Nach der Wiedergabe müssen Sie die Mediensitzung nicht mehr „freigeben“, da die Benachrichtigung automatisch ausgeblendet wird. Denken Sie daran, dass der aktuelle navigator.mediaSession.metadata verwendet wird, wenn die Wiedergabe beginnt. Aus diesem Grund müssen Sie sie aktualisieren, damit in der Medienbenachrichtigung immer relevante Informationen angezeigt werden.

Wenn Ihre Webanwendung eine Playlist bereitstellt, können Sie Nutzern die Möglichkeit bieten, direkt von der Medienbenachrichtigung aus durch Ihre Playlist zu navigieren. Dafür werden einige Symbole für „Vorheriger Titel“ und „Nächster Titel“ verwendet.

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

Die Handler für Mediaaktionen bleiben bestehen. Dies ist dem Muster des Event-Listeners sehr ähnlich, außer dass die Verarbeitung eines Ereignisses dazu führt, dass der Browser kein Standardverhalten mehr ausführt und dies als Signal verwendet, dass Ihre Webanwendung die Medienaktion unterstützt. Daher werden Steuerelemente für Medienaktionen nur angezeigt, wenn Sie den richtigen Handler für Aktionen festgelegt haben.

Übrigens: Das Aufheben der Festlegung eines Handlers für Medienaktionen ist genauso einfach, wie er null zuzuweisen.

Mit der Media Session API kannst du die Medienbenachrichtigungssymbole „Zurückspulen“ und „Vorwärts spulen“ anzeigen, wenn du steuern möchtest, wie viel Zeit übersprungen wird.

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. Wenn das Standardverhalten aus irgendeinem Grund nicht funktioniert, können Sie trotzdem Medienereignisse „Wiedergabe“ und „Pause“ verarbeiten.

Das Tolle an der Media Session API ist, dass die Benachrichtigungsleiste nicht der einzige Ort ist, an dem Medienmetadaten und -steuerelemente sichtbar sind. Die Medienbenachrichtigung wird automatisch mit jedem gekoppelten Wearable-Gerät synchronisiert. Außerdem werden sie auf Sperrbildschirmen angezeigt.

Feedback