मुफ़्त APK बनाएँ

JavaScript ब्रिज API

मीडिया और कास्टिंग

लॉक-स्क्रीन मीडिया नोटिफ़िकेशन जिसे आपका पेज चलाता है, और वीडियो को TV पर भेजना।

navigator.mediaSession वेब स्टैंडर्ड#

navigator.mediaSession + MediaMetadata

उदाहरण

आपका पेज जो चला रहा है उसे मीडिया नोटिफ़िकेशन और लॉक स्क्रीन पर दिखाता है, Previous / Play-Pause / Next / Stop बटन के साथ जो आपके पेज को वापस कॉल करते हैं। यह सामान्य Media Session API इस्तेमाल करता है।

लौटाता है: कुछ नहीं; बटन प्रेस उन हैंडलरों को कॉल करते हैं जो आपने setActionHandler से सेट किए, मानक के अनुसार { action } के साथ। ज़रूरत: बिल्ड करते समय चरण 4 (Access) में मीडिया नोटिफ़िकेशन चालू करें। AI से बने ऐप्स में यह अपने आप चालू हो जाता है जब कोड इसका इस्तेमाल करता है: 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); });

प्ले/पॉज़ बटन और प्रोग्रेस बार को सही रखें:

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

एक पॉडकास्ट प्लेयर जहाँ नोटिफ़िकेशन के Next / Previous बटन एपिसोड बदलने के बजाय 30 सेकंड आगे और 15 सेकंड पीछे जाते हैं:

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

ध्यान दें:

  • ऐप में, navigator.mediaSession पेज की पहली स्क्रिप्ट से पहले इंस्टॉल हो जाता है, इसलिए हैंडलर और मेटाडेटा कभी भी सेट किए जा सकते हैं - पेज के ऊपर या प्लेबैक शुरू होने पर। यह मुख्य पेज में होता है, iframe के अंदर नहीं।
  • समर्थित एक्शन: play, pause, nexttrack, previoustrack, stop। नोटिफ़िकेशन से सीक करना समर्थित नहीं है, इसलिए seekto कभी नहीं चलता।
  • playbackState सेट करने से आपकी पिछली setPositionState कॉल की पोज़िशन बनी रहती है; जब पोज़िशन सच में बदले (सीक), तब setPositionState फिर से कॉल करें।
  • सिर्फ़ आखिरी आर्टवर्क तस्वीर इस्तेमाल होती है। यह इंटरनेट पर पूरा https:// पता होना चाहिए (ऐप इसे डाउनलोड करता है, 4 सेकंड की सीमा); आपके ऐप ZIP के अंदर की फ़ाइल नहीं दिखती।
  • <audio> / <video> एलिमेंट अपने आप ट्रैक होते हैं: जब पेज किसी बटन को न setActionHandler से संभालता है न अपने window.WebToApkOnMediaAction से, तब Play / Pause / Stop उस मीडिया एलिमेंट को चलाते हैं जो बज रहा है (वरना जो आखिरी बार बजा, वरना पहला)।
  • नोटिफ़िकेशन हटाने के लिए WebToApk.clearMediaNotification() कॉल करें (metadata = null सेट करने से वह नहीं हटता)।
  • जो पेज window.WebToApkOnMediaAction = function (action) { ... } परिभाषित करता है (एक्शन play, pause, next, prev, stop) उसे हर बटन प्रेस वहाँ भी मिलता है - ऐप अब उसे नहीं बदलता। दोनों में से एक ही इस्तेमाल करें, दोनों नहीं, वरना एक प्रेस दो बार चलेगा।

requestPictureInPicture वेब स्टैंडर्ड#

video.requestPictureInPicture() — Picture-in-Picture

उदाहरण

मानक पिक्चर-इन-पिक्चर API: उपयोगकर्ता कुछ और करे तब वीडियो को छोटी तैरती विंडो में रखें। Android पर Chrome में यह है और Android के WebView में नहीं; ऐप में वीडियो तैरती विंडो को भर देता है, जैसा Chrome में होता है।

