Crea APK gratis

API del bridge JavaScript

Media e casting

La notifica multimediale sulla schermata di blocco che la tua pagina controlla e l'invio di un video a una TV.

navigator.mediaSession standard web#

navigator.mediaSession + MediaMetadata

Esempio

Mostra ciò che la tua pagina sta riproducendo in una notifica multimediale e sulla schermata di blocco, con i pulsanti Precedente / Play-Pausa / Successivo / Stop che richiamano la tua pagina. Usa la normale Media Session API.

Restituisce: nulla; la pressione dei pulsanti richiama i gestori che hai impostato con setActionHandler, con { action } come previsto dallo standard. Richiede: attiva Media Notification nel Passaggio 4 (Accesso) quando crei l'app. Le app create con l'AI lo trovano già attivo quando il codice 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); });

Mantieni corretti il pulsante play/pausa e la barra di avanzamento:

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

Un lettore di podcast in cui i pulsanti Avanti / Indietro della notifica saltano 30 secondi in avanti e 15 secondi indietro invece di cambiare episodio:

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

Note:

  • Nell'app, navigator.mediaSession viene installato prima del primo script della pagina, quindi i gestori e i metadati si possono impostare in qualsiasi momento - all'inizio della pagina o quando parte la riproduzione. Esiste nella pagina principale, non negli iframe.
  • Azioni supportate: play, pause, nexttrack, previoustrack, stop. Lo spostamento dalla notifica non è supportato, quindi seekto non scatta mai.
  • Impostare playbackState mantiene la posizione dell'ultima chiamata a setPositionState; richiama setPositionState quando la posizione cambia davvero (uno spostamento).
  • Viene usata solo l'ultima immagine di copertina. Deve essere un indirizzo https:// completo su internet (l'app la scarica, limite di 4 s); un file all'interno dello ZIP della tua app non viene mostrato.
  • <audio> / <video> gli elementi vengono seguiti per te: quando la pagina gestisce un pulsante né con setActionHandler né con il proprio window.WebToApkOnMediaAction, Play / Pausa / Stop controllano l'elemento multimediale in riproduzione (altrimenti l'ultimo riprodotto, altrimenti il primo).
  • Per rimuovere la notifica, chiama WebToApk.clearMediaNotification() (impostare metadata = null non la rimuove).
  • Una pagina che definisce window.WebToApkOnMediaAction = function (action) { ... } (azioni play, pause, next, prev, stop) riceve lì anche ogni pressione dei pulsanti - l'app non la sovrascrive più. Usa uno dei due, non entrambi, o una pressione viene eseguita due volte.

requestPictureInPicture standard web#

video.requestPictureInPicture() — Picture-in-Picture

Esempio

L'API standard Picture-in-Picture: fa fluttuare un video in una piccola finestra mentre l'utente fa altro. Chrome su Android la ha e la WebView di Android no; nell'app il video riempie la finestra fluttuante, come in Chrome.

Restituisce: una Promise per un PictureInPictureWindow (evento width, height, resize). Il video riceve enterpictureinpicture, poi leavepictureinpicture quando l'utente chiude o espande la finestra; nel frattempo document.pictureInPictureElement è il video. Viene rifiutata con NotAllowedError senza un tocco, con InvalidStateError prima che i metadati del video siano caricati o quando ha disablePictureInPicture. Richiede: Picture-in-picture attivato nella build (si attiva da solo quando il tuo codice chiama requestPictureInPicture), Android 8 o successivo.

<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>

Passa automaticamente al PiP quando l'utente esce e segnala una build non supportata:

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

Attendi i metadati e ridimensiona insieme alla finestra:

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

Note: Android porta l'intera app nella finestra mobile, quindi il video viene prima mostrato da solo (a schermo intero) e la finestra assume la forma del video; uscendo dalla finestra il video torna nella pagina. document.exitPictureInPicture() riporta l'app a schermo intero. Quando la build non ha il picture-in-picture, document.pictureInPictureEnabled è false, come in un browser che non lo supporta. Il cambio di modalità arriva alla pagina come appmint:pip. Trasporti interni: __pipEnter, __pipExit, __pipAvailable, __pipPrepare.

__pipAvailable bridge#

window.WebToApk.__pipAvailable(): Boolean

