Videowiedergabe im mobilen Web

François Beaufort
François Beaufort

Wie schaffen Sie das beste mobile Medienerlebnis im Web? So einfach ist das. Es hängt alles von der Nutzerinteraktion und der Bedeutung ab, die Sie den Medien auf einer Webseite beimessen. Wir sind uns sicher alle einig, dass die Nutzererfahrung immersiv und ansprechend sein muss, wenn ein Nutzer eine Website nur wegen eines Videos besucht.

Videowiedergabe im mobilen Web

In diesem Artikel zeige ich Ihnen, wie Sie Ihre Medieninhalte schrittweise verbessern und dank einer Vielzahl von Web-APIs immersiver gestalten können. Deshalb entwickeln wir einen einfachen mobilen Player mit benutzerdefinierten Steuerelementen, Vollbildmodus 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 du siehst, ist das HTML-Layout, das wir für unseren Mediaplayer verwenden, ziemlich einfach: Ein <div>-Stammelement enthält ein <video>-Medienelement und ein untergeordnetes <div>-Element für die Videosteuerung.

Zu den Videosteuerelementen, die wir später behandeln, gehören: ein Wiedergabe-/Pause-Button, ein Vollbild-Button, Buttons zum Vor- und Zurückspulen 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>

Video-Metadaten lesen

Zuerst warten wir, bis die Videometadaten geladen sind, um die Videodauer und die aktuelle Zeit festzulegen und die Fortschrittsanzeige zu initialisieren. Die Funktion secondsToTimeCode() ist eine benutzerdefinierte Dienstfunktion, 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 Video-Metadaten
Abbildung 2. Media Player mit Videometadaten

Video abspielen/anhalten

Nachdem die Video-Metadaten geladen wurden, fügen wir den ersten Button hinzu, mit dem 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. Wenn wir unsere Steuerelemente ereignisbasiert gestalten, sind wir flexibler (wie wir später bei der Media Session API sehen werden) und können unsere Steuerelemente synchron halten, wenn der Browser in die Wiedergabe eingreift. Wenn die Videowiedergabe beginnt, ändern wir den Status der Schaltfläche zu „Pause“ und blenden die Videosteuerelemente aus. Wenn das Video pausiert wird, ändern wir einfach den Status des Buttons zu „Wiedergabe“ und blenden die Videosteuerelemente ein.

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

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

Wenn sich die im Videoattribut currentTime angegebene Zeit über das Videoereignis timeupdate ändert, werden auch die benutzerdefinierten Steuerelemente aktualisiert, 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 endet, ändern wir den Buttonstatus einfach in „Wiedergabe“, setzen die currentTime des Videos auf 0 zurück und blenden die Videosteuerelemente ein. Wir könnten auch automatisch ein anderes Video laden, wenn der Nutzer eine Art „Autoplay“-Funktion aktiviert hat.

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

Zurück- und vorspulen

Fügen wir nun die Schaltflächen „Zurückspulen“ und „Vorspulen“ hinzu, damit Nutzer 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);
});

Wie zuvor passen wir das Videostyling nicht in den click-Event-Listenern dieser Schaltflächen an, sondern verwenden die ausgelösten Videoereignisse seeking und seeked, 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');
});

Hier ist, was wir bisher erstellt haben. Im nächsten Abschnitt implementieren wir die Vollbild-Schaltfläche.

Vollbild

Hier nutzen wir mehrere Web-APIs, um eine perfekte und nahtlose Vollbildnutzung zu ermöglichen. Ein Beispiel dafür finden Sie hier.

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

Automatischen Vollbildmodus verhindern

Unter iOS wechseln video-Elemente automatisch in den Vollbildmodus, wenn die Medienwiedergabe beginnt. Da wir versuchen, die Medienwiedergabe in mobilen Browsern so weit wie möglich anzupassen und zu steuern, empfehle ich, das Attribut playsinline des Elements video festzulegen, um die Wiedergabe auf dem iPhone inline zu erzwingen und nicht in den Vollbildmodus zu wechseln, wenn die Wiedergabe beginnt. Dies hat keine Nebenwirkungen auf andere Browser.

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

Vollbildmodus per Klick auf die Schaltfläche ein- oder ausschalten

Da wir den automatischen Vollbildmodus verhindern, müssen wir den Vollbildmodus für das Video mit der Fullscreen API selbst verwalten. Wenn der Nutzer auf die Schaltfläche „Vollbild“ klickt, wird der Vollbildmodus mit document.exitFullscreen() beendet, sofern er gerade vom Dokument verwendet wird. Andernfalls fordern Sie den Vollbildmodus für den Videocontainer mit der Methode requestFullscreen() an, falls verfügbar, oder verwenden Sie 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 ein-/ausschalten

Wenn der Nutzer das Gerät ins Querformat dreht, sollten wir das erkennen und automatisch den Vollbildmodus anfordern, um ein immersives Erlebnis zu schaffen. Dafür benötigen wir die Screen Orientation API, die noch nicht überall unterstützt wird und in einigen Browsern noch ein Präfix hat. 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. die Breite ist größer als die Höhe). Falls 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 per Tastendruck

Da Videos im Querformat möglicherweise besser zu sehen sind, können wir den Bildschirm im Querformat sperren, wenn der Nutzer auf die Schaltfläche „Vollbild“ klickt. Wir kombinieren die zuvor verwendete Screen Orientation API und einige Medienabfragen, um die bestmögliche Nutzererfahrung zu bieten.