लौटाता है: एक Promise, PictureInPictureWindow के लिए (width, height, resize इवेंट)। वीडियो को enterpictureinpicture मिलता है, फिर leavepictureinpicture जब उपयोगकर्ता विंडो बंद या बड़ी करता है; इस बीच document.pictureInPictureElement वही वीडियो होता है। बिना टैप के NotAllowedError के साथ रिजेक्ट होता है, वीडियो का मेटाडेटा लोड होने से पहले या जब उस पर disablePictureInPicture हो तब InvalidStateError के साथ। ज़रूरत: बिल्ड में पिक्चर-इन-पिक्चर चालू हो (जब आपका कोड requestPictureInPicture कॉल करता है तो वह अपने आप चालू हो जाता है), Android 8 या नया।

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

उपयोगकर्ता के जाते ही अपने आप बाहर निकलें, और असमर्थित बिल्ड की सूचना दें:

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

मेटाडेटा का इंतज़ार करें, और विंडो के साथ आकार बदलें:

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

नोट: Android पूरे ऐप को तैरती विंडो में डाल देता है, इसलिए वीडियो पहले अकेला (फ़ुल स्क्रीन) दिखता है और विंडो वीडियो का आकार ले लेती है; विंडो छोड़ने पर वीडियो पेज में वापस आ जाता है। document.exitPictureInPicture() ऐप को फ़ुल स्क्रीन पर लौटाता है। जब बिल्ड में पिक्चर-इन-पिक्चर नहीं है, तब document.pictureInPictureEnabled का मान false होता है, जैसे उसके बिना ब्राउज़र में। मोड बदलना पेज को appmint:pip के रूप में मिलता है। आंतरिक ट्रांसपोर्ट: __pipEnter, __pipExit, __pipAvailable, __pipPrepare.

__pipAvailable ब्रिज#

window.WebToApk.__pipAvailable(): Boolean

क्या video.requestPictureInPicture() काम कर सकता है: बिल्ड में PiP चालू, Android 8+।

उदाहरण

WebToApk.__pipEnter के साथ दस्तावेज़ित - वहाँ का उदाहरण इसे भी दिखाता है।

video.requestPictureInPicture() और document.exitPictureInPicture() के पीछे का आंतरिक ट्रांसपोर्ट। पेज मानक API इस्तेमाल करते हैं; इन्हें कभी सीधे कॉल न करें, क्योंकि __ वाले तरीके बिना सूचना के बदल सकते हैं।

लौटाता है: __pipEnter(width, height) जब Android विंडो स्वीकार कर ले (वीडियो के आस्पेक्ट रेशियो के साथ) तो "ok" लौटाता है, वरना "unsupported" / "failed"। विंडो का खुलना और बंद होना appmint:pip के रूप में {inPip} के साथ आता है। जब बिल्ड में Android 8+ पर पिक्चर-इन-पिक्चर हो तो __pipAvailable() true होता है। ज़रूरत: बिल्ड में पिक्चर-इन-पिक्चर चालू हो।

पब्लिक API इस्तेमाल करें:

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

पहले समर्थन जाँचें, और इनकार की सूचना दें:

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

इसे फिर छोड़ें और मानक इवेंट से स्थिति पर नज़र रखें:

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

नोट: ऐप शेल (PictureInPictureShim.kt) में लागू। __pipPrepare शेल को विंडो में जाते समय फ़ुल-स्क्रीन चरण के लिए स्क्रीन को आड़ा घुमाने से रोकता है।

__pipEnter ब्रिज#

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

PiP विंडो को [width]×[height] वीडियो के आस्पेक्ट रेशियो के साथ खोलता है: "ok", या "unsupported" / "failed"। मोड बदलना इस रूप में आता है: `appmint:pip`.

