Odtwarzanie filmów w internecie mobilnym

François Beaufort
François Beaufort

Jak stworzyć optymalną wygodę korzystania z mediów mobilnych w internecie? To proste. Wszystko zależy od zaangażowania użytkowników oraz tego, jak ważne są dla Ciebie media na stronie internetowej. Wszyscy zgadzamy się, że skoro film jest powodem wizyty użytkownika, musi on być wciągający i ponownie angażujący.

odtwarzanie filmów w internecie mobilnym

W tym artykule pokażę Ci, jak stopniowo wzbogacać multimedia i zwiększać ich zaangażowanie dzięki wielu interfejsom API stron internetowych. Dlatego stworzyliśmy prosty odtwarzacz na urządzenia mobilne, który będzie miał własne elementy sterujące, tryb pełnoekranowy i odtwarzanie w tle. Możesz już teraz wypróbować przykład i znaleźć kod w naszym repozytorium GitHub.

Ustawienia niestandardowe

Układ HTML
Rysunek 1. Układ HTML

Jak widać, układ HTML, którego użyjemy na potrzeby odtwarzacza multimediów, jest dość prosty: element główny <div> zawiera element multimedialny <video> i element podrzędny <div> przeznaczony do elementów sterujących odtwarzaniem filmu.

Omówimy później elementy sterujące odtwarzaniem filmu, takie jak: przycisk odtwarzania/wstrzymywania, przycisk pełnego ekranu, przyciski przewijania do tyłu i do przodu oraz niektóre elementy dotyczące bieżącego czasu, czasu trwania i śledzenia czasu.

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

Odczyt metadanych filmu

Najpierw zaczekaj na załadowanie metadanych filmu, aby ustawić czas trwania filmu, bieżący czas i zainicjować pasek postępu. Funkcja secondsToTimeCode() to napisana przeze mnie niestandardowa funkcja narzędzia, która konwertuje liczbę sekund na ciąg znaków w formacie „gg:mm:ss”, który lepiej sprawdza się w naszym przypadku.

