Criar APK grátis

API da ponte JavaScript

Mídia e transmissão

A notificação de mídia na tela de bloqueio controlada pela sua página e como enviar um vídeo para uma TV.

navigator.mediaSession padrão web#

navigator.mediaSession + MediaMetadata

Exemplo

Mostra o que sua página está reproduzindo em uma notificação de mídia e na tela de bloqueio, com botões Anterior / Play-Pause / Próximo / Parar que chamam de volta a sua página. Usa a Media Session API normal.

Retorna: nada; os toques nos botões chamam os handlers que você definiu com setActionHandler, com { action } como o padrão determina. Requer: ative Notificação de mídia na Etapa 4 (Acesso) ao compilar. Apps criados com IA já vêm com isso ativado quando o código usa navigator.mediaSession.

var audio = document.getElementById('player');
var tracks = [
  { src: 'https://example.com/a.mp3', title: 'Morning', artist: 'Band', art: 'https://example.com/a.jpg' },
  { src: 'https://example.com/b.mp3', title: 'Evening', artist: 'Band', art: 'https://example.com/b.jpg' }
];
var index = 0;
var handlersReady = false;

function setupHandlers() {
  if (handlersReady || !('mediaSession' in navigator)) return;
  handlersReady = true;
  var ms = navigator.mediaSession;
  ms.setActionHandler('play', function () { audio.play(); });
  ms.setActionHandler('pause', function () { audio.pause(); });
  ms.setActionHandler('nexttrack', function () { playTrack(index + 1); });
  ms.setActionHandler('previoustrack', function () { playTrack(index - 1); });
  ms.setActionHandler('stop', function () { audio.pause(); audio.currentTime = 0; });
}

function playTrack(i) {
  index = (i + tracks.length) % tracks.length;
  var t = tracks[index];
  audio.src = t.src;
  audio.play();
  setupHandlers();   // on the first play, not at page load (see Notes)
  if ('mediaSession' in navigator && window.MediaMetadata) {
    navigator.mediaSession.metadata = new MediaMetadata({
      title: t.title, artist: t.artist, album: '',
      artwork: [{ src: t.art, sizes: '512x512', type: 'image/jpeg' }]
    });
  }
}

document.getElementById('play-btn').addEventListener('click', function () { playTrack(0); });

Mantenha o botão de reproduzir/pausar e a barra de progresso corretos:

function publishState() {
  if (!('mediaSession' in navigator)) return;
  navigator.mediaSession.playbackState = audio.paused ? 'paused' : 'playing';   // set this FIRST
  if (audio.duration > 0 && navigator.mediaSession.setPositionState) {
    navigator.mediaSession.setPositionState({
      duration: audio.duration, position: audio.currentTime, playbackRate: 1
    });
  }
}
audio.addEventListener('play', publishState);
audio.addEventListener('pause', publishState);
audio.addEventListener('seeked', publishState);

Um player de podcast em que os botões Próximo / Anterior da notificação avançam 30 segundos e voltam 15 segundos em vez de trocar de episódio:

var audio = document.getElementById('player');
function skip(seconds) {
  var end = audio.duration || 0;
  audio.currentTime = Math.max(0, Math.min(end, audio.currentTime + seconds));
  if ('mediaSession' in navigator && navigator.mediaSession.setPositionState && end > 0) {
    navigator.mediaSession.setPositionState({ duration: end, position: audio.currentTime });
  }
}
if ('mediaSession' in navigator) {
  navigator.mediaSession.setActionHandler('play', function () { audio.play(); });
  navigator.mediaSession.setActionHandler('pause', function () { audio.pause(); });
  navigator.mediaSession.setActionHandler('nexttrack', function () { skip(30); });
  navigator.mediaSession.setActionHandler('previoustrack', function () { skip(-15); });
  if (window.MediaMetadata) {
    navigator.mediaSession.metadata = new MediaMetadata({
      title: 'Episode 12: Getting started', artist: 'My Podcast',
      artwork: [{ src: 'https://example.com/podcast-512.jpg', sizes: '512x512' }]
    });
  }
}