उदाहरण

video.requestPictureInPicture() और document.exitPictureInPicture() के पीछे का आंतरिक ट्रांसपोर्ट। पेज मानक API इस्तेमाल करते हैं; इन्हें कभी सीधे कॉल न करें, क्योंकि __ वाले तरीके बिना सूचना के बदल सकते हैं।

लौटाता है: __pipEnter(width, height) जब Android विंडो स्वीकार कर ले (वीडियो के आस्पेक्ट रेशियो के साथ) तो "ok" लौटाता है, वरना "unsupported" / "failed"। विंडो का खुलना और बंद होना appmint:pip के रूप में {inPip} के साथ आता है। जब बिल्ड में Android 8+ पर पिक्चर-इन-पिक्चर हो तो __pipAvailable() true होता है। ज़रूरत: बिल्ड में पिक्चर-इन-पिक्चर चालू हो।

पब्लिक API इस्तेमाल करें:

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

पहले समर्थन जाँचें, और इनकार की सूचना दें:

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

इसे फिर छोड़ें और मानक इवेंट से स्थिति पर नज़र रखें:

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

नोट: ऐप शेल (PictureInPictureShim.kt) में लागू। __pipPrepare शेल को विंडो में जाते समय फ़ुल-स्क्रीन चरण के लिए स्क्रीन को आड़ा घुमाने से रोकता है।

__pipExit ब्रिज#

window.WebToApk.__pipExit()

document.exitPictureInPicture(): ऐप को फ़ुल स्क्रीन पर वापस लाता है।

उदाहरण

WebToApk.__pipEnter के साथ दस्तावेज़ित - वहाँ का उदाहरण इसे भी दिखाता है।

video.requestPictureInPicture() और document.exitPictureInPicture() के पीछे का आंतरिक ट्रांसपोर्ट। पेज मानक API इस्तेमाल करते हैं; इन्हें कभी सीधे कॉल न करें, क्योंकि __ वाले तरीके बिना सूचना के बदल सकते हैं।

लौटाता है: __pipEnter(width, height) जब Android विंडो स्वीकार कर ले (वीडियो के आस्पेक्ट रेशियो के साथ) तो "ok" लौटाता है, वरना "unsupported" / "failed"। विंडो का खुलना और बंद होना appmint:pip के रूप में {inPip} के साथ आता है। जब बिल्ड में Android 8+ पर पिक्चर-इन-पिक्चर हो तो __pipAvailable() true होता है। ज़रूरत: बिल्ड में पिक्चर-इन-पिक्चर चालू हो।

पब्लिक API इस्तेमाल करें:

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

पहले समर्थन जाँचें, और इनकार की सूचना दें:

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

इसे फिर छोड़ें और मानक इवेंट से स्थिति पर नज़र रखें:

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

नोट: ऐप शेल (PictureInPictureShim.kt) में लागू। __pipPrepare शेल को विंडो में जाते समय फ़ुल-स्क्रीन चरण के लिए स्क्रीन को आड़ा घुमाने से रोकता है।

__pipPrepare ब्रिज#

window.WebToApk.__pipPrepare()

पेज PiP के लिए वीडियो को फ़ुलस्क्रीन करने वाला है: इसके लिए लैंडस्केप को ज़बरदस्ती न लगाएँ।

उदाहरण

WebToApk.__pipEnter के साथ दस्तावेज़ित - वहाँ का उदाहरण इसे भी दिखाता है।

video.requestPictureInPicture() और document.exitPictureInPicture() के पीछे का आंतरिक ट्रांसपोर्ट। पेज मानक API इस्तेमाल करते हैं; इन्हें कभी सीधे कॉल न करें, क्योंकि __ वाले तरीके बिना सूचना के बदल सकते हैं।

