Personalize notificações de mídia e controles de mídia com a API Media Session

Como integrar com teclas de mídia de hardware, personalizar notificações de mídia e muito mais.

François Beaufort
François Beaufort

Para que os usuários saibam o que está sendo reproduzido no navegador e o controlem sem retornar à página que o iniciou, a API Media Session foi introduzida. Ele permite que os desenvolvedores da Web personalizem essa experiência com metadados em notificações de mídia personalizadas, eventos de mídia, como reprodução, pausa, busca, mudança de faixa e eventos de videoconferência, como silenciar/ativar microfone, ativar/desativar câmera e desligar. Essas personalizações estão disponíveis em vários contextos, incluindo hubs de mídia em computadores, notificações de mídia em dispositivos móveis e até mesmo em dispositivos wearable. Vou descrever essas personalizações neste artigo.

Capturas de tela de contextos de sessão de mídia.
Media Hub no computador, notificação de mídia em dispositivos móveis e dispositivo wearable.

Sobre a API Media Session

A API Media session oferece vários benefícios e recursos:

  • Chaves de mídia de hardware são aceitas.
  • As notificações de mídia são personalizadas em dispositivos móveis, computadores e wearables pareados.
  • O hub de mídia está disponível para computadores.
  • Os controles de mídia da tela de bloqueio estão disponíveis no ChromeOS e em dispositivos móveis.
  • Os controles de janela picture-in-picture estão disponíveis para reprodução de áudio, videoconferência e apresentação de slides.
  • A integração do Google Assistente em dispositivos móveis está disponível.

Compatibilidade com navegadores

  • Chrome: 73.
  • Edge: 79.
  • Firefox: 82.
  • Safari: 15.

Origem

Confira alguns exemplos para ilustrar alguns desses pontos.

Exemplo 1:se os usuários pressionarem a tecla de mídia "Próxima faixa" no teclado, os desenvolvedores Web poderão processar essa ação do usuário com o navegador em primeiro ou segundo plano.

Exemplo 2: se os usuários ouvirem um podcast na Web enquanto a tela do dispositivo estiver bloqueada, eles ainda poderão tocar no ícone "procurar para trás" nos controles de mídia da tela de bloqueio para que os desenvolvedores da Web movam o tempo de reprodução para trás por alguns segundos.

Exemplo 3:se os usuários tiverem guias que reproduzem áudio, eles poderão interromper facilmente a reprodução no hub de mídia no computador para que os desenvolvedores Web possam limpar o estado.

Exemplo 4:se os usuários estiverem em uma videochamada, eles poderão pressionar o controle "Ativar microfone" na janela picture-in-picture para impedir que o site receba dados do microfone.

Tudo isso é feito por duas interfaces diferentes: a interface MediaSession e a interface MediaMetadata. O primeiro permite que os usuários controlem o que está sendo reproduzido. A segunda é como você informa ao MediaSession o que precisa ser controlado.

Para ilustrar, a imagem abaixo mostra como essas interfaces estão relacionadas a controles de mídia específicos. Nesse caso, uma notificação de mídia em um dispositivo móvel.

Ilustração das interfaces de sessão de mídia.
Anatomia de uma notificação de mídia em dispositivos móveis.

Informe aos usuários o que está sendo reproduzido

Quando um site está reproduzindo áudio ou vídeo, os usuários recebem automaticamente notificações de mídia na bandeja de notificações em dispositivos móveis ou no hub de mídia em computadores. O navegador faz o possível para mostrar as informações adequadas usando o título do documento e a imagem de ícone maior que puder encontrar. Com a API Media Session, é possível personalizar a notificação de mídia com alguns metadados de mídia mais ricos, como título, nome do artista, nome do álbum e arte, conforme mostrado abaixo.

O Chrome solicita o foco de áudio "total" para mostrar notificações de mídia somente quando a duração da mídia é de pelo menos 5 segundos. Isso garante que sons incidentais, como ruídos, não mostrem notificações.

// After media (video or audio) starts playing
await document.querySelector("video").play();

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://via.placeholder.com/96',   sizes: '96x96',   type: 'image/png' },
      { src: 'https://via.placeholder.com/128', sizes: '128x128', type: 'image/png' },
      { src: 'https://via.placeholder.com/192', sizes: '192x192', type: 'image/png' },
      { src: 'https://via.placeholder.com/256', sizes: '256x256', type: 'image/png' },
      { src: 'https://via.placeholder.com/384', sizes: '384x384', type: 'image/png' },
      { src: 'https://via.placeholder.com/512', sizes: '512x512', type: 'image/png' },
    ]
  });

  // TODO: Update playback state.
}

