Buat APK Gratis

API jembatan JavaScript

Media dan casting

Notifikasi media di layar kunci yang dikendalikan halaman Anda, dan mengirim video ke TV.

navigator.mediaSession standar web#

navigator.mediaSession + MediaMetadata

Contoh

Menampilkan apa yang sedang diputar halaman Anda di notifikasi media dan di layar kunci, dengan tombol Sebelumnya / Putar-Jeda / Berikutnya / Berhenti yang memanggil kembali halaman Anda. Menggunakan Media Session API biasa.

Mengembalikan: tidak ada; penekanan tombol memanggil handler yang Anda atur dengan setActionHandler, dengan { action } sesuai standar. Membutuhkan: aktifkan Media Notification di Langkah 4 (Akses) saat Anda membuat build. Aplikasi buatan AI otomatis mengaktifkannya jika kode menggunakan 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); });

Jaga tombol putar/jeda dan bilah progres tetap benar:

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

Pemutar podcast di mana tombol Berikutnya / Sebelumnya pada notifikasi melompat maju 30 detik dan mundur 15 detik alih-alih berganti episode:

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

Catatan:

  • Di aplikasi, navigator.mediaSession dipasang sebelum skrip pertama halaman, sehingga handler dan metadata dapat diatur kapan saja - di bagian atas halaman atau saat pemutaran dimulai. Ia ada di halaman utama, bukan di dalam iframe.
  • Aksi yang didukung: play, pause, nexttrack, previoustrack, stop. Seek dari notifikasi tidak didukung, jadi seekto tidak pernah aktif.
  • Mengatur playbackState mempertahankan posisi dari panggilan setPositionState terakhir Anda; panggil setPositionState lagi saat posisi benar-benar berubah (seek).
  • Hanya gambar sampul terakhir yang dipakai. Harus berupa alamat https:// lengkap di internet (aplikasi mengunduhnya, batas 4 detik); file di dalam ZIP aplikasi Anda tidak akan tampil.
  • <audio> / <video> elemen dilacak untuk Anda: saat halaman menangani tombol tanpa setActionHandler maupun window.WebToApkOnMediaAction sendiri, Putar / Jeda / Berhenti mengendalikan elemen media yang sedang diputar (jika tidak, yang diputar terakhir, jika tidak, yang pertama).
  • Untuk menghapus notifikasi, panggil WebToApk.clearMediaNotification() (mengatur metadata = null tidak menghapusnya).
  • Halaman yang mendefinisikan window.WebToApkOnMediaAction = function (action) { ... } (aksi play, pause, next, prev, stop) juga menerima setiap penekanan tombol di sana - aplikasi tidak lagi menimpanya. Gunakan salah satu dari keduanya, jangan keduanya, atau satu penekanan berjalan dua kali.

requestPictureInPicture standar web#

video.requestPictureInPicture() — Picture-in-Picture

Contoh

API Picture-in-Picture standar: mengambangkan video di jendela kecil saat pengguna melakukan hal lain. Chrome di Android memilikinya dan WebView Android tidak; di aplikasi video memenuhi jendela mengambang, seperti di Chrome.

Mengembalikan: Promise untuk PictureInPictureWindow (event width, height, resize). Video mendapat enterpictureinpicture, lalu leavepictureinpicture saat pengguna menutup atau memperluas jendela; document.pictureInPictureElement adalah video itu sementara itu. Ditolak dengan NotAllowedError tanpa ketukan, InvalidStateError sebelum metadata video dimuat atau saat ada disablePictureInPicture. Memerlukan: Picture-in-picture diaktifkan di build (aktif sendiri saat kode Anda memanggil requestPictureInPicture), Android 8 atau lebih baru.

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

Munculkan otomatis saat pengguna pergi, dan laporkan build yang tidak mendukung:

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

Tunggu metadata, dan ubah ukuran bersama jendela:

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

Catatan: Android memasukkan seluruh aplikasi ke jendela mengambang, sehingga video lebih dulu ditampilkan sendiri (layar penuh) dan jendela mengikuti bentuk video; saat keluar dari jendela, video kembali ke halaman. document.exitPictureInPicture() mengembalikan aplikasi ke layar penuh. Jika build tidak memiliki picture-in-picture, document.pictureInPictureEnabled bernilai false, seperti di browser yang tidak mendukungnya. Perubahan mode sampai ke halaman sebagai appmint:pip. Transport internal: __pipEnter, __pipExit, __pipAvailable, __pipPrepare.