लौटाता है: __pipEnter(width, height) जब Android विंडो स्वीकार कर ले (वीडियो के आस्पेक्ट रेशियो के साथ) तो "ok" लौटाता है, वरना "unsupported" / "failed"। विंडो का खुलना और बंद होना appmint:pip के रूप में {inPip} के साथ आता है। जब बिल्ड में Android 8+ पर पिक्चर-इन-पिक्चर हो तो __pipAvailable() true होता है। ज़रूरत: बिल्ड में पिक्चर-इन-पिक्चर चालू हो।

पब्लिक API इस्तेमाल करें:

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

पहले समर्थन जाँचें, और इनकार की सूचना दें:

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

इसे फिर छोड़ें और मानक इवेंट से स्थिति पर नज़र रखें:

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

नोट: ऐप शेल (PictureInPictureShim.kt) में लागू। __pipPrepare शेल को विंडो में जाते समय फ़ुल-स्क्रीन चरण के लिए स्क्रीन को आड़ा घुमाने से रोकता है।

castMedia ब्रिज#

window.WebToApk.castMedia(mediaUrl: String)

[mediaUrl] को फ़ोन की उन चीज़ों के सामने रखता है जो उसे दूसरी स्क्रीन पर चला सकती हैं - Chromecast, Smart TV ऐप, लोकल प्लेयर - सिस्टम चूज़र के ज़रिए। टाइप URL से लिया जाता है ([CastMediaType]): ऑडियो फ़ाइल (.mp3, .m4a, .aac, .ogg, .opus, .wav, .flac …) या ऑडियो `data:` URL ऑडियो ऐप्स को दिया जाता है, बाकी सब - वीडियो फ़ाइलें, स्ट्रीम मैनिफ़ेस्ट, बिना एक्सटेंशन वाले URL - वीडियो ऐप्स को। यह सिर्फ़ सौंपना है, कास्ट सेशन नहीं: ऐप प्लेबैक पर नियंत्रण नहीं रखता, और कोई कॉलबैक नहीं है। जिस पेज को असली सेशन चाहिए, उसे JavaScript में Cast SDK इस्तेमाल करना चाहिए।

उदाहरण

वीडियो या ऑडियो का पता फ़ोन के उन ऐप्स को देता है जो उसे दूसरी स्क्रीन पर चला सकते हैं (Chromecast ऐप, Smart TV ऐप, वीडियो प्लेयर), Android के "Cast to..." चूज़र के ज़रिए।

लौटाता है: कुछ नहीं, और कोई कॉलबैक नहीं। ज़रूरत: चालू करने के लिए कुछ नहीं। फ़ोन में ऐसा ऐप होना चाहिए जो लिंक स्वीकार करे।

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

सूची से पॉडकास्ट एपिसोड को स्पीकर या TV ऐप पर भेजें (.mp3 लिंक ऑडियो ऐप्स को दिया जाता है):

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

Cast बटन सिर्फ़ ऐप के अंदर दिखाएँ, और लाइव स्ट्रीम सौंपने से पहले पेज का अपना प्लेयर रोक दें:

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

ध्यान दें:

  • यह सिर्फ़ सौंपना है, कास्ट सेशन नहीं: उपयोगकर्ता के ऐप चुनने के बाद आपका पेज प्लेबैक को नियंत्रित नहीं करता।
  • पता पूरा https:// लिंक होना चाहिए जिसे दूसरे ऐप्स खोल सकें। आपके ऐप ZIP के अंदर की फ़ाइल, blob: URL या वेब पेज का पता नहीं चलेगा।
  • टाइप लिंक से आता है: ऑडियो फ़ाइल (.mp3, .m4a, .aac, .ogg, .opus, .wav, .flac …) या audio/ data: URL ऑडियो ऐप्स को दिया जाता है; बाकी सब - वीडियो फ़ाइलें, .m3u8 स्ट्रीम, बिना फ़ाइल एक्सटेंशन वाले लिंक - वीडियो ऐप्स को।

clearMediaNotification ब्रिज#

