Dostosuj powiadomienia o multimediach i zarządzaj playlistami

François Beaufort
François Beaufort

Dzięki zupełnie nowemu interfejsowi Media Session API możesz teraz dostosowywać powiadomienia o multimediach przez udostępnienie metadanych multimediów odtwarzanych w Twojej aplikacji internetowej. Umożliwia też obsługę zdarzeń związanych z multimediami, takich jak przewijanie lub śledzenie zmian, które mogą pochodzić z powiadomień lub klawiszy multimedialnych. Brzmi dobrze? Wypróbuj oficjalne przykłady sesji medialnych.

Interfejs Media Session API jest obsługiwany w Chrome 57 (wersja beta w lutym 2017 r.; wersja stabilna w marcu 2017 r.).

Sesja medialna TL;DR;
Zdjęcie : Michael Alø-Nielsen / CC BY 2.0

Daj mi to, co chcę

Znasz już interfejs Media Session API, ale wracasz do kopiowania i wklejania bez wstydu? Gotowe.

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

Wejdź do kodu

Zagrajmy 🎷

Dodaj do swojej strony internetowej prosty element <audio> i przypisz kilka źródeł multimediów, by przeglądarka mogła wybrać to, które sprawdza się najlepiej.

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

Jak być może wiesz, w Chrome na Androida funkcja autoplay jest wyłączona w przypadku elementów audio, co oznacza, że musimy używać metody play() elementu audio. Ta metoda musi być aktywowana przez gest użytkownika, np. dotknięcie lub kliknięcie myszą. Oznacza to odsłuchiwanie zdarzeń pointerup, click i touchend. Inaczej mówiąc, użytkownik musi kliknąć przycisk, aby Twoja aplikacja internetowa mogła wydać dźwięk.

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

Jeśli nie chcesz odtwarzać dźwięku od razu po pierwszej interakcji, zalecamy użycie metody load() elementu audio. Dzięki temu przeglądarka może śledzić, czy użytkownik wszedł w interakcję z elementem. Pamiętaj, że może to też pomóc w zapewnieniu płynności odtwarzania, bo treści zostaną już wczytane.

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

Dostosowywanie powiadomienia

Gdy Twoja aplikacja internetowa odtwarza dźwięk, w obszarze powiadomień zobaczysz powiadomienie o multimediach. Na Androidzie Chrome stara się pokazać odpowiednie informacje, używając tytułu dokumentu i największej ikony, jaką może znaleźć.

Bez sesji multimediów
Bez sesji multimediów
Z sesją multimediów
Z sesją multimediów

Ustaw metadane

Zobaczmy, jak dostosować to powiadomienie o multimediach, ustawiając metadane sesji multimedialnej, np. tytuł, wykonawcę, nazwę albumu i grafikę, za pomocą interfejsu 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' },
    ]
    });
}

Po zakończeniu odtwarzania nie musisz „zwalniać” sesji multimediów, ponieważ powiadomienie automatycznie zniknie. Pamiętaj, że bieżąca wartość navigator.mediaSession.metadata zostanie użyta przy rozpoczęciu odtwarzania. Dlatego musisz go zaktualizować, aby mieć pewność, że w powiadomieniach multimedialnych zawsze pojawiają się istotne informacje.

Poprzedni utwór / następny utwór

Jeśli Twoja aplikacja internetowa zawiera playlistę, możesz pozwolić użytkownikowi na poruszanie się po niej bezpośrednio z powiadomień o multimediach za pomocą ikon „Poprzedni utwór” i „Następny utwór”.

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

Pamiętaj, że moduły obsługi działań związanych z multimediami nie zostaną usunięte. Działa to bardzo podobnie do wzorca odbiornika, z tym że obsługa zdarzenia oznacza, że przeglądarka przestaje wykonywać domyślne działania i używa go jako sygnału, że aplikacja internetowa obsługuje akcję multimedialną. Ustawienia działań związanych z multimediami nie będą więc widoczne, jeśli nie ustawisz odpowiedniego modułu.

Przy okazji – anulowanie ustawienia modułu obsługi działań związanych z multimediami jest równie łatwe jak przypisanie go do: null.

Przewijanie do tyłu lub do przodu

Interfejs Media Session API pozwala wyświetlać ikony powiadomień o multimediach „Przewiń do tyłu” i „Przewiń do przodu”, jeśli chcesz kontrolować ilość czasu pomijanego.

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

Odtwarzanie / wstrzymywanie

W powiadomieniu o multimediach jest zawsze widoczna ikona „Odtwórz/wstrzymaj”, a powiązane z nią zdarzenia są obsługiwane automatycznie przez przeglądarkę. Jeśli z jakiegoś powodu domyślne działanie nie przyniesie oczekiwanych rezultatów, nadal możesz obsługiwać zdarzenia multimedialne „Odtwórz” i „Wstrzymaj”.

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

Powiadomienia wszędzie