<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
  })`;
});
tylko metadane filmu
Rysunek 2. Odtwarzacz z metadanymi filmu

Odtwórz/wstrzymaj film

Po wczytaniu metadanych filmu dodajmy pierwszy przycisk, który w zależności od stanu odtwarzania umożliwia użytkownikowi odtwarzanie i wstrzymywanie filmu za pomocą funkcji video.play() i video.pause().

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

Zamiast zmieniać ustawienia filmu w detektorze zdarzeń click, używamy zdarzeń wideo play i pause. Oparte na zdarzeniach sterujących zwiększa elastyczność (jak zobaczymy później w przypadku interfejsu Media Session API) i umożliwia synchronizację elementów sterujących w razie ingerencji przeglądarki w odtwarzanie. Po rozpoczęciu odtwarzania filmu zmieniamy stan przycisku na „Wstrzymaj” i ukrywamy elementy sterujące odtwarzaniem. Kiedy odtwarzanie filmu jest zatrzymywane, zmieniamy stan przycisku na „odtwórz” i wyświetlamy elementy sterujące filmem.

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

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

Gdy czas określony w atrybucie wideo currentTime zmieni się za pomocą zdarzenia wideo timeupdate, aktualizujemy również nasze niestandardowe ustawienia, jeśli są widoczne.

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

Po zakończeniu filmu zmieniamy stan przycisku na „odtwórz”, ustawiamy currentTime z powrotem na 0 i wyświetlamy elementy sterujące odtwarzaniem filmu. Pamiętaj, że możemy też automatycznie wczytywać kolejne filmy, jeśli użytkownik włączył funkcję „Autoodtwarzanie”.

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

Przewijanie do tyłu i do przodu

Przejdźmy dalej i dodaj przyciski „przewiń do tyłu” i „do przodu”, aby użytkownik mógł łatwo pominąć niektóre treści.

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

Tak jak wcześniej, zamiast dostosowywać styl filmu w odbiornikach zdarzeń click tych przycisków, do dostosowania jasności filmu użyjemy wywołanych zdarzeń wideo seeking i seeked. Moja niestandardowa klasa CSS seeking jest tak prosta jak filter: brightness(0);.

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

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

Poniżej podajemy, co już utworzyliśmy. W następnej sekcji zaimplementujemy przycisk pełnego ekranu.

Pełny ekran

Wykorzystamy tu kilka internetowych interfejsów API, aby stworzyć idealną i bezproblemową obsługę na pełnym ekranie. Aby zobaczyć, jak to działa, zapoznaj się z przykładem.

Oczywiście nie musisz korzystać ze wszystkich tych usług. Wybierz te, które są dla Ciebie ważne, i połącz je, by utworzyć własny przepływ.

Wyłącz automatyczne wyświetlanie na pełnym ekranie

Na urządzeniach z iOS elementy video automatycznie przechodzą w tryb pełnoekranowy, gdy rozpocznie się odtwarzanie multimediów. Staramy się w jak największym stopniu dostosowywać multimedia i jak je kontrolować w przeglądarkach mobilnych, dlatego zalecamy ustawienie atrybutu playsinline elementu video w celu wymuszania jego odtwarzania wbudowanego na iPhonie i nieprzechodzenia do trybu pełnoekranowego w momencie rozpoczęcia odtwarzania. Nie ma to skutków ubocznych w innych przeglądarkach.

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

Przełącz na pełny ekran po kliknięciu przycisku

Teraz, gdy wyeliminowaliśmy automatyczne wyświetlanie na pełnym ekranie, musimy włączyć tryb pełnoekranowy w przypadku filmu za pomocą interfejsu Fullscreen API. Gdy użytkownik kliknie „przycisk pełnego ekranu”, dokument wyjdzie z trybu pełnoekranowego za pomocą polecenia document.exitFullscreen(), jeśli dokument jest obecnie używany w tym trybie. W przeciwnym razie zażądaj pełnego ekranu w kontenerze wideo, używając metody requestFullscreen() (jeśli jest dostępna), lub zastosuj metodę webkitEnterFullscreen() w elemencie wideo tylko w 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);
});

Przełącz pełny ekran na zmianie orientacji ekranu

Nie daj się zaskoczyć, gdy użytkownik obraca urządzenie w trybie poziomym, i automatycznie włączaj tryb pełnoekranowy. Potrzebujemy do tego interfejsu Screen Orientation API, który nie jest jeszcze obsługiwany w każdym miejscu, a w niektórych przeglądarkach nadal ma prefiks. To będzie nasze pierwsze stopniowe ulepszenie.

Jak to działa? Gdy tylko wykryjemy zmianę orientacji ekranu, użyj pełnego ekranu, jeśli okno przeglądarki jest w trybie poziomym (czyli jego szerokość jest większa niż wysokość). Jeśli nie, zamknij pełny ekran. To wszystko.

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

Ekran blokady w orientacji poziomej po kliknięciu przycisku

Ze względu na to, że film lepiej ogląda się w trybie poziomym, możemy chcieć zablokować ekran w orientacji poziomej, gdy użytkownik kliknie przycisk pełnego ekranu. Połączymy używany wcześniej interfejs Screen Orientation API i niektóre zapytania o multimedia, aby zapewnić jak najlepsze działanie tego rozwiązania.

Aby zablokować ekran w orientacji poziomej, wystarczy zadzwonić pod numer screen.orientation.lock('landscape'). Należy to zrobić tylko wtedy, gdy urządzenie jest ustawione pionowo w matchMedia('(orientation: portrait)') i można je trzymać w jednej ręce za pomocą matchMedia('(max-device-width: 768px)'), ponieważ nie byłoby to zbyt wygodne w przypadku użytkowników tabletów.

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

Odblokuj ekran po zmianie orientacji urządzenia

Jak można zauważyć, nowy interfejs ekranu blokady nie jest idealny, ponieważ po zablokowaniu nie zmienia się jego orientacja.

Aby rozwiązać ten problem, użyjmy interfejsu Device Orientation API, jeśli jest dostępny. Dostarcza on informacje ze sprzętu do pomiaru pozycji i ruchu urządzenia w przestrzeni kosmicznej: żyroskop i kompas cyfrowy w orientacji oraz akcelerometr – prędkość. Gdy wykryjemy zmianę orientacji urządzenia, odblokujmy ekran za pomocą screen.orientation.unlock(), jeśli użytkownik trzyma urządzenie w orientacji pionowej, a ekran jest zablokowany w trybie poziomym.

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

Jak widać, jest to płynny podgląd na pełnym ekranie. Aby zobaczyć, jak to działa, zapoznaj się z przykładem.

Odtwarzanie w tle

Gdy zauważysz, że strona lub film na niej nie są już widoczne, możesz zaktualizować statystyki. To także może mieć wpływ na bieżące odtwarzanie, np. wybranie innej ścieżki, wstrzymanie go, a nawet wyświetlenie niestandardowych przycisków.

Zmiana widoczności filmu Wstrzymaj film na stronie

Interfejs Page Visibility API pozwala określić aktualną widoczność strony i otrzymywać powiadomienia o zmianach. Kod poniżej wstrzymuje film, gdy strona jest ukryta. Dzieje się tak, gdy blokada ekranu jest aktywna lub gdy przełączasz karty.

Większość przeglądarek mobilnych udostępnia teraz poza przeglądarką elementy sterujące, które umożliwiają wznawianie wstrzymanego filmu, dlatego zalecamy ustawienie tego działania tylko wtedy, gdy użytkownik może odtwarzać treści w tle.

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

Pokaż/ukryj przycisk wyciszenia po zmianie widoczności filmu

Jeśli używasz nowego interfejsu Intersection Observer API, możesz zapewnić jeszcze większą precyzję bez dodatkowych kosztów. Ten interfejs API informuje, czy obserwowany element pojawia się w widocznym obszarze przeglądarki, czy go opuszcza.

Pokażmy lub ukryjmy przycisk wyciszania w zależności od widoczności filmu na stronie. Jeśli film jest odtwarzany, ale nie jest widoczny, w prawym dolnym rogu strony wyświetla się mały przycisk wyciszania, który daje użytkownikowi kontrolę nad dźwiękiem w filmie. Zdarzenie wideo volumechange służy do zmiany stylu przycisku wyciszania.

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

Odtwarzanie tylko jednego filmu w danym momencie

Jeśli na stronie jest kilka filmów, sugeruję odtwarzanie tylko jednego z nich i automatyczne wstrzymywanie pozostałych, aby użytkownik nie musiał jednocześnie słuchać wielu ścieżek dźwiękowych.

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

Spersonalizuj powiadomienia o multimediach

Interfejs Media Session API pozwala też dostosować powiadomienia o multimediach, podając metadane aktualnie odtwarzanego filmu. Umożliwia też obsługę zdarzeń związanych z multimediami, takich jak wyszukiwanie lub śledzenie zmian, które mogą pochodzić z powiadomień lub klawiszy multimedialnych. Aby zobaczyć, jak to działa, obejrzyj przykład.

Gdy aplikacja internetowa odtwarza dźwięk lub obraz, na pasku powiadomień pojawia się powiadomienie o multimediach. Na urządzeniach z Androidem Chrome stara się pokazać odpowiednie informacje, używając tytułu dokumentu i największego obrazu ikony, jaki można znaleźć.

Zobaczmy, jak dostosować to powiadomienie o multimediach, ustawiając za pomocą interfejsu Media Session API metadane sesji multimedialnej, takie jak tytuł, wykonawca, nazwa albumu i grafika.

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

Po zakończeniu odtwarzania nie musisz „zwalniać” sesji multimediów, ponieważ powiadomienie zniknie automatycznie. Pamiętaj, że przy rozpoczęciu odtwarzania będzie używana bieżąca navigator.mediaSession.metadata. Dlatego musisz go zaktualizować, aby w powiadomieniach multimedialnych zawsze wyświetlały się istotne informacje.

Jeśli Twoja aplikacja internetowa udostępnia playlistę, możesz pozwolić użytkownikowi na poruszanie się po niej bezpośrednio z powiadomienia o multimediach za pomocą ikon „Poprzedni utwór” i „Następny utwór”.

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

Pamiętaj, że moduły obsługi działań związanych z multimediami zostaną zachowane. Działa to bardzo podobnie do wzorca odbiornika, ale obsługa zdarzenia oznacza, że przeglądarka przestaje wykonywać domyślne działanie i używa go jako sygnału, że aplikacja internetowa obsługuje akcję związaną z multimediami. Elementy sterujące działaniami związanymi z multimediami nie będą więc widoczne, jeśli nie ustawisz odpowiedniego modułu.

Przy okazji – anulowanie ustawienia modułu obsługi działań związanych z multimediami jest tak proste, jak przypisanie go do: null.

Interfejs Media Session API pozwala wyświetlać ikony powiadomień o multimediach „Przewiń do tyłu” i „Przewiń do przodu”, aby kontrolować ilość czasu, który ma być pomijany.

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

Ikona „Odtwórz/wstrzymaj” jest zawsze widoczna w powiadomieniu o multimediach, a powiązane z nimi zdarzenia są obsługiwane automatycznie przez przeglądarkę. Jeśli z jakiegoś powodu domyślne działanie nie zadziała, nadal możesz obsługiwać zdarzenia multimediów „Odtwórz” i „Wstrzymaj”.

Ogromną zaletą interfejsu Media Session API jest to, że pasek powiadomień nie jest jedynym miejscem, w którym widoczne są metadane i elementy sterujące. Powiadomienie o multimediach jest automagicznie synchronizowane z każdym sparowanym urządzeniem do noszenia. Pojawia się też na ekranie blokady.

Prześlij opinię