Con la nueva API de Media Session, ahora puedes personalizar las notificaciones de contenido multimedia proporcionando metadatos para el contenido multimedia que reproduce tu app web. También te permite controlar eventos relacionados con el contenido multimedia, como el saltado o el cambio de pista, que pueden provenir de notificaciones o teclas multimedia. ¿Estás entusiasmado? Prueba los ejemplos oficiales de Media Session.
La API de Media Session es compatible con Chrome 57 (beta en febrero de 2017 y estable en marzo de 2017).
Dame lo que quiero
¿Ya conoces la API de Media Session y solo regresas para copiar y pegar sin vergüenza un código de plantilla? Así que aquí está.
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() {});
}
Entra al código
Juguemos 🎷
Agrega un elemento <audio>
simple a tu página web y asígnale varias fuentes de contenido multimedia para que el navegador pueda elegir cuál funciona mejor.
<audio controls>
<source src="audio.mp3" type="audio/mp3"/>
<source src="audio.ogg" type="audio/ogg"/>
</audio>
Como sabes, autoplay
está inhabilitado para los elementos de audio en Chrome para Android, lo que significa que debemos usar el método play()
del elemento de audio. Este método debe activarse con un gesto del usuario, como un toque o un clic con el mouse.
Eso significa escuchar los eventos pointerup
, click
y touchend
. En otras palabras, el usuario debe hacer clic en un botón para que la app web pueda hacer ruido.
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) });
});
Si no quieres reproducir audio justo después de la primera interacción, te recomiendo que uses el método load()
del elemento de audio. Esta es una forma en que el navegador puede hacer un seguimiento de si el usuario interactuó con el elemento. Ten en cuenta que esto también puede ayudar a suavizar la reproducción, ya que el contenido ya estará cargado.
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) });
});
Cómo personalizar la notificación
Cuando tu app web reproduce audio, ya puedes ver una notificación multimedia en la bandeja de notificaciones. En Android, Chrome hace todo lo posible para mostrar la información adecuada con el título del documento y la imagen del ícono más grande que pueda encontrar.
Configurar metadatos
Veamos cómo personalizar esta notificación multimedia configurando algunos metadatos de la sesión multimedia, como el título, el artista, el nombre del álbum y el material gráfico con la API de Media Session.
// 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' },
]
});
}
Una vez que finalice la reproducción, no tendrás que “liberar” la sesión multimedia, ya que la notificación desaparecerá automáticamente. Ten en cuenta que se usará el navigator.mediaSession.metadata
actual cuando comience cualquier reproducción. Por este motivo, debes actualizarla para asegurarte de mostrar siempre información relevante en la notificación multimedia.
Pista anterior / siguiente
Si tu app web proporciona una playlist, te recomendamos que permitas que el usuario navegue por ella directamente desde la notificación multimedia con algunos íconos de "Pista anterior" y "Pista siguiente".
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();
});
Ten en cuenta que los controladores de acciones multimedia persistirán. Esto es muy similar al patrón de objeto de escucha de eventos, excepto que controlar un evento significa que el navegador deja de realizar cualquier comportamiento predeterminado y lo usa como una señal de que tu app web admite la acción multimedia. Por lo tanto, no se mostrarán los controles de acción multimedia, a menos que configures el controlador de acciones adecuado.
Por cierto, anular la configuración de un controlador de acción multimedia es tan fácil como asignarlo a null
.
Retroceder o adelantar
La API de Media Session te permite mostrar los íconos de notificación multimedia de "Retroceso" y "Avance rápido" si deseas controlar la cantidad de tiempo que se omite.
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);
});
Reproducir / pausar
El ícono de "Reproducir/pausar" siempre se muestra en la notificación multimedia, y el navegador controla automáticamente los eventos relacionados. Si, por algún motivo, el comportamiento predeterminado no funciona, puedes controlar los eventos multimedia "Reproducir" y "Pausar".
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...
});
Notificaciones en todas partes
Lo interesante de la API de Media Session es que la bandeja de notificaciones no es el único lugar donde se pueden ver los metadatos y los controles multimedia. La notificación multimedia se sincroniza automáticamente con cualquier dispositivo wearable vinculado. También aparece en las pantallas de bloqueo.
Cómo hacer que funcione sin conexión
Sé lo que estás pensando ahora. ¡El service worker al rescate!
Es cierto, pero lo primero que debes hacer es asegurarte de que se hayan marcado todos los elementos de esta lista de tareas:
- Todos los archivos multimedia y de material gráfico se entregan con el encabezado HTTP
Cache-Control
correspondiente. Esto permitirá que el navegador almacenen en caché y vuelvan a usar los recursos recuperados anteriormente. Consulta la lista de tareas para el almacenamiento en caché. - Asegúrate de que todos los archivos multimedia y de material gráfico se entreguen con el encabezado HTTP
Allow-Control-Allow-Origin: *
. Esto permitirá que las apps web de terceros recuperen y consuman respuestas HTTP de tu servidor web.
La estrategia de almacenamiento en caché del service worker
En el caso de los archivos multimedia, te recomiendo una estrategia simple de "caché con resguardo en la red", como lo ilustra Jake Archibald.
Sin embargo, en el caso del material gráfico, sería un poco más específico y elegiría el siguiente enfoque:
- El material gráfico de
If
ya está en la caché, así que se entrega desde allí Else
recupera material gráfico de la red.- La recuperación de
If
se realiza correctamente, se agrega el material gráfico de la cadena a la caché y se entrega Else
entrega el material gráfico de resguardo desde la caché
- La recuperación de
De esta manera, las notificaciones multimedia siempre tendrán un ícono de material gráfico atractivo, incluso cuando el navegador no pueda recuperarlas. A continuación, te mostramos cómo puedes implementarlo:
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));
}
Permite que el usuario controle la caché
A medida que el usuario consume contenido de tu app web, los archivos multimedia y de material gráfico pueden ocupar mucho espacio en su dispositivo. Es tu responsabilidad mostrar cuánto se usa la caché y permitir que los usuarios la borren. Afortunadamente, hacerlo es bastante fácil con la API de Cache.
// 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); });
Notas de la implementación
- Chrome para Android solicita el enfoque de audio "completo" para mostrar notificaciones multimedia solo cuando la duración del archivo multimedia es de al menos 5 segundos.
- El material gráfico de las notificaciones admite URLs de datos y URLs de blob.
- Si no se define ningún material gráfico y hay una imagen de ícono con un tamaño adecuado, las notificaciones multimedia la usarán.
- El tamaño del material gráfico de las notificaciones en Chrome para Android es
512x512
. Para los dispositivos de gama baja, es256x256
. - Descarta las notificaciones multimedia con
audio.src = ''
. - Como la API de Web Audio no solicita el enfoque de audio de Android por motivos históricos, la única manera de hacer que funcione con la API de Media Session es conectar un elemento
<audio>
como la fuente de entrada a la API de Web Audio. Con suerte, la API de Web AudioFocus propuesta mejorará la situación en un futuro cercano. - Las llamadas a Media Session afectarán a las notificaciones de contenido multimedia solo si provienen del mismo marco que el recurso multimedia. Consulta el siguiente fragmento.
<iframe id="iframe">
<audio>...</audio>
</iframe>
<script>
iframe.contentWindow.navigator.mediaSession.metadata = new MediaMetadata({
title: 'Never Gonna Give You Up',
...
});
</script>
Asistencia
En el momento de escribir este artículo, Chrome para Android es la única plataforma que admite la API de Media Session. Puedes encontrar información más actualizada sobre el estado de implementación en navegadores en el Estado de la plataforma de Chrome.
Demos y muestras
Consulta nuestros ejemplos oficiales de Media Session de Chrome, que incluyen Blender Foundation y el trabajo de Jan Morgenstern.
Recursos
Especificación de Media Session: wicg.github.io/mediasession
Problemas con las especificaciones: github.com/WICG/mediasession/issues
Errores de Chrome: crbug.com