Odtwarzanie filmów w internecie mobilnym

François Beaufort
François Beaufort

Jak tworzyć najlepsze wrażenia z korzystania z multimediów mobilnych w internecie? To proste. Wszystko zależy od zaangażowania użytkowników i znaczenia, jakie przypisujesz mediom na stronie internetowej. Zgadzamy się chyba, że jeśli film jest powodem wizyty użytkownika, musi być wciągający i zachęcać do ponownego korzystania z usługi.

odtwarzanie filmów w internecie mobilnym

Z tego artykułu dowiesz się, jak dzięki mnogości internetowych interfejsów API stopniowo wzbogacać wyświetlane treści multimedialne i uatrakcyjniać je. Dlatego stworzymy prosty odtwarzacz mobilny z niestandardowymi elementami sterującymi, odtwarzaniem na pełnym ekranie i w tle. Możesz już teraz wypróbować przykład i znaleźć kod w naszym repozytorium GitHub.

Elementy sterujące niestandardowe

Układ HTML
Rysunek 1. Układ HTML

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

Elementy sterujące filmem, które omówimy później, to m.in. przycisk odtwarzania/wstrzymywania, przycisk pełnoekranowego odtwarzania, przyciski przewijania do tyłu i do przodu oraz 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>

Odczytywanie metadanych

Zaczekajmy, aż załadują się metadane filmu, aby ustawić czas trwania filmu i aktualny czas oraz zainicjować pasek postępu. Funkcja secondsToTimeCode() to niestandardowa funkcja pomocnicza, którą napisałem, aby zamieniać liczbę sekund na ciąg znaków w formacie „godzina:minuty:sekundy”, który jest w naszym przypadku bardziej odpowiedni.

<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 multimediów z metadanymi filmu

Odtwórz/wstrzymaj film

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

<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 dostosowywać elementy sterujące filmem w detektorze zdarzeń click używamy zdarzeń filmowych playpause. Utworzenie elementów sterujących opartych na zdarzeniach zwiększa elastyczność (co zobaczysz później w przypadku interfejsu Media Session API) i umożliwi nam utrzymywanie synchronizacji elementów sterujących, jeśli przeglądarka ingeruje w odtwarzanie. Gdy rozpoczyna się odtwarzanie filmu, zmieniamy stan przycisku na „Wstrzymaj” i ukrywamy elementy sterujące odtwarzaniem. Gdy film jest wstrzymany, po prostu zmieniamy stan przycisku na „odtwórz” i wyświetlamy elementy sterujące.

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

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