Observaçþes:

  • No app, navigator.mediaSession é instalado antes do primeiro script da página, então handlers e metadados podem ser definidos a qualquer momento - no topo da página ou quando a reprodução começa. Ele existe na página principal, não dentro de iframes.
  • Ações suportadas: play, pause, nexttrack, previoustrack, stop. Buscar a partir da notificação não é suportado, então seekto nunca dispara.
  • Definir playbackState mantém a posição da sua última chamada de setPositionState; chame setPositionState de novo quando a posição realmente mudar (uma busca).
  • Somente a última imagem de capa é usada. Ela precisa ser um endereço https:// completo na internet (o app a baixa, com limite de 4 s); um arquivo dentro do ZIP do seu app não aparece.
  • <audio> / <video> os elementos são acompanhados para você: quando a página trata um botão nem com setActionHandler nem com seu próprio window.WebToApkOnMediaAction, Reproduzir / Pausar / Parar controlam o elemento de mídia que está tocando (senão o último tocado, senão o primeiro).
  • Para remover a notificação, chame WebToApk.clearMediaNotification() (definir metadata = null não a remove).
  • Uma página que define window.WebToApkOnMediaAction = function (action) { ... } (ações play, pause, next, prev, stop) também recebe ali cada toque de botão - o app não a sobrescreve mais. Use um dos dois, não ambos, ou um toque roda duas vezes.

requestPictureInPicture padrão web#

video.requestPictureInPicture() — Picture-in-Picture

Exemplo

A API padrão de Picture-in-Picture: deixa um vídeo flutuando em uma janela pequena enquanto o usuário faz outra coisa. O Chrome no Android tem; o WebView do Android não; no app o vídeo preenche a janela flutuante, como no Chrome.

Retorna: uma Promise de um PictureInPictureWindow (evento width, height, resize). O vídeo recebe enterpictureinpicture, depois leavepictureinpicture quando o usuário fecha ou expande a janela; document.pictureInPictureElement é o vídeo nesse meio tempo. Rejeita NotAllowedError sem um toque, InvalidStateError antes de os metadados do vídeo serem carregados ou quando ele tem disablePictureInPicture. Requer: Picture-in-picture ligado no build (é ligado sozinho quando seu código chama requestPictureInPicture), Android 8 ou mais recente.

<video id="v" src="clip.mp4" controls playsinline></video>
<button id="pip">Pop out</button>
<script>
const v = document.getElementById('v');
const btn = document.getElementById('pip');
btn.hidden = !document.pictureInPictureEnabled;
btn.addEventListener('click', async () => {
  try {
    if (document.pictureInPictureElement) await document.exitPictureInPicture();
    else await v.requestPictureInPicture();
  } catch (e) {
    console.warn('Picture-in-Picture:', e.name);
  }
});
v.addEventListener('enterpictureinpicture', () => { btn.textContent = 'Back to app'; });
v.addEventListener('leavepictureinpicture', () => { btn.textContent = 'Pop out'; });
</script>

Abrir a janela flutuante automaticamente quando o usuário sair e avisar quando a compilação não for compatível:

const v = document.getElementById('v');
if (!document.pictureInPictureEnabled || v.disablePictureInPicture) {
  document.getElementById('pip').hidden = true;      // this build has no picture-in-picture
}
document.addEventListener('visibilitychange', async () => {
  if (document.hidden && !v.paused && !document.pictureInPictureElement) {
    try { await v.requestPictureInPicture(); }
    catch (e) { console.warn(e.name); }               // NotAllowedError without a tap
  }
});

Espere os metadados e redimensione junto com a janela:

async function popOut(video) {
  if (video.readyState < 1) await new Promise((r) => video.addEventListener('loadedmetadata', r, { once: true }));
  try {
    const pipWin = await video.requestPictureInPicture();
    pipWin.addEventListener('resize', () => console.log(pipWin.width, pipWin.height));
  } catch (e) {
    if (e.name === 'InvalidStateError') showMessage('This video cannot float.');
    else if (e.name === 'NotAllowedError') showMessage('Tap the button to pop out.');
  }
}
document.getElementById('pip').onclick = () => popOut(document.getElementById('v'));

