Sesuaikan notifikasi media dan tangani playlist

François Beaufort
François Beaufort

Dengan Media Session API yang baru, kini Anda dapat menyesuaikan notifikasi media dengan memberikan metadata untuk media yang diputar oleh aplikasi web Anda. Hal ini juga memungkinkan Anda menangani peristiwa terkait media, seperti mencari atau melacak perubahan yang mungkin berasal dari notifikasi atau kunci media. Tertarik? Coba contoh Sesi Media resmi.

Media Session API didukung di Chrome 57 (versi beta pada Februari 2017, stabil pada Maret 2017).

TL;DR Sesi Media;
Foto oleh Michael Alø-Nielsen / CC BY 2.0

Berikan yang kuinginkan

Anda sudah tahu Media Session API dan baru kembali untuk menyalin dan menempelkan tanpa perlu malu dengan kode boilerplate? Jadi ini dia.

if ('mediaSession' in navigator) {

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

    navigator.mediaSession.setActionHandler('play', function() {});
    navigator.mediaSession.setActionHandler('pause', function() {});
    navigator.mediaSession.setActionHandler('seekbackward', function() {});
    navigator.mediaSession.setActionHandler('seekforward', function() {});
    navigator.mediaSession.setActionHandler('previoustrack', function() {});
    navigator.mediaSession.setActionHandler('nexttrack', function() {});
}

Memahami kode

Ayo main Persona

Tambahkan elemen <audio> sederhana ke halaman web Anda dan tetapkan beberapa sumber media sehingga browser dapat memilih sumber media yang paling efektif.

<audio controls>
    <source src="audio.mp3" type="audio/mp3"/>
    <source src="audio.ogg" type="audio/ogg"/>
</audio>

Seperti yang mungkin Anda ketahui, autoplay dinonaktifkan untuk elemen audio di Chrome untuk Android, yang berarti kita harus menggunakan metode play() elemen audio. Metode ini harus dipicu oleh gestur pengguna seperti sentuhan atau klik mouse. Artinya, memproses peristiwa pointerup, click, dan touchend. Dengan kata lain, pengguna harus mengklik tombol sebelum aplikasi web Anda benar-benar dapat membuat derau.

playButton.addEventListener('pointerup', function(event) {
    let audio = document.querySelector('audio');

    // User interacted with the page. Let's play audio...
    audio.play()
    .then(_ => { /* Set up media session... */ })
    .catch(error => { console.log(error) });
});

Jika Anda tidak ingin memutar audio tepat setelah interaksi pertama, sebaiknya gunakan metode load() elemen audio. Ini adalah salah satu cara bagi browser untuk melacak apakah pengguna berinteraksi dengan elemen. Perlu diketahui bahwa tindakan ini juga dapat membantu memperlancar pemutaran karena konten sudah dimuat.

let audio = document.querySelector('audio');

welcomeButton.addEventListener('pointerup', function(event) {
  // User interacted with the page. Let's load audio...
  <strong>audio.load()</strong>
  .then(_ => { /* Show play button for instance... */ })
  .catch(error => { console.log(error) });
});

// Later...
playButton.addEventListener('pointerup', function(event) {
  <strong>audio.play()</strong>
  .then(_ => { /* Set up media session... */ })
  .catch(error => { console.log(error) });
});

Menyesuaikan notifikasi

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

Tanpa sesi media
Tanpa sesi media
Dengan sesi media
Dengan sesi media

Menetapkan metadata

Mari kita lihat cara menyesuaikan notifikasi media ini dengan menyetel beberapa metadata sesi media seperti judul, artis, nama album, dan karya seni dengan Media Session API.

