Настраивайте медиа-уведомления и управляйте плейлистами

Франсуа Бофор
François Beaufort

Благодаря новому API-интерфейсу Media Session вы теперь можете настраивать медиа-уведомления , предоставляя метаданные для мультимедиа, воспроизводимого вашим веб-приложением. Это также позволяет вам обрабатывать события, связанные с мультимедиа, такие как поиск или отслеживание изменений, которые могут поступать из уведомлений или медиа-ключей. Взволнованный? Попробуйте официальные образцы Media Session .

API сеанса мультимедиа поддерживается в Chrome 57 (бета-версия выпущена в феврале 2017 г., стабильная версия — в марте 2017 г.).

Медиа-сессия TL;DR;
Фото Михаэля Ало-Нильсена / CC BY 2.0

Дай мне то, что я хочу

Вы уже знаете об API Media Session и просто возвращаетесь, чтобы без стыда скопировать и вставить какой-нибудь шаблонный код? Итак, вот оно.

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

Вникнуть в код

Давайте поиграем 🎷

Добавьте на свою веб-страницу простой элемент <audio> и назначьте несколько источников мультимедиа, чтобы браузер мог выбрать, какой из них работает лучше всего.

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

Как вы, возможно, знаете, autoplay отключено для аудиоэлементов в Chrome для Android, что означает, что нам нужно использовать метод play() аудиоэлемента. Этот метод должен запускаться жестом пользователя , например прикосновением или щелчком мыши. Это означает прослушивание событий pointerup , click и touchend . Другими словами, пользователь должен нажать кнопку, прежде чем ваше веб-приложение сможет начать шуметь.

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

Если вы не хотите воспроизводить звук сразу после первого взаимодействия, я рекомендую вам использовать метод load() элемента audio. Это один из способов браузера отслеживать, взаимодействовал ли пользователь с элементом. Обратите внимание, что это также может помочь сгладить воспроизведение, поскольку контент уже загружен.

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

Настройте уведомление

Когда ваше веб-приложение воспроизводит звук, вы уже можете видеть мультимедийное уведомление в области уведомлений. На Android Chrome делает все возможное, чтобы отображать соответствующую информацию, используя заголовок документа и самое большое изображение значка, которое он может найти.

Без медиа-сессии
Без медиа-сессии
С медиа-сессией
С медиа-сессией

Установить метаданные

Давайте посмотрим, как настроить это медиа-уведомление, установив некоторые метаданные медиа-сеанса, такие как название, исполнитель, название альбома и обложку, с помощью 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' },
    ]
    });
}

После завершения воспроизведения вам не нужно «освобождать» сеанс мультимедиа, поскольку уведомление автоматически исчезнет. Имейте в виду, что текущие navigator.mediaSession.metadata будут использоваться при запуске любого воспроизведения. Вот почему вам необходимо обновить его, чтобы всегда отображать соответствующую информацию в медиа-уведомлении.

Предыдущий трек/следующий трек

Если ваше веб-приложение предоставляет список воспроизведения, вы можете разрешить пользователю перемещаться по вашему списку воспроизведения непосредственно из мультимедийного уведомления с помощью значков «Предыдущий трек» и «Следующий трек».

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

Обратите внимание, что обработчики действий мультимедиа сохранятся. Это очень похоже на шаблон прослушивателя событий, за исключением того, что обработка события означает, что браузер прекращает выполнять какое-либо поведение по умолчанию и использует это как сигнал о том, что ваше веб-приложение поддерживает мультимедийное действие. Следовательно, элементы управления действиями мультимедиа не будут отображаться, если вы не установите правильный обработчик действий.

Кстати, отключить обработчик медиа-действия так же просто, как присвоить ему значение null .

Искать назад/искать вперед

API сеанса мультимедиа позволяет отображать значки уведомлений мультимедиа «Искать назад» и «Искать вперед», если вы хотите контролировать количество пропущенного времени.

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

Воспроизведение / пауза

Значок «Воспроизведение/Пауза» всегда отображается в мультимедийном уведомлении, а соответствующие события обрабатываются браузером автоматически. Если по какой-то причине поведение по умолчанию не работает, вы все равно можете обрабатывать медиа-события «Воспроизведение» и «Пауза».

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