Observações: o Android coloca o app inteiro na janela flutuante, então o vídeo é mostrado primeiro sozinho (em tela cheia) e a janela assume o formato do vídeo; ao sair da janela, o vídeo volta para a página. document.exitPictureInPicture() traz o app de volta à tela cheia. Quando a compilação não tem picture-in-picture, document.pictureInPictureEnabled é false, como em um navegador sem o recurso. A mudança de modo chega à página como appmint:pip. Transportes internos: __pipEnter, __pipExit, __pipAvailable, __pipPrepare.

__pipAvailable ponte#

window.WebToApk.__pipAvailable(): Boolean

Se video.requestPictureInPicture() pode funcionar: PiP ativado na compilação, Android 8+.

Exemplo

Documentado junto com WebToApk.__pipEnter - o exemplo de lá mostra este também.

Transporte interno por trás de video.requestPictureInPicture() e document.exitPictureInPicture(). As páginas usam a API padrão; nunca chame estes diretamente, pois os métodos __ podem mudar sem aviso.

Retorna: __pipEnter(width, height) retorna "ok" quando o Android aceitou a janela (com a proporção do vídeo), senão "unsupported" / "failed". A abertura e o fechamento da janela chegam como appmint:pip com {inPip}. __pipAvailable() é verdadeiro quando o build tem picture-in-picture no Android 8+. Requer: picture-in-picture ligado no build.

Use a API pública:

const video = document.querySelector('video');
button.addEventListener('click', () => video.requestPictureInPicture());

Verifique o suporte primeiro e informe uma recusa:

async function floatVideo(video) {
  if (!document.pictureInPictureEnabled) { showMessage('Picture-in-picture is not on in this app.'); return; }
  try {
    await video.requestPictureInPicture();
  } catch (err) {
    showMessage('Could not open the floating window.');
  }
}

Saia de novo e acompanhe o estado com os eventos padrão:

video.addEventListener('enterpictureinpicture', () => hideControls());
video.addEventListener('leavepictureinpicture', () => showControls());
closeBtn.onclick = () => { if (document.pictureInPictureElement) document.exitPictureInPicture(); };

Observações: implementado no shell do app (PictureInPictureShim.kt). __pipPrepare impede o shell de girar a tela para o modo horizontal na etapa de tela cheia ao entrar na janela.

__pipEnter ponte#

window.WebToApk.__pipEnter(width: Int, height: Int): String

Abre a janela PiP com a proporção de um vídeo de [width]×[height]: "ok", ou "unsupported" / "failed". A mudança de modo chega como `appmint:pip`.

Exemplo

Transporte interno por trás de video.requestPictureInPicture() e document.exitPictureInPicture(). As páginas usam a API padrão; nunca chame estes diretamente, pois os métodos __ podem mudar sem aviso.

Retorna: __pipEnter(width, height) retorna "ok" quando o Android aceitou a janela (com a proporção do vídeo), senão "unsupported" / "failed". A abertura e o fechamento da janela chegam como appmint:pip com {inPip}. __pipAvailable() é verdadeiro quando o build tem picture-in-picture no Android 8+. Requer: picture-in-picture ligado no build.

Use a API pública:

const video = document.querySelector('video');
button.addEventListener('click', () => video.requestPictureInPicture());

Verifique o suporte primeiro e informe uma recusa:

async function floatVideo(video) {
  if (!document.pictureInPictureEnabled) { showMessage('Picture-in-picture is not on in this app.'); return; }
  try {
    await video.requestPictureInPicture();
  } catch (err) {
    showMessage('Could not open the floating window.');
  }
}

Saia de novo e acompanhe o estado com os eventos padrão:

video.addEventListener('enterpictureinpicture', () => hideControls());
video.addEventListener('leavepictureinpicture', () => showControls());
closeBtn.onclick = () => { if (document.pictureInPictureElement) document.exitPictureInPicture(); };

Observações: implementado no shell do app (PictureInPictureShim.kt). __pipPrepare impede o shell de girar a tela para o modo horizontal na etapa de tela cheia ao entrar na janela.

__pipExit ponte#

window.WebToApk.__pipExit()

document.exitPictureInPicture(): traz o app de volta para a tela cheia.

Exemplo

Documentado junto com WebToApk.__pipEnter - o exemplo de lá mostra este também.

