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 menyediakan metadata untuk media yang sedang diputar aplikasi web Anda. API ini juga memungkinkan Anda menangani peristiwa terkait media seperti mencari atau melacak perubahan yang mungkin berasal dari notifikasi atau tombol media. Tertarik? Coba contoh Sesi Media resmi.

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

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

Beri saya apa yang saya inginkan

Anda sudah mengetahui Media Session API dan hanya kembali untuk menyalin dan menempelkan beberapa kode boilerplate tanpa malu? 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() {});
}

Mempelajari kode

Ayo main 🎷

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

<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() dari 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 tidak ingin memutar audio tepat setelah interaksi pertama, sebaiknya gunakan metode load() dari elemen audio. Hal ini adalah salah satu cara bagi browser untuk melacak apakah pengguna berinteraksi dengan elemen. Perhatikan bahwa tindakan ini juga dapat membantu melancarkan 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 menetapkan beberapa metadata sesi media seperti judul, artis, nama album, dan karya seni menggunakan 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 "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.

Lagu sebelumnya / lagu berikutnya

Jika aplikasi web Anda menyediakan playlist, sebaiknya izinkan pengguna menjelajahi 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. Hal ini sangat mirip dengan pola pemroses peristiwa, kecuali bahwa menangani peristiwa berarti browser berhenti melakukan perilaku default dan menggunakan ini sebagai sinyal bahwa aplikasi web Anda mendukung tindakan media. Oleh karena itu, kontrol tindakan media tidak akan ditampilkan kecuali jika Anda menetapkan pengendali tindakan yang sesuai.

Sekadar informasi, membatalkan setelan pengendali tindakan media semudah menetapkannya ke null.

Mundur / 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 ditangani secara otomatis oleh browser. Jika karena alasan tertentu perilaku default tidak berfungsi, Anda tetap 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 dari Media Session API adalah baki notifikasi bukan satu-satunya tempat di mana 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

Buat video menarik saat offline

Saya tahu apa yang Anda pikirkan sekarang. Pekerja layanan dapat membantu.

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. Tindakan ini akan memungkinkan browser menyimpan dalam cache dan menggunakan kembali resource yang diambil sebelumnya. Lihat Checklist penyimpanan dalam cache.
  • Pastikan semua file media dan karya seni ditayangkan dengan header HTTP Allow-Control-Allow-Origin: *. Hal ini akan memungkinkan aplikasi web pihak ketiga mengambil dan menggunakan respons HTTP dari server web Anda.

Strategi caching pekerja layanan

Sehubungan dengan file media, sebaiknya gunakan strategi "Cache, kembali ke jaringan" sederhana seperti yang diilustrasikan oleh Jake Archibald.

Namun, untuk karya seni, saya akan sedikit lebih spesifik dan memilih pendekatan di bawah:

  • Poster If sudah ada di cache, tayangkan dari cache
  • Else mengambil poster 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

Saat pengguna menggunakan konten dari aplikasi web Anda, file media dan karya seni dapat menghabiskan banyak ruang di perangkat mereka. Anda bertanggung jawab untuk menunjukkan berapa banyak cache yang digunakan dan memberi pengguna kemampuan untuk menghapusnya. Untungnya bagi kita, melakukannya cukup mudah dengan 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 hanya akan memengaruhi notifikasi media jika 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 penulisan, Chrome untuk Android adalah satu-satunya platform yang mendukung Media Session API. Informasi terbaru tentang status implementasi browser dapat ditemukan di Status Platform Chrome.

Contoh & demo

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

Resource

Spesifikasi Sesi Media: wicg.github.io/mediasession

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

Bug Chrome: crbug.com