Pemutaran Video Web Seluler

François Beaufort
François Beaufort

Bagaimana cara Anda menciptakan pengalaman media seluler terbaik di Web? Mudah! Semua bergantung pada engagement pengguna dan tingkat kepentingan yang Anda berikan kepada media di halaman web. Saya rasa kita semua setuju bahwa jika video adalah ALASAN kunjungan pengguna, pengalaman pengguna harus imersif dan menarik kembali.

pemutaran video web seluler

Dalam artikel ini, saya akan menunjukkan cara meningkatkan pengalaman media Anda secara progresif dan membuatnya lebih imersif berkat banyaknya API Web. Itulah sebabnya kami akan membuat pengalaman pemutar seluler sederhana dengan kontrol kustom, layar penuh, dan pemutaran latar belakang. Anda dapat mencoba contoh sekarang dan menemukan kode di repositori GitHub.

Kontrol kustom

Tata Letak HTML
Gambar 1. Tata Letak HTML

Seperti yang dapat Anda lihat, tata letak HTML yang akan kita gunakan untuk pemutar media cukup sederhana: elemen root <div> berisi elemen media <video> dan elemen turunan <div> yang didedikasikan untuk kontrol video.

Kontrol video yang akan kita bahas nanti, meliputi: tombol putar/jeda, tombol layar penuh, tombol cari mundur dan maju, serta beberapa elemen untuk waktu saat ini, durasi, dan pelacakan waktu.

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

Membaca metadata video

Pertama, mari kita tunggu metadata video dimuat untuk menetapkan durasi video, waktu saat ini, dan menginisialisasi status progres. Perhatikan bahwa fungsi secondsToTimeCode() adalah fungsi utilitas kustom yang telah saya tulis yang mengonversi sejumlah detik menjadi string dalam format "hh:mm:ss" yang lebih cocok dalam kasus kita.