Ciekawe w interfejsie Media Session API jest to, że obszar powiadomień nie jest jedynym miejscem, w którym widoczne są metadane i elementy sterujące. Powiadomienie o multimediach jest automagicznie synchronizowane z dowolnym sparowanym urządzeniem do noszenia. Wyświetla się też na ekranie blokady.

Ekran blokady
Lock Screen – zdjęcie : Michael Alø-Nielsen / CC BY 2.0
Powiadomienie z Wear
Powiadomienie na Wear

Dobrze baw się offline

Wiem, co teraz myślisz. Serwisant na ratunek!

Prawda, ale przede wszystkim sprawdź, czy wszystkie elementy na tej liście kontrolnej są zaznaczone:

  • Wszystkie pliki multimedialne i grafiki mają odpowiedni nagłówek HTTP Cache-Control. Dzięki temu przeglądarka będzie mogła buforować i ponownie wykorzystywać pobrane wcześniej zasoby. Zobacz Listę kontrolną dotyczącą buforowania.
  • Upewnij się, że wszystkie pliki multimedialne i grafiki mają nagłówek HTTP Allow-Control-Allow-Origin: *. Dzięki temu aplikacje internetowe innych firm będą mogły pobierać i wykorzystywać odpowiedzi HTTP z Twojego serwera WWW.

Strategia buforowania skryptu service worker

Jeśli chodzi o pliki multimedialne, polecamy prostą strategię „Cache, fasting back to network”, zgodnie z ilustracją Jake'a Archibalda.

W przypadku elementów graficznych użyję więcej szczegółów i wybierz metodę poniżej:

  • Dzieło sztuki If jest już w pamięci podręcznej, wyświetl je z pamięci podręcznej
  • Else pobierze grafikę z sieci
    • Plik If został pobrany, dodaj grafikę sieci do pamięci podręcznej i wyświetl ją
    • Else wyświetla grafikę zastępczą z pamięci podręcznej

Dzięki temu powiadomienia o multimediach zawsze będą miały ładną ikonę grafiki, nawet jeśli przeglądarka nie może ich pobrać. Aby to zrobić:

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

Pozwól użytkownikowi na kontrolowanie pamięci podręcznej

Gdy użytkownik korzysta z treści z Twojej aplikacji internetowej, pliki multimedialne i grafiki mogą zajmować dużo miejsca na urządzeniu. To Ty ponosisz odpowiedzialność za pokazanie, ile pamięci podręcznej jest używana, i zapewnienie użytkownikom możliwości jej wyczyszczenia. Na szczęście za pomocą interfejsu Cache API jest to całkiem łatwe.

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

Uwagi dotyczące implementacji

  • Chrome na Androida prosi o „pełne” zaznaczenie dźwięku, aby pokazywać powiadomienia o multimediach tylko wtedy, gdy czas trwania pliku multimedialnego wynosi co najmniej 5 sekund.
  • Grafika powiadomień obsługuje adresy URL obiektów blob i adresów URL danych.
  • Jeśli nie zdefiniowano żadnej grafiki, a obraz ikony ma odpowiedni rozmiar, będzie używany w powiadomieniach o multimediach.
  • Rozmiar grafiki powiadomień w Chrome na Androida to 512x512. W przypadku urządzeń mniej zaawansowanych jest to 256x256.
  • Zamknij powiadomienia o multimediach na urządzeniu audio.src = ''.
  • Interfejs Web Audio API nie wysyła żądania Android Audio Focus z przyczyn historycznych, dlatego jedynym sposobem na umożliwienie jego współdziałania z interfejsem Media Session API jest podłączenie elementu <audio> jako źródła danych wejściowych do interfejsu Web Audio API. Mamy nadzieję, że proponowany interfejs Web AudioFocus API pomoże w niedalekiej przyszłości poprawić sytuację.
  • Wywołania sesji multimediów mają wpływ na powiadomienia o multimediach tylko wtedy, gdy pochodzą z tej samej klatki co zasób multimedialny. Zobacz ten fragment kodu.
<iframe id="iframe">
  <audio>...</audio>
</iframe>
<script>
  iframe.contentWindow.navigator.mediaSession.metadata = new MediaMetadata({
    title: 'Never Gonna Give You Up',
    ...
  });
</script>

Pomoc

Obecnie Chrome na Androida jest jedyną platformą, która obsługuje interfejs Media Session API. Aktualne informacje o stanie implementacji przeglądarki znajdziesz w artykule Stan platformy Chrome.

Sample i wersje demonstracyjne

Zobacz nasze oficjalne przykłady sesji multimedialnych w Chrome, w których m.in. Blender Foundation i dzieła Jana Morgensterna.

Zasoby

Specyfikacja sesji multimediów: wicg.github.io/mediasession

Problemy ze specyfikacją: github.com/WICG/mediasession/issues

Błędy w Chrome: crbug.com