Com a nova API Media Session, agora é possível personalizar notificações de mídia fornecendo metadados para a mídia que o app da Web está reproduzindo. Ele também permite processar eventos relacionados à mídia, como a busca ou a mudança de faixa, que podem vir de notificações ou teclas de mídia. Gostou? Teste os exemplos oficiais de sessão de mídia.
A API Media Session tem suporte no Chrome 57 (Beta em fevereiro de 2017, estável em março de 2017).
Gimme what I want
Você já conhece a API Media Session e está voltando para copiar e colar sem vergonha algum código boilerplate? Aqui está ele.
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() {});
}
Acessar o código
Vamos jogar 🎷
Adicione um elemento <audio>
simples à sua página da Web e atribua várias fontes de mídia para
que o navegador possa escolher qual funciona melhor.
<audio controls>
<source src="audio.mp3" type="audio/mp3"/>
<source src="audio.ogg" type="audio/ogg"/>
</audio>
Como você deve saber, o autoplay
está desativado para elementos de áudio no Chrome
para Android, o que significa que precisamos usar o método play()
do elemento
de áudio. Esse método precisa ser acionado por um gesto do usuário, como um toque ou um
clique do mouse.
Isso significa detectar os eventos
pointerup
, click
e
touchend
. Em outras palavras, o usuário precisa clicar em um botão antes que o app da Web possa
fazer barulho.
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) });
});
Se você não quiser reproduzir o áudio logo após a primeira interação, recomendamos
usar o método load()
do elemento de áudio. Essa é uma maneira de o
navegador acompanhar se o usuário interagiu com o elemento. Isso também
pode ajudar a suavizar a reprodução, porque o conteúdo já estará
carregado.
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) });
});
Personalizar a notificação
Quando o app da Web está reproduzindo áudio, você já pode ver uma notificação de mídia na bandeja de notificações. No Android, o Chrome faz o possível para mostrar informações adequadas usando o título do documento e a imagem de ícone maior que puder encontrar.
Definir metadados
Vamos personalizar essa notificação de mídia definindo alguns metadados da sessão de mídia, como título, artista, nome do álbum e arte com a API 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' },
]
});
}
Depois que a reprodução terminar, você não precisará "liberar" a sessão de mídia, porque a
notificação vai desaparecer automaticamente. O
navigator.mediaSession.metadata
atual será usado quando qualquer reprodução começar. Por isso,
é necessário fazer a atualização para garantir que as informações
relevantes sejam sempre mostradas na notificação de mídia.
Faixa anterior / próxima faixa
Se o seu app da Web oferecer uma playlist, permita que o usuário navegue por ela diretamente da notificação de mídia com alguns ícones de "Faixa anterior" e "Faixa seguinte".
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();
});
Os manipuladores de ação de mídia vão persistir. Isso é muito semelhante ao padrão de listener de eventos, exceto que o processamento de um evento significa que o navegador para de fazer qualquer comportamento padrão e usa isso como um sinal de que seu app da Web oferece suporte à ação de mídia. Portanto, os controles de ação de mídia não serão mostrados, a menos que você defina o gerenciador de ação adequado.
A propósito, é fácil desativar um gerenciador de ação de mídia, basta atribuir a null
.
Voltar / avançar
A API Media Session permite mostrar ícones de notificação de mídia "Procurar para trás" e "Procurar para frente" se você quiser controlar a quantidade de tempo pulado.
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);
});
Reproduzir / pausar
O ícone "Reproduzir/pausar" é sempre mostrado na notificação de mídia, e os eventos relacionados são processados automaticamente pelo navegador. Se, por algum motivo, o comportamento padrão não funcionar, você ainda poderá processar eventos de mídia "Play" e "Pause".
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...
});
Notificações em todos os lugares
O legal da API MediaSession é que a bandeja de notificações não é o único lugar em que os metadados e controles de mídia ficam visíveis. A notificação de mídia é sincronizada automaticamente com qualquer dispositivo portátil pareado. Ele também aparece na tela de bloqueio.
Deixar o conteúdo off-line mais agradável
Sei o que você está pensando. Service worker ao resgate!
É verdade, mas antes de tudo, verifique se todos os itens desta lista de verificação estão marcados:
- Todos os arquivos de mídia e arte são veiculados com o cabeçalho HTTP
Cache-Control
adequado. Isso permite que o navegador armazene em cache e reutilize recursos buscados anteriormente. Consulte a lista de verificação de armazenamento em cache. - Confira se todos os arquivos de mídia e arte são veiculados com o
cabeçalho HTTP
Allow-Control-Allow-Origin: *
. Isso permite que apps da Web de terceiros busquem e consumam respostas HTTP do seu servidor da Web.
A estratégia de armazenamento em cache do service worker
Em relação aos arquivos de mídia, recomendo uma estratégia simples de cache, com fallback para a rede, como ilustrado por Jake Archibald.
No caso de artes, porém, eu seria um pouco mais específico e escolheria a abordagem abaixo:
- A arte do
If
já está no cache, então mostre-a Else
buscar artes da rede- O fetch de
If
foi concluído, adicione a arte da rede ao cache e a exiba Else
veicular a arte de substituição do cache
- O fetch de
Dessa forma, as notificações de mídia sempre terão um ícone de arte legal, mesmo quando o navegador não conseguir fazer a busca. Veja como implementar isso:
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 o usuário controle o cache
À medida que o usuário consome conteúdo do seu app da Web, os arquivos de mídia e arte podem ocupar muito espaço no dispositivo. É sua responsabilidade mostrar quanto de cache é usado e permitir que os usuários o limpem. Felizmente para nós, isso é muito fácil com a API 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); });
Observações sobre implementação
- O Chrome para Android solicita o foco de áudio "total" para mostrar notificações de mídia somente quando a duração do arquivo de mídia é de pelo menos 5 segundos.
- A arte da notificação oferece suporte a URLs de dados e blob.
- Se nenhuma arte for definida e houver uma imagem de ícone no tamanho desejado, as notificações de mídia vão usar essa imagem.
- O tamanho da arte da notificação no Chrome para Android é
512x512
. Para dispositivos básicos, é256x256
. - Dispense as notificações de mídia com
audio.src = ''
. - Como a API Web Audio não solicita o foco de áudio do Android por motivos históricos, a única maneira de fazer com que ela funcione com a API Media Session é vincular
um elemento
<audio>
como a origem de entrada da API Web Audio. Esperamos que a API Web AudioFocus proposta melhore a situação em breve. - As chamadas de sessão de mídia só vão afetar as notificações de mídia se vierem do mesmo frame do recurso de mídia. Confira o snippet abaixo.
<iframe id="iframe">
<audio>...</audio>
</iframe>
<script>
iframe.contentWindow.navigator.mediaSession.metadata = new MediaMetadata({
title: 'Never Gonna Give You Up',
...
});
</script>
Suporte
No momento da redação deste artigo, o Chrome para Android é a única plataforma que oferece suporte à API Media Session. Confira informações mais atualizadas sobre o status de implementação do navegador em Status da plataforma do Chrome.
Exemplos e demonstrações
Confira os exemplos oficiais de sessão de mídia do Chrome com a Blender Foundation e o trabalho de Jan Morgenstern.
Recursos
Especificação de sessão de mídia: wicg.github.io/mediasession (link em inglês)
Problemas de especificação: github.com/WICG/mediasession/issues
Bugs do Chrome: crbug.com (link em inglês)