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