window.WebToApk.clearMediaNotification()

वेब कंटेंट को Android के मीडिया-स्टाइल नोटिफ़िकेशन को चलाने देता है और लॉक स्क्रीन व नोटिफ़िकेशन शेड से ट्रांसपोर्ट कंट्रोल कॉलबैक (play/pause/next/prev) पाने देता है। JavaScript से इस्तेमाल: window.WebToApk.setMediaMetadata('Song Title', 'Artist Name', 'https://…/art.jpg') window.WebToApk.setPlaybackState(true, 30000, 240000) // बज रहा है, पोज़िशन ms, अवधि ms window.WebToApk.clearMediaNotification() Android → JS कॉलबैक: window.WebToApkOnMediaAction('play'|'pause'|'next'|'prev'|'stop')

इसे अलग से नहीं, बल्कि इसके समूह मीडिया नोटिफ़िकेशन ब्रिज में बताया गया है।

उदाहरण

मीडिया नोटिफ़िकेशन और उसके लॉक-स्क्रीन कंट्रोल हटाता है, जैसे जब उपयोगकर्ता आपका प्लेयर बंद करे।

लौटाता है: कुछ नहीं। ज़रूरत: बिल्ड करते समय चरण 4 (Access) में मीडिया नोटिफ़िकेशन चालू करें। इसके बिना कॉल कुछ नहीं करती।

इसके लिए कोई मानक वेब कॉल नहीं है, इसलिए ब्रिज इस्तेमाल करें:

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

एक स्लीप टाइमर जो चुने हुए मिनटों के बाद ऑडियो रोक देता है और नोटिफ़िकेशन हटा देता है:

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

प्लेलिस्ट का आखिरी ट्रैक खत्म होने पर नोटिफ़िकेशन हटा दें, बेकार का Play बटन छोड़ने के बजाय:

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

नोट: इसे "स्टॉप" के लिए इस्तेमाल करें, "पॉज़" के लिए नहीं (पॉज़ के लिए setPlaybackState(false, ...) भेजें ताकि Play बटन बना रहे)। navigator.mediaSession.metadata = null सेट करने से नोटिफ़िकेशन नहीं हटता। अगली setMediaMetadata / setPlaybackState कॉल (या नया navigator.mediaSession मेटाडेटा / स्थिति) नोटिफ़िकेशन और लॉक-स्क्रीन कंट्रोल को वापस ले आती है।

setMediaMetadata ब्रिज#

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

वेब कंटेंट को Android के मीडिया-स्टाइल नोटिफ़िकेशन को चलाने देता है और लॉक स्क्रीन व नोटिफ़िकेशन शेड से ट्रांसपोर्ट कंट्रोल कॉलबैक (play/pause/next/prev) पाने देता है। JavaScript से इस्तेमाल: window.WebToApk.setMediaMetadata('Song Title', 'Artist Name', 'https://…/art.jpg') window.WebToApk.setPlaybackState(true, 30000, 240000) // बज रहा है, पोज़िशन ms, अवधि ms window.WebToApk.clearMediaNotification() Android → JS कॉलबैक: window.WebToApkOnMediaAction('play'|'pause'|'next'|'prev'|'stop')

इसे अलग से नहीं, बल्कि इसके समूह मीडिया नोटिफ़िकेशन ब्रिज में बताया गया है।

उदाहरण

मीडिया नोटिफ़िकेशन और लॉक-स्क्रीन प्लेयर का शीर्षक, कलाकार और कवर तस्वीर सेट करता है। पेज आम तौर पर navigator.mediaSession.metadata सेट करते हैं, जो आपके लिए इसे कॉल कर देता है।

लौटाता है: कुछ नहीं। ज़रूरत: बिल्ड करते समय चरण 4 (Access) में मीडिया नोटिफ़िकेशन चालू करें। इसके बिना कॉल कुछ नहीं करती।