Quando a reprodução termina, não é necessário "liberar" a sessão de mídia, porque a notificação desaparece automaticamente. No entanto, navigator.mediaSession.metadata será usado quando a próxima reprodução começar. Por isso, é importante atualizar o conteúdo quando a fonte de reprodução de mídia mudar para garantir que as informações relevantes sejam mostradas na notificação de mídia.

Há algumas observações sobre os metadados de mídia.

  • A matriz de arte da notificação oferece suporte a URLs de dados e blobs.
  • Se nenhuma arte for definida e houver uma imagem de ícone (especificada usando <link rel=icon>) no tamanho desejado, as notificações de mídia vão usar essa imagem.
  • O tamanho de destino da arte da notificação no Chrome para Android é 512x512. Para dispositivos básicos, é 256x256.
  • O atributo title do elemento HTML de mídia é usado no widget "Now playing" do macOS.
  • Se o recurso de mídia estiver incorporado (por exemplo, em um iframe), as informações da API Media Session precisam ser definidas no contexto incorporado. Confira o snippet abaixo.
<iframe id="iframe">
  <video>...</video>
</iframe>
<script>
  iframe.contentWindow.navigator.mediaSession.metadata = new MediaMetadata({
    title: 'Never Gonna Give You Up',
    ...
  });
</script>

Você também pode adicionar informações de capítulos individuais, como o título da seção, o carimbo de data/hora e uma imagem de captura de tela aos metadados de mídia. Isso permite que os usuários naveguem pelo conteúdo da mídia.

navigator.mediaSession.metadata = new MediaMetadata({
  // title, artist, album, artwork, ...
  chapterInfo: [{
    title: 'Chapter 1',
    startTime: 0,
    artwork: [
      { src: 'https://via.placeholder.com/128', sizes: '128x128', type: 'image/png' },
      { src: 'https://via.placeholder.com/512', sizes: '512x512', type: 'image/png' },
    ]
  }, {
    title: 'Chapter 2',
    startTime: 42,
    artwork: [
      { src: 'https://via.placeholder.com/128', sizes: '128x128', type: 'image/png' },
      { src: 'https://via.placeholder.com/512', sizes: '512x512', type: 'image/png' },
    ]
  }]
});
Informações do capítulo exibidas em uma notificação de mídia do ChromeOS.
Notificação de mídia com capítulos no ChromeOS.

Permitir que os usuários controlem o que está tocando

Uma ação de sessão de mídia é uma ação (por exemplo, "reproduzir" ou "pausar") que um site pode processar para os usuários quando eles interagem com a reprodução de mídia atual. As ações são análogas e funcionam de maneira semelhante aos eventos. Assim como os eventos, as ações são implementadas definindo manipuladores em um objeto apropriado, uma instância de MediaSession, neste caso. Algumas ações são acionadas quando os usuários pressionam botões de um fone de ouvido, outro dispositivo remoto, um teclado ou interagem com uma notificação de mídia.

Captura de tela de uma notificação de mídia no Windows 10.
Notificação de mídia personalizada no Windows 10.

Como algumas ações de sessão de mídia podem não ser compatíveis, é recomendável usar um bloco try…catch ao configurá-las.

const actionHandlers = [
  ['play',          () => { /* ... */ }],
  ['pause',         () => { /* ... */ }],
  ['previoustrack', () => { /* ... */ }],
  ['nexttrack',     () => { /* ... */ }],
  ['stop',          () => { /* ... */ }],
  ['seekbackward',  (details) => { /* ... */ }],
  ['seekforward',   (details) => { /* ... */ }],
  ['seekto',        (details) => { /* ... */ }],
  /* Video conferencing actions */
  ['togglemicrophone', () => { /* ... */ }],
  ['togglecamera',     () => { /* ... */ }],
  ['hangup',           () => { /* ... */ }],
  /* Presenting slides actions */
  ['previousslide', () => { /* ... */ }],
  ['nextslide',     () => { /* ... */ }],
];

for (const [action, handler] of actionHandlers) {
  try {
    navigator.mediaSession.setActionHandler(action, handler);
  } catch (error) {
    console.log(`The media session action "${action}" is not supported yet.`);
  }
}

Desativar um gerenciador de ações de sessão de mídia é tão fácil quanto configurá-lo como null.

