Personaliza las notificaciones multimedia y administra listas de reproducción

François Beaufort
François Beaufort

Con la nueva API de Media Session, ahora puedes personalizar las notificaciones multimedia proporcionando metadatos del contenido multimedia que reproduce tu app web. También te permite controlar eventos relacionados con contenido multimedia, como búsquedas o cambios de seguimiento que pueden provenir de notificaciones o teclas multimedia. ¿Estás entusiasmado? Prueba las muestras oficiales de sesiones multimedia.

La API de Media Session es compatible con Chrome 57 (versión beta en febrero de 2017; versión estable en marzo de 2017).

Resumen de las sesiones multimedia
Foto de Michael Alø-Nielsen / CC BY 2.0

Quiero lo que quiero

¿Ya conoces la API de Media Session y solo vuelves a copiar y pegar sin avergonzarte de usar código estándar? 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() {});
}

Ingresa al código

Juguemos a...

Agrega un elemento <audio> simple a tu página web y asigna varias fuentes 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 ya sabrás, 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 se debe activar mediante un gesto del usuario, como un toque o un clic con el mouse. Esto significa escuchar eventos pointerup, click y touchend. En otras palabras, el usuario debe hacer clic en un botón antes de que tu 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 inmediatamente después de la primera interacción, te recomendamos que uses el método load() del elemento de audio. De esta forma, el navegador puede realizar un seguimiento de si el usuario interactuó con el elemento. Ten en cuenta que también puede ayudar a mejorar la reproducción debido a 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 la app web está reproduciendo 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 mediante el título del documento y la imagen de ícono más grande que pueda encontrar.

Sin sesión multimedia
Sin sesión multimedia
Con sesión multimedia
Con sesión multimedia

Configurar metadatos

Veamos cómo personalizar esta notificación multimedia. Para ello, configura 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 termine 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 objeto navigator.mediaSession.metadata actual cuando se inicie cualquier reproducción. Es por eso que debes actualizarla para asegurarte de mostrar siempre información relevante en la notificación multimedia.

Pista anterior / pista siguiente

Si la app web proporciona una playlist, es posible que quieras permitir que el usuario navegue por ella directamente desde la notificación de contenido 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 se conservarán los controladores de acciones multimedia. Esto es muy similar al patrón del objeto de escucha de eventos, con la excepción de que controlar un evento significa que el navegador deja de ejecutar cualquier comportamiento predeterminado y lo usa como indicador de que tu app web admite la acción multimedia. Por lo tanto, los controles de acciones multimedia no se mostrarán, a menos que configures el controlador de acciones adecuado.

Por cierto, anular la configuración de un controlador de acciones multimedia es tan fácil como asignarlo a null.

Retroceder / Avanzar

La API de Media Session te permite mostrar los íconos de notificaciones multimedia "Seek Backward" y "Seek Forward" si quieres 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. Aunque, 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 bueno de la API de Media Session es que la bandeja de notificaciones no es el único lugar en el que los metadatos y los controles multimedia son visibles. La notificación multimedia se sincroniza automáticamente con cualquier dispositivo wearable vinculado. Y también aparece en las pantallas de bloqueo.

Bloquear la pantalla
Pantalla de bloqueo - Foto de Michael Alø-Nielsen / CC BY 2.0
Notificación de Wear
Notificación de Wear

Haz que se reproduzca sin conexión

Sé lo que estás pensando ahora. Service worker al rescate

Verdadero, pero primero debes asegurarte de que todos los elementos de esta lista de tareas estén marcados:

  • Todos los archivos multimedia y de material gráfico se entregan con el encabezado HTTP Cache-Control adecuado. Esto permitirá que el navegador almacene en caché y reutilice los recursos obtenidos con anterioridad. Consulta la Lista de tareas de almacenamiento en caché.
  • Asegúrate de que todos los archivos multimedia y de material gráfico se publiquen 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

Con respecto a los archivos multimedia, recomendamos una estrategia simple de "Caché, recurrir a 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é; publícalo desde la caché
  • Else recuperar material gráfico de la red
    • La recuperación de If se realizó correctamente. Agrega el material gráfico de la red a la caché y publícala
    • Else publica el material gráfico de resguardo de la caché

De esa manera, las notificaciones de contenido multimedia siempre tendrán un buen ícono de material gráfico, incluso cuando el navegador no pueda recuperarlos. Puedes hacerlo de la siguiente manera:

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

Permitir que el usuario controle la caché

Como el usuario consume contenido de tu app web, los archivos multimedia y de material gráfico pueden ocupar mucho espacio en el dispositivo. Tu responsabilidad es mostrar la cantidad de caché que se usa y darles a los usuarios la capacidad de borrarla. Afortunadamente para nosotros, es bastante fácil hacerlo 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 foco 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 la notificación admite URLs de BLOB y URLs de datos.
  • Si no se define ningún material gráfico y hay una imagen de ícono con un tamaño deseable, las notificaciones de contenido multimedia la usarán.
  • El tamaño del material gráfico de las notificaciones en Chrome para Android es 512x512. En el caso de los dispositivos de gama baja, es 256x256.
  • Descartar las notificaciones multimedia con audio.src = ''
  • Como la API de Web Audio no solicita Android Audio Focus por razones históricas, la única manera de lograr 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 el futuro cercano.
  • Las llamadas de sesiones multimedia afectarán las notificaciones multimedia solo si provienen del mismo fotograma 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

Al momento de escribir, 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 del navegador en Estado de la plataforma de Chrome.

Muestras y demostraciones

Consulta nuestras muestras oficiales de sesiones multimedia de Chrome en las que se destacan Blender Foundation y el trabajo de Jan Morgenstern.

Recursos

Especificaciones de la sesión multimedia: wicg.github.io/mediasession

Problemas de especificaciones: github.com/WICG/mediasession/issues

Errores de Chrome: crbug.com