Transporte interno por trás de video.requestPictureInPicture() e document.exitPictureInPicture(). As páginas usam a API padrão; nunca chame estes diretamente, pois os métodos __ podem mudar sem aviso.

Retorna: __pipEnter(width, height) retorna "ok" quando o Android aceitou a janela (com a proporção do vídeo), senão "unsupported" / "failed". A abertura e o fechamento da janela chegam como appmint:pip com {inPip}. __pipAvailable() é verdadeiro quando o build tem picture-in-picture no Android 8+. Requer: picture-in-picture ligado no build.

Use a API pública:

const video = document.querySelector('video');
button.addEventListener('click', () => video.requestPictureInPicture());

Verifique o suporte primeiro e informe uma recusa:

async function floatVideo(video) {
  if (!document.pictureInPictureEnabled) { showMessage('Picture-in-picture is not on in this app.'); return; }
  try {
    await video.requestPictureInPicture();
  } catch (err) {
    showMessage('Could not open the floating window.');
  }
}

Saia de novo e acompanhe o estado com os eventos padrão:

video.addEventListener('enterpictureinpicture', () => hideControls());
video.addEventListener('leavepictureinpicture', () => showControls());
closeBtn.onclick = () => { if (document.pictureInPictureElement) document.exitPictureInPicture(); };

Observações: implementado no shell do app (PictureInPictureShim.kt). __pipPrepare impede o shell de girar a tela para o modo horizontal na etapa de tela cheia ao entrar na janela.

__pipPrepare ponte#

window.WebToApk.__pipPrepare()

A página está prestes a colocar um vídeo em tela cheia para o PiP: não force o modo paisagem para isso.

Exemplo

Documentado junto com WebToApk.__pipEnter - o exemplo de lá mostra este também.

Transporte interno por trás de video.requestPictureInPicture() e document.exitPictureInPicture(). As páginas usam a API padrão; nunca chame estes diretamente, pois os métodos __ podem mudar sem aviso.

Retorna: __pipEnter(width, height) retorna "ok" quando o Android aceitou a janela (com a proporção do vídeo), senão "unsupported" / "failed". A abertura e o fechamento da janela chegam como appmint:pip com {inPip}. __pipAvailable() é verdadeiro quando o build tem picture-in-picture no Android 8+. Requer: picture-in-picture ligado no build.

Use a API pública:

const video = document.querySelector('video');
button.addEventListener('click', () => video.requestPictureInPicture());

Verifique o suporte primeiro e informe uma recusa:

async function floatVideo(video) {
  if (!document.pictureInPictureEnabled) { showMessage('Picture-in-picture is not on in this app.'); return; }
  try {
    await video.requestPictureInPicture();
  } catch (err) {
    showMessage('Could not open the floating window.');
  }
}

Saia de novo e acompanhe o estado com os eventos padrão:

video.addEventListener('enterpictureinpicture', () => hideControls());
video.addEventListener('leavepictureinpicture', () => showControls());
closeBtn.onclick = () => { if (document.pictureInPictureElement) document.exitPictureInPicture(); };

Observações: implementado no shell do app (PictureInPictureShim.kt). __pipPrepare impede o shell de girar a tela para o modo horizontal na etapa de tela cheia ao entrar na janela.

castMedia ponte#

window.WebToApk.castMedia(mediaUrl: String)

Oferece [mediaUrl] ao que houver neste celular que possa reproduzi-lo em outra tela - Chromecast, um app de Smart TV, um player local - pelo seletor do sistema. O tipo é tirado da URL ([CastMediaType]): um arquivo de áudio (.mp3, .m4a, .aac, .ogg, .opus, .wav, .flac …) ou uma URL `data:` de áudio é oferecido a apps de áudio; qualquer outra coisa - arquivos de vídeo, manifestos de stream, URLs sem extensão - a apps de vídeo. Isto é uma transferência, não uma sessão de transmissão: o app não continua controlando a reprodução e não há callback. Uma página que precise de uma sessão real deve usar o Cast SDK em JavaScript.

Exemplo

Oferece um endereço de vídeo ou áudio aos apps do celular que podem reproduzi-lo em outra tela (um app de Chromecast, um app de Smart TV, um player de vídeo), pelo seletor "Transmitir para..." do Android.