try {
  // Unset the "nexttrack" action handler at the end of a playlist.
  navigator.mediaSession.setActionHandler('nexttrack', null);
} catch (error) {
  console.log(`The media session action "nexttrack" is not supported yet.`);
}

Depois de definidos, os manipuladores de ação da sessão de mídia vão persistir durante as reproduções de mídia. Isso é semelhante ao padrão do 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 o site oferece suporte à ação de mídia. Portanto, os controles de ação de mídia não serão mostrados, a menos que o manipulador de ação adequado seja definido.

Captura de tela do widget Tocando agora no macOS Big Sur.
Widget "Now Playing" no macOS Big Sur.

Reproduzir / pausar

A ação "play" indica que o usuário quer retomar a reprodução de mídia, enquanto "pause" indica que ele quer interromper temporariamente.

O ícone "tocar/pausar" é sempre mostrado em uma notificação de mídia, e os eventos de mídia relacionados são processados automaticamente pelo navegador. Para substituir o comportamento padrão, processe as ações de mídia "play" e "pause", conforme mostrado abaixo.

O navegador pode considerar que um site não está reproduzindo mídia ao procurar ou carregar, por exemplo. Nesse caso, substitua esse comportamento definindo navigator.mediaSession.playbackState como "playing" ou "paused" para garantir que a interface do site permaneça sincronizada com os controles de notificação de mídia.

const video = document.querySelector('video');

navigator.mediaSession.setActionHandler('play', async () => {
  // Resume playback
  await video.play();
});

navigator.mediaSession.setActionHandler('pause', () => {
  // Pause active playback
  video.pause();
});

video.addEventListener('play', () => {
  navigator.mediaSession.playbackState = 'playing';
});

video.addEventListener('pause', () => {
  navigator.mediaSession.playbackState = 'paused';
});

Faixa anterior

A ação "previoustrack" indica que o usuário quer iniciar a reprodução de mídia atual do início, se a reprodução de mídia tiver uma noção de início, ou mover para o item anterior na playlist, se a reprodução de mídia tiver uma noção de playlist.

navigator.mediaSession.setActionHandler('previoustrack', () => {
  // Play previous track.
});

Próxima faixa

A ação "nexttrack" indica que o usuário quer mover a reprodução de mídia para o próximo item da playlist se ela tiver uma noção de playlist.

navigator.mediaSession.setActionHandler('nexttrack', () => {
  // Play next track.
});

Parar

A ação "stop" indica que o usuário quer interromper a reprodução de mídia e limpar o estado, se apropriado.

navigator.mediaSession.setActionHandler('stop', () => {
  // Stop playback and clear state if appropriate.
});

Retroceder / avançar

A ação "seekbackward" indica que o usuário quer mover o tempo de reprodução da mídia para trás por um curto período, enquanto "seekforward" indica o desejo de mover o tempo de reprodução da mídia para frente por um curto período. Em ambos os casos, um período curto significa alguns segundos.

O valor seekOffset fornecido no action handler é o tempo em segundos para mover a reprodução de mídia. Se ele não for fornecido (por exemplo, undefined), use um tempo razoável (por exemplo, 10 a 30 segundos).

const video = document.querySelector('video');
const defaultSkipTime = 10; /* Time to skip in seconds by default */

navigator.mediaSession.setActionHandler('seekbackward', (details) => {
  const skipTime = details.seekOffset || defaultSkipTime;
  video.currentTime = Math.max(video.currentTime - skipTime, 0);
  // TODO: Update playback state.
});

navigator.mediaSession.setActionHandler('seekforward', (details) => {
  const skipTime = details.seekOffset || defaultSkipTime;
  video.currentTime = Math.min(video.currentTime + skipTime, video.duration);
  // TODO: Update playback state.
});

Buscar um horário específico

A ação "seekto" indica que o usuário quer mover o tempo de reprodução da mídia para um horário específico.

O valor seekTime fornecido no action handler é o tempo em segundos para mover a reprodução de mídia.

O booleano fastSeek fornecido no action handler é verdadeiro se a ação estiver sendo chamada várias vezes como parte de uma sequência e essa não for a última chamada naquela sequência.

const video = document.querySelector('video');

navigator.mediaSession.setActionHandler('seekto', (details) => {
  if (details.fastSeek && 'fastSeek' in video) {
    // Only use fast seek if supported.
    video.fastSeek(details.seekTime);
    return;
  }
  video.currentTime = details.seekTime;
  // TODO: Update playback state.
});

Definir a posição de reprodução

