Pemutaran Video Web Seluler

Prancis Beaufort
François Beaufort

Bagaimana Anda menciptakan pengalaman media seluler terbaik di Web? Mudah. Semuanya bergantung pada engagement pengguna dan nilai penting yang Anda berikan pada media di halaman web. Saya pikir kita semua setuju bahwa jika video adalah alasan kunjungan pengguna, pengalaman pengguna harus imersif dan menarik kembali.

pemutaran video web seluler

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

Kontrol kustom

Tata Letak HTML
Gambar 1.Tata Letak HTML

Seperti yang 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 dikhususkan untuk kontrol video.

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

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

Membaca metadata video

Pertama-tama, mari tunggu metadata video dimuat untuk menyetel durasi video, waktu saat ini, dan melakukan inisialisasi status progres. Perhatikan bahwa fungsi secondsToTimeCode() adalah fungsi utilitas kustom yang saya tulis yang mengonversi jumlah 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. Media Player yang menampilkan metadata video

Putar/jeda video

Setelah metadata video dimuat, mari 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 peristiwa kontrol berbasis akan membantu fleksibilitas (seperti yang akan kita lihat nanti dalam Media Session API) dan akan memungkinkan kontrol kita tetap sinkron jika browser mengintervensi pemutaran. Saat video mulai diputar, kita mengubah status tombol menjadi "jeda" dan menyembunyikan kontrol video. Saat video dijeda, kita cukup mengubah status tombol menjadi "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 berubah melalui peristiwa video timeupdate, kami juga memperbarui kontrol kustom jika kontrol kustom tersebut terlihat.

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

Setelah video berakhir, kita cukup mengubah status tombol menjadi "putar", menyetel video currentTime kembali ke 0 dan menampilkan kontrol video untuk saat ini. Perhatikan bahwa kita juga dapat memilih untuk memuat video lain secara otomatis jika pengguna telah mengaktifkan semacam fitur "Putar Otomatis".

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

Mundur dan maju

Mari kita lanjutkan dan tambahkan tombol "cari mundur" dan "maju ke depan" sehingga 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 semudah filter: brightness(0);.

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

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

Berikut adalah indikator yang telah kami buat sejauh ini. Di bagian berikutnya, kita akan mengimplementasikan 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 perlu menggunakan semuanya. Cukup pilih salah satu yang menurut Anda sesuai dan gabungkan untuk membuat alur khusus.

Cegah layar penuh otomatis

Di iOS, elemen video secara otomatis masuk ke mode layar penuh saat pemutaran media dimulai. Karena kami mencoba menyesuaikan dan mengontrol sebanyak mungkin pengalaman media kami di seluruh browser seluler, sebaiknya setel atribut playsinline dari elemen video untuk memaksanya memutar secara inline di iPhone dan tidak masuk ke mode layar penuh saat pemutaran dimulai. Perhatikan bahwa hal ini tidak memiliki efek samping pada browser lain.

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

Aktifkan/nonaktifkan layar penuh saat tombol diklik

Karena sekarang kita mencegah layar penuh otomatis, kita harus menangani sendiri mode layar penuh untuk video dengan Fullscreen API. Saat pengguna mengklik "tombol layar penuh", mari keluar dari mode layar penuh dengan document.exitFullscreen() jika mode layar penuh saat ini sedang 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);
});

Aktifkan/nonaktifkan layar penuh saat orientasi layar diubah

Saat pengguna memutar perangkat dalam mode lanskap, mari kita gunakan hal ini dengan cerdas dan otomatis minta layar penuh untuk menciptakan pengalaman imersif. Untuk hal ini, kita memerlukan Screen Orientation API yang belum didukung di mana pun dan masih diawali di beberapa browser pada saat itu. Jadi, ini akan menjadi {i>progressive enhancement<i} kami.

Bagaimana cara kerjanya? Segera setelah kami mendeteksi perubahan orientasi layar, mari kita minta layar penuh jika jendela browser dalam mode lanskap (yaitu, lebarnya lebih besar dari tingginya). Jika tidak, mari 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();
    }
  });
}