Уведомления повсюду

Самое интересное в API сеанса мультимедиа то, что панель уведомлений — не единственное место, где видны метаданные мультимедиа и элементы управления. Мультимедийное уведомление автоматически синхронизируется с любым подключенным носимым устройством. И это также отображается на экранах блокировки.

Экран блокировки
Экран блокировки — фото Майкла Алё-Нильсена / CC BY 2.0
Уведомление об износе
Уведомление об износе

Сделайте так, чтобы игра играла хорошо в офлайн-режиме

Я знаю, о чем ты сейчас думаешь. Работник сервиса спешит на помощь!

Верно, но прежде всего вам нужно убедиться , что все пункты в этом контрольном списке проверены :

  • Все файлы мультимедиа и графических изображений обслуживаются с соответствующим HTTP-заголовком Cache-Control . Это позволит браузеру кэшировать и повторно использовать ранее полученные ресурсы. См. контрольный список кэширования .
  • Убедитесь, что все файлы мультимедиа и графических изображений обслуживаются с HTTP-заголовком Allow-Control-Allow-Origin: * . Это позволит сторонним веб-приложениям получать и использовать HTTP-ответы с вашего веб-сервера.

Стратегия кэширования сервис-воркера

Что касается медиа-файлов, я рекомендую простую стратегию « Кэш с возвратом к сети », как показано Джейком Арчибальдом.

Однако в отношении иллюстраций я бы был немного более конкретен и выбрал следующий подход:

  • If произведение искусства уже находится в кеше, подайте его из кеша.
  • Else получить иллюстрацию из сети.
    • If выборка прошла успешно, добавьте сетевое изображение в кеш и обслужите его.
    • Else можно использовать резервную иллюстрацию из кеша.

Таким образом, медиа-уведомления всегда будут иметь красивый значок в виде обложки, даже если браузер не сможет их получить. Вот как это можно реализовать:

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

Разрешить пользователю управлять кешем

Поскольку пользователь потребляет контент из вашего веб-приложения, файлы мультимедиа и графических изображений могут занимать много места на его устройстве. Вы несете ответственность за то, чтобы показать, какой объем кэша используется, и предоставить пользователям возможность его очистить . К счастью для нас, сделать это довольно легко с помощью 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); });

Замечания по реализации

  • Chrome для Android запрашивает «полный» фокус звука, чтобы отображать мультимедийные уведомления только в том случае, если продолжительность мультимедийного файла составляет не менее 5 секунд .
  • Обложки уведомлений поддерживают URL-адреса больших двоичных объектов и URL-адреса данных.
  • Если графическое изображение не определено и имеется изображение значка желаемого размера, мультимедийные уведомления будут использовать его.
  • Размер обложки уведомления в Chrome для Android составляет 512x512 . Для бюджетных устройств это 256x256 .
  • Отклоните медиа-уведомления с помощью audio.src = '' .
  • Поскольку API веб-аудио не запрашивает Android Audio Focus по историческим причинам, единственный способ заставить его работать с API сеанса мультимедиа — подключить элемент <audio> в качестве источника входных данных к API веб-аудио. Будем надеяться, что предлагаемый Web AudioFocus API улучшит ситуацию в ближайшем будущем.
  • Вызовы сеанса мультимедиа повлияют на медиа-уведомления, только если они происходят из того же кадра, что и медиа-ресурс. Смотрите фрагмент ниже.
<iframe id="iframe">
  <audio>...</audio>
</iframe>
<script>
  iframe.contentWindow.navigator.mediaSession.metadata = new MediaMetadata({
    title: 'Never Gonna Give You Up',
    ...
  });
</script>

Поддерживать

На момент написания Chrome для Android — единственная платформа, поддерживающая API медиасессии. Более актуальную информацию о статусе реализации браузера можно найти на странице «Состояние платформы Chrome» .

Образцы и демо

Ознакомьтесь с нашими официальными образцами Chrome Media Session , в которых представлены работы Blender Foundation и Яна Моргенштерна .

Ресурсы

Спецификация медиа-сессии: wicg.github.io/mediasession

Проблемы со спецификациями: github.com/WICG/mediasession/issues .

Ошибки Chrome: crbug.com