Retorna: nada, e nenhum callback. Requer: nada para ativar. O celular precisa de um app que aceite o link.

function castToTv(url) {
  if (window.WebToApk && typeof window.WebToApk.castMedia === 'function') {
    window.WebToApk.castMedia(url);          // opens the "Cast to..." chooser
  } else {
    alert('Casting is only available in the app');
  }
}

document.getElementById('cast-btn').addEventListener('click', function () {
  var video = document.querySelector('video');
  var url = video && video.currentSrc ? video.currentSrc : 'https://example.com/movie.mp4';
  castToTv(url);
});

Envie um episódio de podcast de uma lista para um alto-falante ou app de TV (um link .mp3 é oferecido a apps de áudio):

// <li><button class="cast" data-src="https://example.com/ep12.mp3">Cast</button> Episode 12</li>
document.getElementById('episodes').addEventListener('click', function (e) {
  var btn = e.target.closest('button.cast');
  if (!btn) return;
  var src = btn.getAttribute('data-src') || '';
  if (src.indexOf('https://') !== 0) {
    alert('This episode cannot be cast');   // files inside the app cannot be handed over
    return;
  }
  if (window.WebToApk && typeof window.WebToApk.castMedia === 'function') {
    window.WebToApk.castMedia(src);
  }
});

Mostre o botão Cast somente dentro do app e pause o player da própria página antes de enviar uma transmissão ao vivo:

var castBtn = document.getElementById('cast-live');
var canCast = !!(window.WebToApk && typeof window.WebToApk.castMedia === 'function');
castBtn.hidden = !canCast;

castBtn.addEventListener('click', function () {
  var video = document.getElementById('live');
  video.pause();                                   // avoid playing twice, here and on the TV
  window.WebToApk.castMedia('https://example.com/live/stream.m3u8');   // offered to video apps
});

Observaçþes:

  • Isto é um repasse, não uma sessão de cast: sua página não controla a reprodução depois que o usuário escolhe um app.
  • O endereço deve ser um link https:// completo que outros apps possam abrir. Um arquivo dentro do ZIP do seu app, uma URL blob: ou o endereço de uma página web não vai tocar.
  • O tipo vem do link: um arquivo de áudio (.mp3, .m4a, .aac, .ogg, .opus, .wav, .flac …) ou uma URL audio/ data: é oferecido a apps de áudio; qualquer outra coisa - arquivos de vídeo, streams .m3u8, links sem extensão de arquivo - a apps de vídeo.

clearMediaNotification ponte#

window.WebToApk.clearMediaNotification()

Permite que o conteúdo web controle a notificação em estilo de mídia do Android e receba callbacks de controle de reprodução (play/pause/next/prev) da tela de bloqueio e da central de notificações. Uso em JavaScript: window.WebToApk.setMediaMetadata('Song Title', 'Artist Name', 'https://…/art.jpg') window.WebToApk.setPlaybackState(true, 30000, 240000) // tocando, posição em ms, duração em ms window.WebToApk.clearMediaNotification() Callback do Android → JS: window.WebToApkOnMediaAction('play'|'pause'|'next'|'prev'|'stop')

Descrito pelo seu grupo, Ponte de notificação de mídia, e não isoladamente.

Exemplo

Remove a notificação de mídia e seus controles da tela de bloqueio, por exemplo quando o usuário fecha seu player.

Retorna: nada. Requer: ative a Notificação de mídia na Etapa 4 (Acesso) ao compilar. Sem isso a chamada não faz nada.

Não há chamada padrão da web para isso, então use a ponte:

function closePlayer() {
  var audio = document.getElementById('player');
  audio.pause();
  audio.currentTime = 0;
  if (window.WebToApk && typeof window.WebToApk.clearMediaNotification === 'function') {
    window.WebToApk.clearMediaNotification();
  }
}

document.getElementById('close-player').addEventListener('click', closePlayer);

// The notification's own Stop button already removes it; just stop your audio:
if ('mediaSession' in navigator) {
  navigator.mediaSession.setActionHandler('stop', function () {
    var audio = document.getElementById('player');
    audio.pause();
    audio.currentTime = 0;
  });
}