Wenn du das Display im Querformat sperren möchtest, musst du nur screen.orientation.lock('landscape') aufrufen. Wir sollten dies jedoch nur tun, wenn sich das Gerät im Porträtmodus mit matchMedia('(orientation: portrait)') befindet und mit einer Hand gehalten werden kann (matchMedia('(max-device-width: 768px)')), da dies für Nutzer auf Tablets 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');
  }
}

Display bei Änderung der Geräteausrichtung entsperren

Vielleicht ist Ihnen aufgefallen, dass die von uns erstellte Sperrbildschirm-Ansicht nicht perfekt ist, da wir keine Änderungen der Bildschirmausrichtung erhalten, wenn der Bildschirm 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 Porträtmodus 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 genau die nahtlose Vollbilddarstellung, die wir wollten. Ein Beispiel dafür finden Sie hier.

Hintergrundwiedergabe

Wenn ihr feststellt, dass eine Webseite oder ein Video auf der Webseite nicht mehr sichtbar ist, solltet ihr eure Analysen entsprechend aktualisieren. Dies kann sich auch auf die aktuelle Wiedergabe auswirken, z. B. durch die Auswahl eines anderen Titels, das Pausieren oder das Anzeigen benutzerdefinierter Schaltflächen für den Nutzer.

Video bei Änderung der Seitensichtbarkeit pausieren

Mit der Page Visibility API können wir die aktuelle Sichtbarkeit einer Seite ermitteln und uns über Änderungen der Sichtbarkeit benachrichtigen lassen. Der folgende Code pausiert das Video, wenn die Seite ausgeblendet wird. Das passiert beispielsweise, wenn die Displaysperre aktiv ist oder wenn du den Tab wechselst.

Da die meisten mobilen Browser mittlerweile Steuerelemente außerhalb des Browsers anbieten, mit denen ein pausiertes Video fortgesetzt werden kann, empfehle ich, dieses Verhalten nur festzulegen, wenn der Nutzer die Wiedergabe im Hintergrund erlaubt.

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

Taste „Stummschalten“ bei Änderung der Videosichtbarkeit ein-/ausblenden

Wenn Sie die neue Intersection Observer API verwenden, können Sie kostenlos noch detailliertere Informationen erhalten. Mit dieser API können Sie feststellen, wann ein beobachtetes Element in den Darstellungsbereich des Browsers eintritt oder ihn verlässt.

Wir möchten eine Stummschaltfläche ein- oder ausblenden, je nachdem, ob das Video auf der Seite sichtbar ist. Wenn ein Video abgespielt wird, aber gerade nicht sichtbar ist, wird rechts unten auf der Seite eine kleine Taste „Stummschalten“ angezeigt, über die der Nutzer den Ton des Videos steuern kann. Das Videoereignis volumechange wird verwendet, um das Design der Taste „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 ein Video gleichzeitig abspielen

Wenn sich auf einer Seite mehrere Videos befinden, sollten Sie nur eines abspielen und die anderen automatisch pausieren, damit der Nutzer nicht mehrere Audiotracks gleichzeitig hört.

// 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 das Suchen oder Ändern von Titeln verarbeiten, die von Benachrichtigungen oder Medientasten stammen können. Sehen Sie sich das Beispiel an, um dies in Aktion zu sehen.

Wenn deine Web-App Audio- oder Videoinhalte wiedergibt, wird in der Benachrichtigungsleiste bereits eine Medienbenachrichtigung angezeigt. Unter Android versucht Chrome, die passenden Informationen anzuzeigen, indem der Titel des Dokuments und das größte gefundene Symbolbild verwendet werden.

Sehen wir uns an, wie Sie diese Medienbenachrichtigung anpassen können, indem Sie mit der Media Session API einige Metadaten für die 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',
      },
    ],
  });
}

Nach der Wiedergabe musst du die Mediensitzung nicht „freigeben“, da die Benachrichtigung automatisch verschwindet. Denken Sie daran, dass die aktuelle navigator.mediaSession.metadata verwendet wird, wenn die Wiedergabe beginnt. Deshalb musst du sie aktualisieren, damit in der Medienbenachrichtigung immer relevante Informationen angezeigt werden.

Wenn deine Web-App eine Playlist enthält, kannst du dem Nutzer die Möglichkeit geben, direkt über die Medienbenachrichtigung mit den Symbolen „Vorheriger Titel“ und „Nächster Titel“ durch die Playlist zu navigieren.

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

Beachte, dass Media Action Handler beibehalten werden. Das ist dem Event-Listener-Muster sehr ähnlich. Der Unterschied besteht darin, dass der Browser bei der Verarbeitung eines Ereignisses das Standardverhalten beendet und dies als Signal dafür verwendet, dass Ihre Web-App die Medienaktion unterstützt. Daher werden die Steuerelemente für Medienaktionen nur angezeigt, wenn du den richtigen Aktions-Handler festlegst.

Übrigens ist das Aufheben der Festlegung eines Media Action Handlers so einfach wie das Zuweisen zu null.

Mit der Media Session API können Sie die Medienbenachrichtigungssymbole „Zurückspulen“ und „Vorspulen“ anzeigen, wenn Sie die übersprungene Zeit steuern möchten.

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, kannst du die 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 allen gekoppelten Wearables synchronisiert. Und sie wird auch auf Sperrbildschirmen angezeigt.

Feedback