Se video.requestPictureInPicture() può funzionare: PiP attivo nella build, Android 8+.

Esempio

Documentato insieme a WebToApk.__pipEnter - l'esempio lì mostra anche questo.

Trasporto interno dietro video.requestPictureInPicture() e document.exitPictureInPicture(). Le pagine usano l'API standard; non chiamare mai queste funzioni direttamente, perché i metodi __ possono cambiare senza preavviso.

Restituisce: __pipEnter(width, height) restituisce "ok" quando Android ha accettato la finestra (con il rapporto d'aspetto del video), altrimenti "unsupported" / "failed". L'apertura e la chiusura della finestra arrivano come appmint:pip con {inPip}. __pipAvailable() è vero quando la build ha picture-in-picture su Android 8+. Richiede: picture-in-picture attivato nella build.

Usa l'API pubblica:

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

Controlla prima il supporto e segnala un rifiuto:

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

Esci di nuovo e segui lo stato con gli eventi standard:

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

Note: Implementato nella shell dell'app (PictureInPictureShim.kt). __pipPrepare impedisce alla shell di ruotare lo schermo di lato per il passaggio a schermo intero prima di entrare nella finestra.

__pipEnter bridge#

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

Apre la finestra PiP con le proporzioni di un video [width]×[height]: "ok", oppure "unsupported" / "failed". Il cambio di modalità arriva come `appmint:pip`.

Esempio

Trasporto interno dietro video.requestPictureInPicture() e document.exitPictureInPicture(). Le pagine usano l'API standard; non chiamare mai queste funzioni direttamente, perché i metodi __ possono cambiare senza preavviso.

Restituisce: __pipEnter(width, height) restituisce "ok" quando Android ha accettato la finestra (con il rapporto d'aspetto del video), altrimenti "unsupported" / "failed". L'apertura e la chiusura della finestra arrivano come appmint:pip con {inPip}. __pipAvailable() è vero quando la build ha picture-in-picture su Android 8+. Richiede: picture-in-picture attivato nella build.

Usa l'API pubblica:

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

Controlla prima il supporto e segnala un rifiuto:

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

Esci di nuovo e segui lo stato con gli eventi standard:

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

Note: Implementato nella shell dell'app (PictureInPictureShim.kt). __pipPrepare impedisce alla shell di ruotare lo schermo di lato per il passaggio a schermo intero prima di entrare nella finestra.

__pipExit bridge#

window.WebToApk.__pipExit()

document.exitPictureInPicture(): riporta l'app a schermo intero.

Esempio

Documentato insieme a WebToApk.__pipEnter - l'esempio lì mostra anche questo.

Trasporto interno dietro video.requestPictureInPicture() e document.exitPictureInPicture(). Le pagine usano l'API standard; non chiamare mai queste funzioni direttamente, perché i metodi __ possono cambiare senza preavviso.

Restituisce: __pipEnter(width, height) restituisce "ok" quando Android ha accettato la finestra (con il rapporto d'aspetto del video), altrimenti "unsupported" / "failed". L'apertura e la chiusura della finestra arrivano come appmint:pip con {inPip}. __pipAvailable() è vero quando la build ha picture-in-picture su Android 8+. Richiede: picture-in-picture attivato nella build.

Usa l'API pubblica:

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

Controlla prima il supporto e segnala un rifiuto:

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

Esci di nuovo e segui lo stato con gli eventi standard:

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

Note: Implementato nella shell dell'app (PictureInPictureShim.kt). __pipPrepare impedisce alla shell di ruotare lo schermo di lato per il passaggio a schermo intero prima di entrare nella finestra.

__pipPrepare bridge#

window.WebToApk.__pipPrepare()

La pagina sta per mettere un video a schermo intero per il PiP: non forzare la modalità orizzontale.

Esempio

Documentato insieme a WebToApk.__pipEnter - l'esempio lì mostra anche questo.

Trasporto interno dietro video.requestPictureInPicture() e document.exitPictureInPicture(). Le pagine usano l'API standard; non chiamare mai queste funzioni direttamente, perché i metodi __ possono cambiare senza preavviso.

Restituisce: __pipEnter(width, height) restituisce "ok" quando Android ha accettato la finestra (con il rapporto d'aspetto del video), altrimenti "unsupported" / "failed". L'apertura e la chiusura della finestra arrivano come appmint:pip con {inPip}. __pipAvailable() è vero quando la build ha picture-in-picture su Android 8+. Richiede: picture-in-picture attivato nella build.

Usa l'API pubblica:

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

Controlla prima il supporto e segnala un rifiuto:

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

Esci di nuovo e segui lo stato con gli eventi standard:

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

Note: Implementato nella shell dell'app (PictureInPictureShim.kt). __pipPrepare impedisce alla shell di ruotare lo schermo di lato per il passaggio a schermo intero prima di entrare nella finestra.

castMedia bridge#

window.WebToApk.castMedia(mediaUrl: String)

Propone [mediaUrl] a qualsiasi cosa su questo telefono possa riprodurlo su un altro schermo - Chromecast, un'app per Smart TV, un lettore locale - tramite il selettore di sistema. Il tipo viene dedotto dall'URL ([CastMediaType]): un file audio (.mp3, .m4a, .aac, .ogg, .opus, .wav, .flac …) o un URL `data:` audio viene proposto alle app audio, tutto il resto - file video, manifest di stream, URL senza estensione - alle app video. È un passaggio di consegne, non una sessione di cast: l'app non mantiene il controllo della riproduzione e non c'è alcuna callback. Una pagina che ha bisogno di una vera sessione dovrebbe usare invece il Cast SDK in JavaScript.

Esempio

Propone un indirizzo video o audio alle app sul telefono che possono riprodurlo su un altro schermo (un'app Chromecast, un'app per Smart TV, un lettore video), tramite il selettore Android «Trasmetti su...».

Restituisce: nulla, e nessuna callback. Richiede: nulla da attivare. Il telefono deve avere un'app che accetta il 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);
});

Invia l'episodio di un podcast da un elenco a un altoparlante o a un'app TV (un link .mp3 viene proposto alle app audio):

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

Mostra il pulsante Cast solo dentro l'app e metti in pausa il lettore della pagina prima di passare una diretta:

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

Note:

  • È un passaggio di consegne, non una sessione di cast: la tua pagina non controlla la riproduzione dopo che l'utente ha scelto un'app.
  • L'indirizzo deve essere un link https:// completo che altre app possano aprire. Un file dentro lo ZIP della tua app, un URL blob: o l'indirizzo di una pagina web non verranno riprodotti.
  • Il tipo deriva dal link: un file audio (.mp3, .m4a, .aac, .ogg, .opus, .wav, .flac …) o un URL audio/ data: viene proposto alle app audio; tutto il resto - file video, stream .m3u8, link senza estensione di file - alle app video.

clearMediaNotification bridge#

window.WebToApk.clearMediaNotification()

Permette ai contenuti web di gestire la notifica in stile multimediale di Android e di ricevere le callback dei controlli di riproduzione (play/pause/next/prev) dalla schermata di blocco e dalla tendina delle notifiche. Uso da JavaScript: window.WebToApk.setMediaMetadata('Song Title', 'Artist Name', 'https://…/art.jpg') window.WebToApk.setPlaybackState(true, 30000, 240000) // in riproduzione, pos ms, dur ms window.WebToApk.clearMediaNotification() Callback da Android → JS: window.WebToApkOnMediaAction('play'|'pause'|'next'|'prev'|'stop')

Descritta insieme al suo gruppo, Media Notification Bridge, e non da sola.

Esempio

Rimuove la notifica multimediale e i suoi controlli nella schermata di blocco, per esempio quando l'utente chiude il tuo lettore.

Restituisce: niente. Richiede: attiva Notifica multimediale nel Passaggio 4 (Accesso) quando crei l'app. Senza, la chiamata non fa nulla.

Non esiste una chiamata web standard per questo, quindi usa il bridge:

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

Un timer di spegnimento che ferma l'audio e rimuove la notifica dopo il numero di minuti scelto:

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

Rimuovi la notifica quando l'ultimo brano di una playlist è finito, invece di lasciare un pulsante Play inutile:

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

Note: Usalo per «stop», non per «pausa» (per la pausa invia setPlaybackState(false, ...) così il pulsante Play resta). Impostare navigator.mediaSession.metadata = null non rimuove la notifica. Il prossimo setMediaMetadata / setPlaybackState (o nuovi metadati / stato di navigator.mediaSession) riporta la notifica e i controlli sulla schermata di blocco.

setMediaMetadata bridge#

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

Permette ai contenuti web di gestire la notifica in stile multimediale di Android e di ricevere le callback dei controlli di riproduzione (play/pause/next/prev) dalla schermata di blocco e dalla tendina delle notifiche. Uso da JavaScript: window.WebToApk.setMediaMetadata('Song Title', 'Artist Name', 'https://…/art.jpg') window.WebToApk.setPlaybackState(true, 30000, 240000) // in riproduzione, pos ms, dur ms window.WebToApk.clearMediaNotification() Callback da Android → JS: window.WebToApkOnMediaAction('play'|'pause'|'next'|'prev'|'stop')

Descritta insieme al suo gruppo, Media Notification Bridge, e non da sola.

Esempio

Imposta titolo, artista e immagine di copertina della notifica multimediale e del lettore sulla schermata di blocco. Di solito le pagine impostano navigator.mediaSession.metadata, che lo richiama per te.

Restituisce: niente. Richiede: attiva Notifica multimediale nel Passaggio 4 (Accesso) quando crei l'app. Senza, la chiamata non fa nulla.

Il metodo pubblico (usa questo):

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

La chiamata grezza. Argomenti: title, artist, artworkUrl (usa '' per nessuna immagine):

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

Tieni aggiornato il titolo della notifica per una radio in diretta, dove la canzone cambia senza che la pagina carichi un nuovo file:

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

Note:

  • La notifica compare (o si aggiorna) con questa chiamata. Un titolo vuoto mostra il nome dell'app.
  • artworkUrl deve essere un indirizzo https:// completo su internet. L'app lo scarica (limite di 4 secondi); se non riesce, viene mostrata l'icona dell'app.
  • Ricevi le pressioni dei pulsanti con navigator.mediaSession.setActionHandler (vedi navigator.mediaSession), oppure con il tuo window.WebToApkOnMediaAction = function (action) { ... } (play, pause, next, prev, stop) - usa uno dei due. Dopo clearMediaNotification(), la chiamata successiva a questa (o a setPlaybackState) riporta la notifica e i controlli nella schermata di blocco.

setPlaybackState bridge#

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

Permette ai contenuti web di gestire la notifica in stile multimediale di Android e di ricevere le callback dei controlli di riproduzione (play/pause/next/prev) dalla schermata di blocco e dalla tendina delle notifiche. Uso da JavaScript: window.WebToApk.setMediaMetadata('Song Title', 'Artist Name', 'https://…/art.jpg') window.WebToApk.setPlaybackState(true, 30000, 240000) // in riproduzione, pos ms, dur ms window.WebToApk.clearMediaNotification() Callback da Android → JS: window.WebToApkOnMediaAction('play'|'pause'|'next'|'prev'|'stop')

Descritta insieme al suo gruppo, Media Notification Bridge, e non da sola.

Esempio

Dice alla notifica multimediale se stai riproducendo o sei in pausa, e a che punto sei nel brano. Di solito le pagine impostano navigator.mediaSession.playbackState e chiamano setPositionState, che chiamano questa funzione per te.

Restituisce: niente. Richiede: attiva Notifica multimediale nel Passaggio 4 (Accesso) quando crei l'app. Senza, la chiamata non fa nulla.

Il metodo pubblico (usa questo):

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

La chiamata grezza. Argomenti: isPlaying (true/false), positionMs, durationMs (millisecondi interi):

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

Segnala la nuova posizione dopo che l'utente si sposta nel brano, e passa la notifica su Play quando il brano finisce:

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

Note: true mostra il pulsante Pausa e impedisce di far scorrere via la notifica; false mostra Play. Richiama prima setMediaMetadata, così la notifica ha un titolo. Per rimuovere la notifica quando l'utente ferma la riproduzione, richiama clearMediaNotification().

Generato dal runtime dell'app e dai suoi file di esempio a ogni build della documentazione. Leggilo in Markdown · Tutte le famiglie.

Verificato rispetto al bridge distribuito il 22/10/2026.