Um timer de sono que para o áudio e remove a notificação após o número de minutos escolhido:

var sleepTimer = null;
function setSleepTimer(minutes) {
  clearTimeout(sleepTimer);
  sleepTimer = setTimeout(function () {
    var audio = document.getElementById('player');
    audio.pause();
    if (window.WebToApk && typeof window.WebToApk.clearMediaNotification === 'function') {
      window.WebToApk.clearMediaNotification();
    }
  }, minutes * 60 * 1000);
}
document.getElementById('sleep-30').addEventListener('click', function () { setSleepTimer(30); });

Remova a notificação quando a última faixa de uma playlist terminar, em vez de deixar um botão Reproduzir sem função:

var audio = document.getElementById('player');
var playlist = ['https://example.com/1.mp3', 'https://example.com/2.mp3'];
var current = 0;

audio.addEventListener('ended', function () {
  current++;
  if (current < playlist.length) {
    audio.src = playlist[current];
    audio.play();
  } else if (window.WebToApk && typeof window.WebToApk.clearMediaNotification === 'function') {
    window.WebToApk.clearMediaNotification();
  }
});

Observações: use isto para "parar", não para "pausar" (para pausar, envie setPlaybackState(false, ...) para o botão Play continuar aparecendo). Definir navigator.mediaSession.metadata = null não remove a notificação. O próximo setMediaMetadata / setPlaybackState (ou novos metadados / estado de navigator.mediaSession) traz de volta a notificação e os controles da tela de bloqueio.

setMediaMetadata ponte#

window.WebToApk.setMediaMetadata(title: String, artist: String, artworkUrl: String)

Permite que o conteúdo web controle a notificação em estilo de mídia do Android e receba callbacks de controle de reprodução (play/pause/next/prev) da tela de bloqueio e da central de notificações. Uso em JavaScript: window.WebToApk.setMediaMetadata('Song Title', 'Artist Name', 'https://…/art.jpg') window.WebToApk.setPlaybackState(true, 30000, 240000) // tocando, posição em ms, duração em ms window.WebToApk.clearMediaNotification() Callback do Android → JS: window.WebToApkOnMediaAction('play'|'pause'|'next'|'prev'|'stop')

Descrito pelo seu grupo, Ponte de notificação de mídia, e não isoladamente.

Exemplo

Define o título, o artista e a imagem de capa da notificação de mídia e do player da tela de bloqueio. Normalmente as páginas definem navigator.mediaSession.metadata, que chama isto por você.

Retorna: nada. Requer: ative a Notificação de mídia na Etapa 4 (Acesso) ao compilar. Sem isso a chamada não faz nada.

A forma pública (use esta):

if ('mediaSession' in navigator && window.MediaMetadata) {
  navigator.mediaSession.metadata = new MediaMetadata({
    title: 'Episode 12', artist: 'My Podcast',
    artwork: [{ src: 'https://example.com/cover.jpg', sizes: '512x512' }]
  });
}

A chamada direta. Argumentos: title, artist, artworkUrl (use '' para nenhuma imagem):

function showNowPlaying(track) {
  if (!(window.WebToApk && typeof window.WebToApk.setMediaMetadata === 'function')) return;
  window.WebToApk.setMediaMetadata(track.title, track.artist || '', track.cover || '');
  window.WebToApk.setPlaybackState(true, 0, Math.round(track.seconds * 1000));
}

// Buttons in the notification come back through the standard API:
if ('mediaSession' in navigator) {
  navigator.mediaSession.setActionHandler('nexttrack', function () { playNext(); });
  navigator.mediaSession.setActionHandler('previoustrack', function () { playPrevious(); });
}

Mantenha o título da notificação atualizado em uma rádio ao vivo, em que a música muda sem a página carregar um novo arquivo:

var lastSong = '';
function refreshNowPlaying() {
  fetch('https://example.com/radio/now-playing.json')
    .then(function (r) { return r.json(); })
    .then(function (np) {            // e.g. { "title": "...", "artist": "...", "cover": "https://..." }
      var key = np.title + '|' + np.artist;
      if (key === lastSong) return;  // only update when the song really changed
      lastSong = key;
      if (window.WebToApk && typeof window.WebToApk.setMediaMetadata === 'function') {
        window.WebToApk.setMediaMetadata(np.title || 'Live radio', np.artist || '', np.cover || '');
      }
    })
    .catch(function () { /* try again on the next tick */ });
}
document.getElementById('radio').addEventListener('play', function () {
  refreshNowPlaying();
  setInterval(refreshNowPlaying, 30000);
}, { once: true });