Gdy czas wskazany przez atrybut filmu currentTime zmieni się za pomocą zdarzenia timeupdate wideo, aktualizujemy też nasze elementy sterujące, 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
    })`;
  }
});

Gdy film się kończy, po prostu zmieniamy stan przycisku na „odtwórz”, ustawiamy wartość parametru currentTime na 0 i tymczasowo wyświetlamy elementy sterujące odtwarzaniem. Pamiętaj, że możemy też automatycznie wczytać inny film, jeśli użytkownik włączy jakąś funkcję „Autoodtwarzania”.

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

Przewijanie do tyłu i do przodu

Dodamy przyciski „przewiń wstecz” i „przewiń 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 detektorach zdarzeń click tych przycisków, do dostosowywania jasności filmu użyjemy uruchomionych 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 znajdziesz listę tego, co udało nam się do tej pory zrobić. W następnej sekcji zaimplementujemy przycisk pełnego ekranu.

Pełny ekran

W tym celu użyjemy kilku interfejsów Web API, aby zapewnić płynne korzystanie z reklamy w trybie pełnoekranowym. Aby zobaczyć, jak to działa, zapoznaj się z przykładem.

Oczywiście nie musisz używać wszystkich tych funkcji. Wystarczy, że wybierzesz te, które mają dla Ciebie sens, i połączysz je, aby utworzyć własny proces.

Zapobiegaj automatycznemu pełnoekranowym

Na iOS elementy video automatycznie przechodzą w tryb pełnoekranowy, gdy rozpoczyna się odtwarzanie multimediów. Staramy się jak najlepiej dostosować sposób wyświetlania treści multimedialnych w przeglądarkach mobilnych i kontrolować je, dlatego zalecamy ustawienie atrybutu playsinline elementu video, tak aby wymuszało to odtwarzanie w tekście na iPhonie, a nie w trybie pełnoekranowym po rozpoczęciu odtwarzania. Pamiętaj, że nie ma to wpływu na inne przeglądarki.

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

Włączanie i wyłączanie trybu pełnoekranowego po kliknięciu przycisku

Ponieważ nie chcemy, aby filmy automatycznie wyświetlały się na pełnym ekranie, musimy sami zająć się obsługą trybu pełnoekranowego za pomocą interfejsu Fullscreen API. Gdy użytkownik kliknie „przycisk pełnoekranowy”, dokument powinien wyjść z trybu pełnoekranowego za pomocą document.exitFullscreen(), jeśli jest on obecnie używany. W przeciwnym razie poproś o wyświetlenie na pełnym ekranie w kontenerze wideo metodę requestFullscreen() (jeśli jest dostępna) lub użyj kreacji zastępczej 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łączanie trybu pełnoekranowego po zmianie orientacji ekranu

Gdy użytkownik obróci urządzenie w poziomej, automatycznie poproś o tryb pełnoekranowy, aby zapewnić lepsze wrażenia. W tym celu potrzebujemy interfejsu Screen Orientation API, który nie jest jeszcze obsługiwany wszędzie i w przypadku niektórych przeglądarek nadal wymaga prefiksu. To będzie pierwsze stopniowe ulepszenie.

Jak to działa? Gdy tylko wykryjemy zmianę orientacji ekranu, poprośmy o pełny ekran, jeśli okno przeglądarki jest w orientacji poziomej (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();
    }
  });
}

Blokowanie ekranu w poziomie po kliknięciu przycisku

Ponieważ filmy lepiej ogląda się w orientacji poziomej, warto zablokować ekran w orientacji poziomej, gdy użytkownik kliknie „Przycisk pełnoekranowy”. Aby zapewnić Ci jak najlepsze wrażenia, połączymy używany wcześniej interfejs Screen Orientation API z niektórymi zapytaniami media.

Zablokowanie ekranu w orientacji poziomej jest tak proste jak nawiązanie połączeniascreen.orientation.lock('landscape'). Powinniśmy jednak to zrobić tylko wtedy, gdy urządzenie jest w trybie pionowym z urządzeniem matchMedia('(orientation: portrait)') i można je trzymać w jednej ręce, bo raczej nie będzie to wygodne dla użytkowników tabletów.matchMedia('(max-device-width: 768px)')

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

Być może zauważyliście, że stworzony przez nas właśnie ekran blokady nie jest doskonały, ponieważ nie zmieniamy orientacji ekranu, gdy jest on zablokowany.

Aby to naprawić, użyj interfejsu Device Orientation API, jeśli jest dostępny. Ten interfejs API udostępnia informacje z urządzenia, które mierzy pozycję i ruchy urządzenia w przestrzeni: żyroskop i kompas cyfrowy służą do określania orientacji, a przyspieszeniomierz do pomiaru prędkości. Gdy wykryjemy zmianę orientacji urządzenia, odblokujemy ekran za pomocą funkcji screen.orientation.unlock(), jeśli użytkownik trzyma urządzenie w trybie pionowym, 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łynne wyświetlanie pełnoekranowe, którego szukaliśmy. Aby zobaczyć, jak to działa, zapoznaj się z przykładem.

Odtwarzanie w tle

Jeśli zauważysz, że strona internetowa lub film na niej nie są już widoczne, możesz zaktualizować dane analityczne, aby odzwierciedlić tę zmianę. Może to również wpływać na bieżące odtwarzanie, na przykład w wybranym utworze, w pauzie lub nawet w wyświetlanych dla użytkownika przyciskach niestandardowych.

Wstrzymywanie filmu po zmianie widoczności strony

Dzięki interfejsowi Page Visibility API możemy określić aktualną widoczność strony i otrzymywać powiadomienia o zmianach widoczności. Kod poniżej wstrzymuje film, gdy strona jest ukryta. Dzieje się tak, gdy aktywna jest blokada ekranu 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 sposobu tylko wtedy, gdy użytkownik może odtwarzać 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 uzyskać jeszcze bardziej szczegółowe informacje bez ponoszenia dodatkowych kosztów. Ten interfejs API informuje, kiedy obserwowany element wchodzi do widoku lub z niego wychodzi.

Pokaż/ukryj przycisk wyciszenia w zależności od widoczności filmu na stronie. Jeśli film jest odtwarzany, ale nie jest widoczny, w prawym dolnym rogu strony pojawi się przycisk wyciszenia, dzięki któremu użytkownik będzie mógł kontrolować dźwięk. Zdarzenie volumechange służy do aktualizowania stylu przycisku wyciszenia.

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

odtwarzać tylko 1 film w danym momencie,

Jeśli na stronie jest więcej niż 1 film, zalecam odtwarzanie tylko jednego z nich, a pozostałe automatycznie wstrzymywać, aby użytkownik nie musiał słyszeć jednocześnie wielu ścieżek audio.

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

Dostosowywanie powiadomień o multimediach

Interfejs Media Session API pozwala też dostosować powiadomienia o multimediach, podając metadane aktualnie odtwarzanego filmu. Pozwala też obsługiwać zdarzenia związane z multimediami, takie jak przeskakiwanie lub zmiana ścieżki, które mogą pochodzić z powiadomień lub kluczy multimediów. Aby zobaczyć, jak to działa, zapoznaj się z przykładem.

Gdy aplikacja internetowa odtwarza dźwięk lub film, w pasku powiadomień pojawia się powiadomienie multimedialne. Na Androidzie Chrome stara się wyświetlać odpowiednie informacje, korzystając z tytułu dokumentu i największego znalezionego obrazu ikony.

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

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 podczas odtwarzania będzie używana aktualna wersja navigator.mediaSession.metadata. Dlatego musisz je zaktualizować, aby zawsze wyświetlać odpowiednie informacje w powiadomieniach dla mediów.

Jeśli Twoja aplikacja internetowa zawiera playlistę, możesz zezwolić 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 przetwarzacze działań związanych z multimediami pozostaną bez zmian. Jest to bardzo podobne do wzorca eventListener, z tym że obsługa zdarzenia oznacza, że przeglądarka przestaje wykonywać jakiekolwiek domyślne działanie i używa tego jako sygnału, że Twoja aplikacja internetowa obsługuje działanie dotyczące multimediów. Dlatego opcje działania multimediów nie będą widoczne, dopóki nie skonfigurujesz odpowiedniego modułu obsługi działania.

Odznaczenie przetwarzacza akcji typu Media Action jest tak samo proste jak przypisanie go do null.

Interfejs API sesji multimediów umożliwia wyświetlanie ikon „Przewinąć do tyłu” i „Przewinąć do przodu” w powiadomieniach multimedialnych, jeśli chcesz kontrolować czas przewijania.

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 wyświetlana w powiadomieniu o multimediach, a powiązane 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 multimedialne „Odtwórz” i „Wstrzymaj”.

Wspaniałą rzeczą w przypadku interfejsu Media Session API jest to, że panel powiadomień to nie jedyne miejsce, w którym są widoczne metadane i opcje sterowania multimediami. Powiadomienie o multimediach jest automatycznie synchronizowane z każdym sparowanym urządzeniem do noszenia. Wyświetla się też na ekranie blokady.

Prześlij opinię