__pipAvailable jembatan#

window.WebToApk.__pipAvailable(): Boolean

Apakah video.requestPictureInPicture() bisa berfungsi: PiP aktif di build, Android 8+.

Contoh

Didokumentasikan bersama WebToApk.__pipEnter - contoh di sana menunjukkan yang ini juga.

Transport internal di balik video.requestPictureInPicture() dan document.exitPictureInPicture(). Halaman memakai API standar; jangan pernah memanggil ini langsung, karena metode __ dapat berubah tanpa pemberitahuan.

Mengembalikan: __pipEnter(width, height) mengembalikan "ok" saat Android menerima jendela (dengan rasio aspek video), jika tidak "unsupported" / "failed". Pembukaan dan penutupan jendela datang sebagai appmint:pip dengan {inPip}. __pipAvailable() bernilai true saat build mengaktifkan picture-in-picture di Android 8+. Memerlukan: picture-in-picture diaktifkan di build.

Gunakan API publik:

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

Periksa dukungan dulu, dan laporkan penolakan:

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

Keluar lagi dan ikuti statusnya dengan event standar:

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

Catatan: Diimplementasikan di shell aplikasi (PictureInPictureShim.kt). __pipPrepare mencegah shell memutar layar ke samping untuk langkah layar penuh saat masuk ke jendela.

__pipEnter jembatan#

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

Membuka jendela PiP dengan rasio aspek video [width]×[height]: "ok", atau "unsupported" / "failed". Perubahan mode datang sebagai `appmint:pip`.

Contoh

Transport internal di balik video.requestPictureInPicture() dan document.exitPictureInPicture(). Halaman memakai API standar; jangan pernah memanggil ini langsung, karena metode __ dapat berubah tanpa pemberitahuan.

Mengembalikan: __pipEnter(width, height) mengembalikan "ok" saat Android menerima jendela (dengan rasio aspek video), jika tidak "unsupported" / "failed". Pembukaan dan penutupan jendela datang sebagai appmint:pip dengan {inPip}. __pipAvailable() bernilai true saat build mengaktifkan picture-in-picture di Android 8+. Memerlukan: picture-in-picture diaktifkan di build.

Gunakan API publik:

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

Periksa dukungan dulu, dan laporkan penolakan:

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

Keluar lagi dan ikuti statusnya dengan event standar:

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

Catatan: Diimplementasikan di shell aplikasi (PictureInPictureShim.kt). __pipPrepare mencegah shell memutar layar ke samping untuk langkah layar penuh saat masuk ke jendela.

__pipExit jembatan#

window.WebToApk.__pipExit()

document.exitPictureInPicture(): mengembalikan aplikasi ke layar penuh.

Contoh

Didokumentasikan bersama WebToApk.__pipEnter - contoh di sana menunjukkan yang ini juga.

Transport internal di balik video.requestPictureInPicture() dan document.exitPictureInPicture(). Halaman memakai API standar; jangan pernah memanggil ini langsung, karena metode __ dapat berubah tanpa pemberitahuan.

Mengembalikan: __pipEnter(width, height) mengembalikan "ok" saat Android menerima jendela (dengan rasio aspek video), jika tidak "unsupported" / "failed". Pembukaan dan penutupan jendela datang sebagai appmint:pip dengan {inPip}. __pipAvailable() bernilai true saat build mengaktifkan picture-in-picture di Android 8+. Memerlukan: picture-in-picture diaktifkan di build.

Gunakan API publik:

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

Periksa dukungan dulu, dan laporkan penolakan:

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

Keluar lagi dan ikuti statusnya dengan event standar:

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

Catatan: Diimplementasikan di shell aplikasi (PictureInPictureShim.kt). __pipPrepare mencegah shell memutar layar ke samping untuk langkah layar penuh saat masuk ke jendela.

__pipPrepare jembatan#

window.WebToApk.__pipPrepare()

Halaman akan menampilkan video layar penuh untuk PiP: jangan paksa mode lanskap untuknya.

Contoh

Didokumentasikan bersama WebToApk.__pipEnter - contoh di sana menunjukkan yang ini juga.

Transport internal di balik video.requestPictureInPicture() dan document.exitPictureInPicture(). Halaman memakai API standar; jangan pernah memanggil ini langsung, karena metode __ dapat berubah tanpa pemberitahuan.