// When audio starts playing...
if ('mediaSession' in navigator) {

    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 "merilis" 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.

Lagu sebelumnya / lagu berikutnya

Jika aplikasi web Anda menyediakan playlist, Anda mungkin ingin mengizinkan pengguna membuka playlist langsung dari notifikasi media dengan beberapa ikon "Lagu Sebelumnya" dan "Lagu Berikutnya".

let audio = document.createElement('audio');

let playlist = ['audio1.mp3', 'audio2.mp3', 'audio3.mp3'];
let index = 0;

navigator.mediaSession.setActionHandler('previoustrack', function() {
    // User clicked "Previous Track" media notification icon.
    index = (index - 1 + playlist.length) % playlist.length;
    playAudio();
});

navigator.mediaSession.setActionHandler('nexttrack', function() {
    // User clicked "Next Track" media notification icon.
    index = (index + 1) % playlist.length;
    playAudio();
});

function playAudio() {
    audio.src = playlist[index];
    audio.play()
    .then(_ => { /* Set up media session... */ })
    .catch(error => { console.log(error); });
}

playButton.addEventListener('pointerup', function(event) {
    playAudio();
});

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

Ngomong-ngomong, membatalkan penetapan pengendali tindakan media semudah menetapkannya ke null.

Mundur / cari maju

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

let skipTime = 10; // Time to skip in seconds

navigator.mediaSession.setActionHandler('seekbackward', function() {
    // User clicked "Seek Backward" media notification icon.
    audio.currentTime = Math.max(audio.currentTime - skipTime, 0);
});

navigator.mediaSession.setActionHandler('seekforward', function() {
    // User clicked "Seek Forward" media notification icon.
    audio.currentTime = Math.min(audio.currentTime + skipTime, audio.duration);
});

Memutar / menjeda

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

navigator.mediaSession.setActionHandler('play', function() {
    // User clicked "Play" media notification icon.
    // Do something more than just playing current audio...
});

navigator.mediaSession.setActionHandler('pause', function() {
    // User clicked "Pause" media notification icon.
    // Do something more than just pausing current audio...
});

Notifikasi di mana saja

Hal yang 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 itu juga muncul di layar kunci.

Layar Kunci
Lock Screen - Foto oleh Michael Alø-Nielsen / CC BY 2.0
Notifikasi Wear
Notifikasi Wear

Putar musik dengan nyaman saat offline

Aku tahu apa yang kamu pikirkan sekarang. Pekerja layanan siap menolong.

Benar, tetapi yang terpenting, Anda ingin memastikan semua item dalam checklist ini telah dicentang:

  • Semua file media dan poster ditayangkan dengan header HTTP Cache-Control yang sesuai. Hal ini akan memungkinkan browser meng-cache dan menggunakan kembali resource yang diambil sebelumnya. Lihat Checklist penyimpanan dalam cache.
  • Pastikan semua file media dan poster ditayangkan dengan header HTTP Allow-Control-Allow-Origin: *. Tindakan ini akan memungkinkan aplikasi web pihak ketiga mengambil dan menggunakan respons HTTP dari server web Anda.

Strategi penyimpanan dalam cache pekerja layanan

Mengenai file media, saya merekomendasikan strategi "Cache, fallback to network" sederhana seperti yang diilustrasikan oleh Jake Archibald.

Untuk poster, saya akan sedikit lebih spesifik dan memilih pendekatan di bawah ini:

  • Poster If sudah ada di cache, tayangkan dari cache
  • Else mengambil karya seni dari jaringan
    • Pengambilan If berhasil, tambahkan poster jaringan ke cache dan tayangkan
    • Else menayangkan poster penggantian dari cache

Dengan demikian, notifikasi media akan selalu memiliki ikon poster yang bagus meskipun browser tidak dapat mengambilnya. Berikut cara menerapkannya:

const FALLBACK_ARTWORK_URL = 'fallbackArtwork.png';

addEventListener('install', event => {
    self.skipWaiting();
    event.waitUntil(initArtworkCache());
});

function initArtworkCache() {
    caches.open('artwork-cache-v1')
    .then(cache => cache.add(FALLBACK_ARTWORK_URL));
}

addEventListener('fetch', event => {
    if (/artwork-[0-9]+\.png$/.test(event.request.url)) {
    event.respondWith(handleFetchArtwork(event.request));
    }
});

function handleFetchArtwork(request) {
    // Return cache request if it's in the cache already, otherwise fetch
    // network artwork.
    return getCacheArtwork(request)
    .then(cacheResponse => cacheResponse || getNetworkArtwork(request));
}

function getCacheArtwork(request) {
    return caches.open('artwork-cache-v1')
    .then(cache => cache.match(request));
}

function getNetworkArtwork(request) {
    // Fetch network artwork.
    return fetch(request)
    .then(networkResponse => {
    if (networkResponse.status !== 200) {
        return Promise.reject('Network artwork response is not valid');
    }
    // Add artwork to the cache for later use and return network response.
    addArtworkToCache(request, networkResponse.clone())
    return networkResponse;
    })
    .catch(error => {
    // Return cached fallback artwork.
    return getCacheArtwork(new Request(FALLBACK_ARTWORK_URL))
    });
}

function addArtworkToCache(request, response) {
    return caches.open('artwork-cache-v1')
    .then(cache => cache.put(request, response));
}

Izinkan pengguna mengontrol cache

Karena pengguna menggunakan konten dari aplikasi web Anda, file media dan karya seni dapat memerlukan banyak ruang di perangkat mereka. Anda bertanggung jawab untuk menunjukkan seberapa banyak cache yang digunakan dan memberi pengguna kemampuan untuk menghapusnya. Untungnya, kami dapat melakukannya dengan mudah menggunakan Cache API.

// Here's how I'd compute how much cache is used by artwork files...
caches.open('artwork-cache-v1')
.then(cache => cache.matchAll())
.then(responses => {
    let cacheSize = 0;
    let blobQueue = Promise.resolve();

    responses.forEach(response => {
    let responseSize = response.headers.get('content-length');
    if (responseSize) {
        // Use content-length HTTP header when possible.
        cacheSize += Number(responseSize);
    } else {
        // Otherwise, use the uncompressed blob size.
        blobQueue = blobQueue.then(_ => response.blob())
            .then(blob => { cacheSize += blob.size; blob.close(); });
    }
    });

    return blobQueue.then(_ => {
    console.log('Artwork cache is about ' + cacheSize + ' Bytes.');
    });
})
.catch(error => { console.log(error); });

// And here's how to delete some artwork files...
const artworkFilesToDelete = ['artwork1.png', 'artwork2.png', 'artwork3.png'];

caches.open('artwork-cache-v1')
.then(cache => Promise.all(artworkFilesToDelete.map(artwork => cache.delete(artwork))))
.catch(error => { console.log(error); });

Catatan penerapan

  • Chrome untuk Android meminta fokus audio "penuh" untuk menampilkan notifikasi media hanya jika durasi file media adalah setidaknya 5 detik.
  • Poster notifikasi mendukung URL blob dan URL data.
  • Jika tidak ada karya seni yang ditentukan dan ada gambar ikon dengan ukuran yang diinginkan, notifikasi media akan menggunakannya.
  • Ukuran poster notifikasi di Chrome untuk Android adalah 512x512. Untuk perangkat kelas bawah, nilainya adalah 256x256.
  • Tutup notifikasi media dengan audio.src = ''.
  • Karena Web Audio API tidak meminta Fokus Audio Android karena alasan historis, satu-satunya cara untuk membuatnya berfungsi dengan Media Session API adalah dengan menghubungkan elemen <audio> sebagai sumber input ke Web Audio API. Semoga Web AudioFocus API yang diusulkan akan memperbaiki situasi ini dalam waktu dekat.
  • Panggilan Sesi Media akan memengaruhi notifikasi media hanya jika panggilan tersebut berasal dari frame yang sama dengan resource media. Lihat cuplikan di bawah.
<iframe id="iframe">
  <audio>...</audio>
</iframe>
<script>
  iframe.contentWindow.navigator.mediaSession.metadata = new MediaMetadata({
    title: 'Never Gonna Give You Up',
    ...
  });
</script>

Dukungan

Pada saat ini ditulis, Chrome untuk Android adalah satu-satunya platform yang mendukung Media Session API. Informasi terbaru lebih lanjut tentang status penerapan browser dapat ditemukan di Status Platform Chrome.

Contoh & demo

Lihat contoh Sesi Media Chrome resmi kami yang menampilkan Blender Foundation dan karya Jan Morgenstern.

Referensi

Spesifikasi Sesi Media: wicg.github.io/mediasession

Masalah Spesifikasi: github.com/WICG/mediasession/issues

Bug Chrome: crbug.com