<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
  })`;
});
khusus metadata video
Gambar 2. Pemutar Media menampilkan metadata video

Putar/jeda video

Setelah metadata video dimuat, mari kita tambahkan tombol pertama yang memungkinkan pengguna memutar dan menjeda video dengan video.play() dan video.pause(), bergantung pada status pemutarannya.

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

Daripada menyesuaikan kontrol video di pemroses peristiwa click, kita menggunakan peristiwa video play dan pause. Membuat kontrol berbasis peristiwa akan membantu fleksibilitas (seperti yang akan kita lihat nanti dengan Media Session API) dan akan memungkinkan kita untuk tetap menyinkronkan kontrol jika browser melakukan intervensi dalam pemutaran. Saat video mulai diputar, kami mengubah status tombol menjadi "jeda" dan menyembunyikan kontrol video. Saat video dijeda, kita cukup mengubah status tombol ke "putar" dan menampilkan kontrol video.

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

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

Saat waktu yang ditunjukkan oleh atribut currentTime video diubah melalui peristiwa video timeupdate, kami juga memperbarui kontrol kustom jika terlihat.

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

Saat video berakhir, cukup ubah status tombol menjadi "putar", setel currentTime video kembali ke 0, dan tampilkan kontrol video untuk saat ini. Perhatikan bahwa kita juga dapat memilih untuk memuat video lain secara otomatis jika pengguna telah mengaktifkan beberapa jenis fitur "Putar Otomatis".

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

Memutar mundur dan maju

Mari kita lanjutkan dan tambahkan tombol "cari mundur" dan "cari maju" agar pengguna dapat dengan mudah melewati beberapa konten.

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

Seperti sebelumnya, daripada menyesuaikan gaya video di pemroses peristiwa click tombol ini, kita akan menggunakan peristiwa video seeking dan seeked yang diaktifkan untuk menyesuaikan kecerahan video. Class CSS seeking kustom saya sesederhana filter: brightness(0);.

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

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

Berikut adalah hal yang telah kita buat sejauh ini. Di bagian berikutnya, kita akan menerapkan tombol layar penuh.

Layar penuh

Di sini, kita akan memanfaatkan beberapa Web API untuk menciptakan pengalaman layar penuh yang sempurna dan lancar. Untuk melihat cara kerjanya, lihat contoh.

Tentu saja, Anda tidak harus menggunakan semuanya. Pilih saja yang masuk akal dan gabungkan untuk membuat alur khusus.

Mencegah layar penuh otomatis

Di iOS, elemen video secara otomatis memasuki mode layar penuh saat pemutaran media dimulai. Selagi kami mencoba menyesuaikan dan mengontrol sebanyak mungkin pengalaman media kami di seluruh browser seluler, sebaiknya tetapkan atribut playsinline dari elemen video untuk memaksanya diputar secara inline di iPhone dan tidak memasuki mode layar penuh saat pemutaran dimulai. Perhatikan bahwa tindakan ini tidak memiliki efek samping di browser lain.

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

Beralih ke layar penuh saat tombol diklik

Setelah mencegah layar penuh otomatis, kita harus menangani mode layar penuh untuk video dengan Fullscreen API. Saat pengguna mengklik "tombol layar penuh", mari kita keluar dari mode layar penuh dengan document.exitFullscreen() jika mode layar penuh saat ini digunakan oleh dokumen. Jika tidak, minta layar penuh di penampung video dengan metode requestFullscreen() jika tersedia atau kembali ke webkitEnterFullscreen() di elemen video hanya di 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);
});

Beralih ke layar penuh saat orientasi layar diubah

Saat pengguna memutar perangkat dalam mode lanskap, mari kita lakukan dengan cerdas dan meminta layar penuh secara otomatis untuk menciptakan pengalaman yang imersif. Untuk itu, kita memerlukan Screen Orientation API yang belum didukung di mana saja dan masih menjadi awalan di beberapa browser pada saat itu. Dengan demikian, ini akan menjadi {i> progressive enhancement <i}pertama.

Bagaimana cara kerjanya? Segera setelah kita mendeteksi perubahan orientasi layar, mari kita minta layar penuh jika jendela browser dalam mode lanskap (yaitu, lebarnya lebih besar dari tingginya). Jika tidak, mari kita keluar dari layar penuh. Itu saja.

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

Layar kunci dalam mode lanskap saat tombol diklik

Karena video mungkin lebih baik dilihat dalam mode lanskap, sebaiknya kita mengunci layar dalam lanskap saat pengguna mengklik "tombol layar penuh". Kita akan menggabungkan Screen Orientation API yang sebelumnya digunakan dan beberapa kueri media untuk memastikan pengalaman ini adalah yang terbaik.

Mengunci layar dalam lanskap semudah memanggil screen.orientation.lock('landscape'). Namun, kita hanya boleh melakukannya saat perangkat dalam mode potret dengan matchMedia('(orientation: portrait)') dan dapat dipegang dengan satu tangan dengan matchMedia('(max-device-width: 768px)') karena hal ini tidak akan menjadi pengalaman yang baik bagi pengguna di tablet.

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

Membuka kunci layar saat orientasi perangkat berubah

Anda mungkin melihat bahwa pengalaman layar kunci yang baru saja dibuat tidak sempurna karena kami tidak menerima perubahan orientasi layar saat layar terkunci.

Untuk memperbaikinya, mari kita gunakan Device Orientation API jika tersedia. API ini memberikan informasi dari hardware yang mengukur posisi dan gerakan perangkat dalam ruang: giroskop dan kompas digital untuk orientasinya, dan akselerometer untuk kecepatannya. Saat mendeteksi perubahan orientasi perangkat, mari buka kunci layar dengan screen.orientation.unlock() jika pengguna memegang perangkat dalam mode potret dan layar terkunci dalam mode lanskap.

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

Seperti yang dapat Anda lihat, ini adalah pengalaman layar penuh yang lancar yang kita cari. Untuk melihat cara kerjanya, lihat contoh.

Pemutaran di latar belakang

Jika Anda mendeteksi bahwa halaman web atau video di halaman web tidak terlihat lagi, Anda dapat memperbarui analisis untuk mencerminkan hal ini. Hal ini juga dapat memengaruhi pemutaran saat ini seperti memilih trek yang berbeda, menjeda, atau bahkan menampilkan tombol kustom kepada pengguna.

Menjeda video saat visibilitas halaman berubah

Dengan Page Visibility API, kita dapat menentukan visibilitas halaman saat ini dan menerima notifikasi tentang perubahan visibilitas. Kode di bawah menjeda video saat halaman disembunyikan. Hal ini terjadi saat kunci layar aktif, atau saat Anda beralih tab.

Karena sebagian besar browser seluler kini menawarkan kontrol di luar browser yang memungkinkan melanjutkan video yang dijeda, sebaiknya tetapkan perilaku ini hanya jika pengguna diizinkan untuk memutar di latar belakang.

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

Menampilkan/menyembunyikan tombol bisukan saat visibilitas video berubah

Jika menggunakan Intersection Observer API baru, Anda dapat lebih terperinci tanpa biaya. API ini memungkinkan Anda mengetahui kapan elemen yang diamati memasuki atau keluar dari area pandang browser.

Mari kita tampilkan/sembunyikan tombol bisukan berdasarkan visibilitas video di halaman. Jika video diputar tetapi saat ini tidak terlihat, tombol bisukan mini akan ditampilkan di sudut kanan bawah halaman untuk memberikan kontrol kepada pengguna atas suara video. Peristiwa video volumechange digunakan untuk memperbarui gaya tombol bisukan.

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

Hanya memutar satu video dalam satu waktu

Jika ada lebih dari satu video pada satu halaman, sebaiknya hanya putar salah satu video dan jeda video lainnya secara otomatis, sehingga pengguna tidak perlu mendengar beberapa trek audio diputar secara bersamaan.

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

Menyesuaikan Notifikasi Media

Dengan Media Session API, Anda juga dapat menyesuaikan notifikasi media dengan memberikan metadata untuk video yang sedang diputar. Hal ini juga memungkinkan Anda menangani peristiwa terkait media seperti mencari atau mengubah trek yang mungkin berasal dari notifikasi atau tombol media. Untuk melihat penerapannya, lihat contoh.

Saat aplikasi web memutar audio atau video, Anda sudah dapat melihat notifikasi media di baki notifikasi. Di Android, Chrome akan melakukan yang terbaik untuk menampilkan informasi yang sesuai dengan menggunakan judul dokumen dan gambar ikon terbesar yang dapat ditemukan.

Mari kita lihat cara menyesuaikan notifikasi media ini dengan menetapkan beberapa metadata sesi media seperti judul, artis, nama album, dan karya seni dengan 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',
      },
    ],
  });
}

Setelah pemutaran selesai, Anda tidak perlu "melepaskan" sesi media karena notifikasi akan otomatis hilang. Perlu diingat bahwa navigator.mediaSession.metadata saat ini akan digunakan saat pemutaran dimulai. Itulah alasan Anda perlu memperbaruinya untuk memastikan Anda selalu menampilkan informasi yang relevan dalam notifikasi media.

Jika aplikasi web Anda menyediakan playlist, sebaiknya izinkan pengguna menjelajahi playlist langsung dari notifikasi media dengan beberapa ikon "Lagu Sebelumnya" dan "Lagu Berikutnya".

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

Perhatikan bahwa pengendali tindakan media akan tetap ada. Ini sangat mirip dengan pola pemroses peristiwa, kecuali bahwa menangani suatu peristiwa berarti browser berhenti melakukan perilaku default apa pun dan menggunakannya sebagai sinyal bahwa aplikasi web Anda mendukung tindakan media. Oleh karena itu, kontrol tindakan media tidak akan ditampilkan, kecuali Anda menetapkan pengendali tindakan yang tepat.

Selain itu, membatalkan penetapan pengendali tindakan media semudah menetapkannya ke null.

Media Session API memungkinkan Anda menampilkan ikon notifikasi media "Seek Backward" dan "Seek Forward" jika ingin mengontrol jumlah waktu yang dilewati.

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

Ikon "Putar/Jeda" selalu ditampilkan di notifikasi media dan peristiwa terkait ditangani secara otomatis oleh browser. Jika karena alasan tertentu perilaku default tidak berfungsi, Anda masih dapat menangani peristiwa media "Putar" dan "Jeda".

Hal yang menarik dari Media Session API adalah bahwa baki notifikasi bukan satu-satunya tempat metadata dan kontrol media terlihat. Notifikasi media disinkronkan secara otomatis ke perangkat wearable yang disambungkan. Selain itu, pesan juga akan muncul di layar kunci.

Masukan