Observaçþes:

  • A notificação aparece (ou é atualizada) com esta chamada. Um título vazio mostra o nome do app.
  • artworkUrl deve ser um endereço https:// completo na internet. O app o baixa (limite de 4 segundos); se falhar, o ícone do app é mostrado.
  • Receba toques em botões com navigator.mediaSession.setActionHandler (veja navigator.mediaSession), ou com seu próprio window.WebToApkOnMediaAction = function (action) { ... } (play, pause, next, prev, stop) - use um dos dois. Depois de clearMediaNotification(), a próxima chamada a isto (ou a setPlaybackState) traz de volta a notificação e os controles da tela de bloqueio.

setPlaybackState ponte#

window.WebToApk.setPlaybackState(isPlaying: Boolean, positionMs: Long, durationMs: Long)

Permite que o conteúdo web controle a notificação em estilo de mídia do Android e receba callbacks de controle de reprodução (play/pause/next/prev) da tela de bloqueio e da central de notificações. Uso em JavaScript: window.WebToApk.setMediaMetadata('Song Title', 'Artist Name', 'https://…/art.jpg') window.WebToApk.setPlaybackState(true, 30000, 240000) // tocando, posição em ms, duração em ms window.WebToApk.clearMediaNotification() Callback do Android → JS: window.WebToApkOnMediaAction('play'|'pause'|'next'|'prev'|'stop')

Descrito pelo seu grupo, Ponte de notificação de mídia, e não isoladamente.

Exemplo

Informa à notificação de mídia se você está reproduzindo ou pausado, e em que ponto da faixa está. As páginas normalmente definem navigator.mediaSession.playbackState e chamam setPositionState, que chamam isto por você.

Retorna: nada. Requer: ative a Notificação de mídia na Etapa 4 (Acesso) ao compilar. Sem isso a chamada não faz nada.

A forma pública (use esta):

var audio = document.getElementById('player');
function publish() {
  if (!('mediaSession' in navigator)) return;
  navigator.mediaSession.playbackState = audio.paused ? 'paused' : 'playing';
  if (audio.duration > 0 && navigator.mediaSession.setPositionState) {
    navigator.mediaSession.setPositionState({ duration: audio.duration, position: audio.currentTime });
  }
}
audio.addEventListener('play', publish);
audio.addEventListener('pause', publish);

A chamada direta. Argumentos: isPlaying (true/false), positionMs, durationMs (milissegundos inteiros):

function publishRaw(audio) {
  if (!(window.WebToApk && typeof window.WebToApk.setPlaybackState === 'function')) return;
  window.WebToApk.setPlaybackState(
    !audio.paused,
    Math.floor(audio.currentTime * 1000),
    Math.floor((audio.duration || 0) * 1000)
  );
}

Informe a nova posição depois que o usuário buscar e mude a notificação para Reproduzir quando a faixa terminar:

var audio = document.getElementById('player');
function report(playing) {
  if (!(window.WebToApk && typeof window.WebToApk.setPlaybackState === 'function')) return;
  var durMs = Math.floor((audio.duration || 0) * 1000);
  var posMs = Math.floor(audio.currentTime * 1000);
  window.WebToApk.setPlaybackState(playing, Math.min(posMs, durMs || posMs), durMs);
}
audio.addEventListener('seeked', function () { report(!audio.paused); });
audio.addEventListener('ended', function () { report(false); });   // Play button, notification can be swiped away

Observações: true mostra o botão Pausar e impede que a notificação seja dispensada com um deslize; false mostra Play. Chame setMediaMetadata primeiro, para a notificação ter um título. Para remover a notificação quando o usuário parar, chame clearMediaNotification().

Gerado a partir do runtime do app e de seus arquivos de exemplo a cada build da documentação. Leia em Markdown · Todas as famílias.

Conferido com a ponte atual em 22/10/2026.