Mengembalikan: __pipEnter(width, height) mengembalikan "ok" saat Android menerima jendela (dengan rasio aspek video), jika tidak "unsupported" / "failed". Pembukaan dan penutupan jendela datang sebagai appmint:pip dengan {inPip}. __pipAvailable() bernilai true saat build mengaktifkan picture-in-picture di Android 8+. Memerlukan: picture-in-picture diaktifkan di build.

Gunakan API publik:

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

Periksa dukungan dulu, dan laporkan penolakan:

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

Keluar lagi dan ikuti statusnya dengan event standar:

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

Catatan: Diimplementasikan di shell aplikasi (PictureInPictureShim.kt). __pipPrepare mencegah shell memutar layar ke samping untuk langkah layar penuh saat masuk ke jendela.

castMedia jembatan#

window.WebToApk.castMedia(mediaUrl: String)

Menawarkan [mediaUrl] ke apa pun di ponsel ini yang dapat memutarnya di layar lain - Chromecast, aplikasi Smart TV, pemutar lokal - lewat pemilih sistem. Tipenya diambil dari URL ([CastMediaType]): berkas audio (.mp3, .m4a, .aac, .ogg, .opus, .wav, .flac …) atau URL `data:` audio ditawarkan ke aplikasi audio, selain itu - berkas video, manifes stream, URL tanpa ekstensi - ke aplikasi video. Ini adalah penyerahan, bukan sesi cast: aplikasi tidak tetap mengendalikan pemutaran, dan tidak ada callback. Halaman yang membutuhkan sesi sungguhan sebaiknya memakai Cast SDK di JavaScript.

Contoh

Menawarkan alamat video atau audio ke aplikasi di ponsel yang dapat memutarnya di layar lain (aplikasi Chromecast, aplikasi Smart TV, pemutar video), lewat pemilih "Cast ke..." Android.

Mengembalikan: tidak ada, dan tanpa callback. Membutuhkan: tidak ada yang perlu diaktifkan. Ponsel membutuhkan aplikasi yang menerima tautan tersebut.

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

Kirim episode podcast dari daftar ke speaker atau aplikasi TV (tautan .mp3 ditawarkan ke aplikasi 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);
  }
});

Tampilkan tombol Cast hanya di dalam aplikasi, dan jeda pemutar halaman sendiri sebelum menyerahkan siaran langsung:

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

Catatan:

  • Ini adalah serah-terima, bukan sesi cast: halaman Anda tidak mengendalikan pemutaran setelah pengguna memilih aplikasi.
  • Alamat harus berupa tautan https:// lengkap yang dapat dibuka aplikasi lain. Berkas di dalam ZIP aplikasi Anda, URL blob: atau alamat halaman web tidak akan diputar.
  • Tipe berasal dari tautan: berkas audio (.mp3, .m4a, .aac, .ogg, .opus, .wav, .flac …) atau URL audio/ data: ditawarkan ke aplikasi audio; selain itu - berkas video, stream .m3u8, tautan tanpa ekstensi berkas - ke aplikasi video.

clearMediaNotification jembatan#

window.WebToApk.clearMediaNotification()

Memungkinkan konten web mengendalikan notifikasi bergaya media Android dan menerima callback kontrol transport (putar/jeda/berikutnya/sebelumnya) dari layar kunci dan bayangan notifikasi. Penggunaan dari JavaScript: window.WebToApk.setMediaMetadata('Song Title', 'Artist Name', 'https://…/art.jpg') window.WebToApk.setPlaybackState(true, 30000, 240000) // memutar, posisi ms, durasi ms window.WebToApk.clearMediaNotification() Callback dari Android → JS: window.WebToApkOnMediaAction('play'|'pause'|'next'|'prev'|'stop')

Dijelaskan oleh grupnya, Jembatan Notifikasi Media, bukan sendiri.

Contoh

Menghapus notifikasi media dan kontrol layar kuncinya, misalnya saat pengguna menutup pemutar Anda.

Mengembalikan: tidak ada. Memerlukan: aktifkan Notifikasi Media di Langkah 4 (Akses) saat Anda membuat build. Tanpanya panggilan ini tidak melakukan apa-apa.

Tidak ada panggilan web standar untuk ini, jadi gunakan jembatan:

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

Timer tidur yang menghentikan audio dan menghapus notifikasi setelah jumlah menit yang dipilih:

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

Hapus notifikasi saat trek terakhir daftar putar selesai, daripada membiarkan tombol Putar tanpa guna:

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