सार्वजनिक तरीका (यही इस्तेमाल करें):

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

सीधी कॉल। आर्गुमेंट: title, artist, artworkUrl (बिना तस्वीर के लिए '' दें):

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

लाइव रेडियो स्ट्रीम के लिए नोटिफ़िकेशन का शीर्षक अपडेट रखें, जहाँ पेज नई फ़ाइल लोड किए बिना गाना बदल जाता है:

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

ध्यान दें:

  • इसी कॉल से नोटिफ़िकेशन दिखता (या अपडेट होता) है। खाली शीर्षक होने पर ऐप का नाम दिखता है।
  • artworkUrl इंटरनेट पर पूरा https:// पता होना चाहिए। ऐप इसे डाउनलोड करता है (4 सेकंड की सीमा); अगर यह न हो सके, तो ऐप आइकन दिखता है।
  • बटन प्रेस navigator.mediaSession.setActionHandler से पाएँ (देखें navigator.mediaSession), या अपने window.WebToApkOnMediaAction = function (action) { ... } से (play, pause, next, prev, stop) - दोनों में से एक ही इस्तेमाल करें। clearMediaNotification() के बाद, इसकी (या setPlaybackState की) अगली कॉल नोटिफ़िकेशन और लॉक-स्क्रीन कंट्रोल वापस ले आती है।

setPlaybackState ब्रिज#

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

वेब कंटेंट को Android के मीडिया-स्टाइल नोटिफ़िकेशन को चलाने देता है और लॉक स्क्रीन व नोटिफ़िकेशन शेड से ट्रांसपोर्ट कंट्रोल कॉलबैक (play/pause/next/prev) पाने देता है। JavaScript से इस्तेमाल: window.WebToApk.setMediaMetadata('Song Title', 'Artist Name', 'https://…/art.jpg') window.WebToApk.setPlaybackState(true, 30000, 240000) // बज रहा है, पोज़िशन ms, अवधि ms window.WebToApk.clearMediaNotification() Android → JS कॉलबैक: window.WebToApkOnMediaAction('play'|'pause'|'next'|'prev'|'stop')

इसे अलग से नहीं, बल्कि इसके समूह मीडिया नोटिफ़िकेशन ब्रिज में बताया गया है।

उदाहरण

मीडिया नोटिफ़िकेशन को बताता है कि आप चला रहे हैं या रोका है, और ट्रैक में कहाँ हैं। पेज आम तौर पर navigator.mediaSession.playbackState सेट करते हैं और setPositionState कॉल करते हैं, जो आपके लिए इसे कॉल कर देते हैं।

लौटाता है: कुछ नहीं। ज़रूरत: बिल्ड करते समय चरण 4 (Access) में मीडिया नोटिफ़िकेशन चालू करें। इसके बिना कॉल कुछ नहीं करती।

सार्वजनिक तरीका (यही इस्तेमाल करें):

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

सीधी कॉल। आर्गुमेंट: isPlaying (true/false), positionMs, durationMs (पूरे मिलीसेकंड):

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

उपयोगकर्ता के सीक करने के बाद नई पोज़िशन बताएँ, और ट्रैक खत्म होने पर नोटिफ़िकेशन को Play पर ले जाएँ:

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

नोट: true Pause बटन दिखाता है और नोटिफ़िकेशन को स्वाइप करके हटने से रोकता है; false Play दिखाता है। पहले setMediaMetadata कॉल करें, ताकि नोटिफ़िकेशन का शीर्षक हो। उपयोगकर्ता के रोकने पर नोटिफ़िकेशन हटाने के लिए यह कॉल करें: clearMediaNotification().

हर डॉक्स बिल्ड पर ऐप रनटाइम और उसकी उदाहरण फ़ाइलों से बनाया जाता है। Markdown में पढ़ें · सभी फ़ैमिली.

2026-10-03 को शिप किए गए ब्रिज से मिलाकर जाँचा गया।