Kunci layar dalam mode lanskap saat tombol diklik

Karena video dapat ditonton dengan lebih baik dalam mode lanskap, kita mungkin perlu mengunci layar dalam mode 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 mode lanskap semudah memanggil screen.orientation.lock('landscape'). Namun, kita hanya dapat melakukan ini saat perangkat dalam mode potret dengan matchMedia('(orientation: portrait)') dan dapat dipegang satu tangan dengan matchMedia('(max-device-width: 768px)') karena ini tidak akan menjadi pengalaman bagus 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');
  }
}

Buka kunci layar saat orientasi perangkat diubah

Anda mungkin memperhatikan 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 menyediakan informasi dari hardware yang mengukur posisi perangkat dan gerakan dalam ruang angkasa: giroskop dan kompas digital untuk orientasinya, serta akselerometer untuk kecepatannya. Saat kami 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 Anda lihat, ini adalah pengalaman layar penuh tanpa hambatan yang kami cari. Untuk melihat cara kerjanya, lihat contoh.

Pemutaran di latar belakang

Jika Anda mendeteksi halaman web atau video di halaman web tidak terlihat lagi, sebaiknya perbarui analisis untuk mencerminkan hal ini. Hal ini juga dapat memengaruhi pemutaran saat ini, seperti memilih trek lain, menjedanya, atau bahkan menampilkan tombol kustom kepada pengguna.

Jeda video saat perubahan visibilitas halaman

Dengan Page Visibility API, kami dapat menentukan visibilitas halaman saat ini dan diberi tahu jika ada 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 pelanjutan video yang dijeda, sebaiknya Anda menyetel perilaku ini hanya jika pengguna diizinkan untuk memutar video di latar belakang.

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

Tampilkan/sembunyikan tombol bisukan pada perubahan visibilitas video

Jika menggunakan Intersection Observer API baru, Anda dapat lebih terperinci tanpa biaya. API ini memberi tahu Anda kapan elemen yang diamati masuk 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 memberi pengguna kontrol 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 di satu halaman, sebaiknya Anda hanya memutar satu video dan menjeda video lainnya secara otomatis agar pengguna tidak perlu mendengar beberapa trek audio yang 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();
  });
}

Sesuaikan Notifikasi Media

Dengan Media Session API, Anda juga dapat menyesuaikan notifikasi media dengan memberikan metadata untuk video yang sedang diputar. Dengan demikian, Anda juga dapat menangani peristiwa terkait media seperti mencari atau melacak perubahan yang mungkin berasal dari notifikasi atau kunci media. Untuk melihat cara kerjanya, lihat contoh.

Saat aplikasi web memutar audio atau video, Anda sudah dapat melihat notifikasi media yang ada di baki notifikasi. Di Android, Chrome 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 menghilang. Perlu diingat bahwa navigator.mediaSession.metadata saat ini akan digunakan saat pemutaran dimulai. Oleh karena itu, 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. Pola ini sangat mirip dengan pola pemroses peristiwa, kecuali bahwa menangani peristiwa berarti browser berhenti melakukan perilaku default apa pun dan menggunakannya sebagai sinyal bahwa aplikasi web Anda mendukung tindakan media tersebut. Oleh karena itu, kontrol tindakan media tidak akan ditampilkan kecuali Anda menetapkan pengendali tindakan yang tepat.

Ngomong-ngomong, menghapus setelan 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 akan otomatis ditangani oleh browser. Jika karena alasan tertentu perilaku default tersebut tidak berhasil, Anda masih dapat menangani peristiwa media "Putar" dan "Jeda".

Hal menarik tentang Media Session API adalah baki notifikasi bukan satu-satunya tempat metadata dan kontrol media terlihat. Notifikasi media disinkronkan secara otomatis ke perangkat wearable yang disambungkan. Dan juga muncul di layar kunci.

Masukan