A exibição precisa da posição de reprodução de mídia em uma notificação é tão simples quanto definir o estado da posição em um momento adequado, conforme mostrado abaixo. O estado de posição é uma combinação da taxa de reprodução de mídia, duração e hora atual.

Captura de tela dos controles de mídia da tela de bloqueio no ChromeOS.
Controles de mídia da tela de bloqueio no ChromeOS.

A duração precisa ser fornecida e positiva. A posição precisa ser positiva e menor que a duração. A taxa de reprodução precisa ser maior que 0.

const video = document.querySelector('video');

function updatePositionState() {
  if ('setPositionState' in navigator.mediaSession) {
    navigator.mediaSession.setPositionState({
      duration: video.duration,
      playbackRate: video.playbackRate,
      position: video.currentTime,
    });
  }
}

// When video starts playing, update duration.
await video.play();
updatePositionState();

// When user wants to seek backward, update position.
navigator.mediaSession.setActionHandler('seekbackward', (details) => {
  /* ... */
  updatePositionState();
});

// When user wants to seek forward, update position.
navigator.mediaSession.setActionHandler('seekforward', (details) => {
  /* ... */
  updatePositionState();
});

// When user wants to seek to a specific time, update position.
navigator.mediaSession.setActionHandler('seekto', (details) => {
  /* ... */
  updatePositionState();
});

// When video playback rate changes, update position state.
video.addEventListener('ratechange', (event) => {
  updatePositionState();
});

É fácil redefinir o estado da posição para null.

// Reset position state when media is reset.
navigator.mediaSession.setPositionState(null);

Ações de videoconferência

Quando o usuário coloca a videochamada em uma janela picture-in-picture, o navegador pode mostrar controles para o microfone e a câmera e para desligar. Quando o usuário clica nelas, o site as processa usando as ações de videoconferência abaixo. Confira este exemplo de videoconferência.

Captura de tela dos controles de videoconferência em uma janela picture-in-picture.
Controles de videoconferência em uma janela picture-in-picture.

Ativar ou desativar o microfone

A ação "togglemicrophone" indica que o usuário quer ativar ou desativar o microfone. O método setMicrophoneActive(isActive) informa ao navegador se o site considera o microfone ativo no momento.

let isMicrophoneActive = false;

navigator.mediaSession.setActionHandler('togglemicrophone', () => {
  if (isMicrophoneActive) {
    // Mute the microphone.
  } else {
    // Unmute the microphone.
  }
  isMicrophoneActive = !isMicrophoneActive;
  navigator.mediaSession.setMicrophoneActive(isMicrophoneActive);
});

Abrir/fechar câmera

A ação "togglecamera" indica que o usuário quer ativar ou desativar a câmera ativa. O método setCameraActive(isActive) indica se o navegador considera o site ativo.

let isCameraActive = false;

navigator.mediaSession.setActionHandler('togglecamera', () => {
  if (isCameraActive) {
    // Disable the camera.
  } else {
    // Enable the camera.
  }
  isCameraActive = !isCameraActive;
  navigator.mediaSession.setCameraActive(isCameraActive);
});

Desligar

A ação "hangup" indica que o usuário quer encerrar uma chamada.

navigator.mediaSession.setActionHandler('hangup', () => {
  // End the call.
});

Ações de apresentação de slides

Quando o usuário coloca a apresentação de slides em uma janela picture-in-picture, o navegador pode mostrar controles para navegar pelos slides. Quando o usuário clicar neles, o site vai processá-los pela API Media Session. Para um exemplo, consulte o exemplo de apresentação de slides.

Slide anterior

A ação "previousslide" indica que o usuário quer voltar ao slide anterior ao apresentar slides.

navigator.mediaSession.setActionHandler('previousslide', () => {
  // Show previous slide.
});

Compatibilidade com navegadores

  • Chrome: 111.
  • Borda: 111.
  • Firefox: não é compatível.
  • Safari: não é compatível.

Próximo slide

A ação "nextslide" indica que o usuário quer ir para o próximo slide ao apresentar slides.

navigator.mediaSession.setActionHandler('nextslide', () => {
  // Show next slide.
});

Compatibilidade com navegadores

  • Chrome: 111.
  • Edge: 111.
  • Firefox: não é compatível.
  • Safari: não é compatível.

Amostras

Confira alguns exemplos de Sessões de mídia com a Liquider Foundation e o trabalho de Jan Morgenstern (links em inglês).

Um screencast que ilustra a API Media Session.

Recursos