Catatan: Gunakan ini untuk "berhenti", bukan untuk "jeda" (untuk jeda, kirim setPlaybackState(false, ...) agar tombol Putar tetap ada). Mengatur navigator.mediaSession.metadata = null tidak menghapus notifikasi. setMediaMetadata / setPlaybackState berikutnya (atau metadata / status navigator.mediaSession yang baru) memunculkan kembali notifikasi dan kontrol layar kunci.

setMediaMetadata jembatan#

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

Memungkinkan konten web mengendalikan notifikasi bergaya media Android dan menerima callback kontrol transport (putar/jeda/berikutnya/sebelumnya) dari layar kunci dan bayangan notifikasi. Penggunaan dari JavaScript: window.WebToApk.setMediaMetadata('Song Title', 'Artist Name', 'https://…/art.jpg') window.WebToApk.setPlaybackState(true, 30000, 240000) // memutar, posisi ms, durasi ms window.WebToApk.clearMediaNotification() Callback dari Android → JS: window.WebToApkOnMediaAction('play'|'pause'|'next'|'prev'|'stop')

Dijelaskan oleh grupnya, Jembatan Notifikasi Media, bukan sendiri.

Contoh

Mengatur judul, artis, dan gambar sampul notifikasi media serta pemutar layar kunci. Halaman biasanya mengatur navigator.mediaSession.metadata, yang memanggil ini untuk Anda.

Mengembalikan: tidak ada. Memerlukan: aktifkan Notifikasi Media di Langkah 4 (Akses) saat Anda membuat build. Tanpanya panggilan ini tidak melakukan apa-apa.

Cara publik (gunakan ini):

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

Panggilan mentah. Argumen: title, artist, artworkUrl (gunakan '' untuk tanpa gambar):

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

Jaga judul notifikasi tetap terbaru untuk siaran radio langsung, di mana lagu berganti tanpa halaman memuat berkas baru:

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

Catatan:

  • Notifikasi muncul (atau diperbarui) dengan panggilan ini. Judul kosong menampilkan nama aplikasi.
  • artworkUrl harus berupa alamat https:// lengkap di internet. Aplikasi mengunduhnya (batas 4 detik); jika gagal, ikon aplikasi ditampilkan.
  • Terima penekanan tombol dengan navigator.mediaSession.setActionHandler (lihat navigator.mediaSession), atau dengan window.WebToApkOnMediaAction = function (action) { ... } Anda sendiri (play, pause, next, prev, stop) - gunakan salah satu dari keduanya. Setelah clearMediaNotification(), panggilan berikutnya ke ini (atau setPlaybackState) memunculkan kembali notifikasi dan kontrol layar kunci.

setPlaybackState jembatan#

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

Memungkinkan konten web mengendalikan notifikasi bergaya media Android dan menerima callback kontrol transport (putar/jeda/berikutnya/sebelumnya) dari layar kunci dan bayangan notifikasi. Penggunaan dari JavaScript: window.WebToApk.setMediaMetadata('Song Title', 'Artist Name', 'https://…/art.jpg') window.WebToApk.setPlaybackState(true, 30000, 240000) // memutar, posisi ms, durasi ms window.WebToApk.clearMediaNotification() Callback dari Android → JS: window.WebToApkOnMediaAction('play'|'pause'|'next'|'prev'|'stop')

Dijelaskan oleh grupnya, Jembatan Notifikasi Media, bukan sendiri.

Contoh

Memberi tahu notifikasi media apakah Anda sedang memutar atau dijeda, dan di mana posisi Anda dalam trek. Halaman biasanya mengatur navigator.mediaSession.playbackState dan memanggil setPositionState, yang memanggil ini untuk Anda.

Mengembalikan: tidak ada. Memerlukan: aktifkan Notifikasi Media di Langkah 4 (Akses) saat Anda membuat build. Tanpanya panggilan ini tidak melakukan apa-apa.

Cara publik (gunakan ini):

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

Panggilan mentah. Argumen: isPlaying (true/false), positionMs, durationMs (milidetik bulat):

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

Laporkan posisi baru setelah pengguna melakukan seek, dan alihkan notifikasi ke Putar saat trek berakhir:

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

Catatan: true menampilkan tombol Jeda dan mencegah notifikasi digeser hilang; false menampilkan Putar. Panggil setMediaMetadata lebih dulu, agar notifikasi punya judul. Untuk menghapus notifikasi saat pengguna berhenti, panggil clearMediaNotification().

Dibuat dari runtime aplikasi dan file contohnya pada setiap build dokumentasi. Baca sebagai Markdown · Semua keluarga.

Diperiksa terhadap jembatan yang dirilis pada 2026-10-22.