ساخت APK رایگان

API پل جاوااسکریپت

رسانه و پخش روی صفحه دیگر

اعلان رسانه‌ای صفحهٔ قفل که صفحهٔ شما کنترل می‌کند، و ارسال ویدیو به تلویزیون.

navigator.mediaSession استاندارد وب#

navigator.mediaSession + MediaMetadata

نمونه

آنچه صفحهٔ شما پخش می‌کند را در یک اعلان رسانه و در صفحهٔ قفل نشان می‌دهد، با دکمه‌های قبلی / پخش-مکث / بعدی / توقف که به صفحهٔ شما برمی‌گردند. از Media Session API معمولی استفاده می‌کند.

مقدار برگشتی: هیچ؛ فشار دکمه‌ها کنترل‌کننده‌هایی را که با setActionHandler تنظیم کرده‌اید صدا می‌زند، با { action } طبق استاندارد. نیازمندی: هنگام ساخت، در مرحله ۴ (دسترسی) گزینهٔ اعلان رسانه را روشن کنید. برای اپلیکیشن‌هایی که با هوش مصنوعی ساخته شده‌اند، اگر کد از آن استفاده کند خودکار روشن می‌شود 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);

یک پخش‌کنندهٔ پادکست که دکمه‌های بعدی / قبلی اعلان به‌جای تغییر قسمت، ۳۰ ثانیه جلو و ۱۵ ثانیه عقب می‌روند:

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 را فراخوانی کنید.
  • فقط تصویر آخرین artwork استفاده می‌شود. باید یک نشانی کامل https:// در اینترنت باشد (اپلیکیشن آن را دانلود می‌کند، با محدودیت ۴ ثانیه)؛ فایلی داخل ZIP اپلیکیشن شما نمایش داده نمی‌شود.
  • <audio> / <video> عنصرها برایتان ردیابی می‌شوند: وقتی صفحه یک دکمه را نه با setActionHandler و نه با window.WebToApkOnMediaAction خودش مدیریت کند، پخش / توقف / ایست عنصر رسانه‌ای را که در حال پخش است کنترل می‌کنند (وگرنه آخرین پخش‌شده، وگرنه اولین).
  • برای حذف اعلان، WebToApk.clearMediaNotification() را فراخوانی کنید (تنظیم metadata = null آن را حذف نمی‌کند).
  • صفحه‌ای که window.WebToApkOnMediaAction = function (action) { ... } را تعریف کند (اکشن‌های play، pause، next، prev، stop) هر فشار دکمه را آنجا هم می‌گیرد - اپلیکیشن دیگر آن را بازنویسی نمی‌کند. یکی از این دو را استفاده کنید، نه هر دو، وگرنه یک فشار دو بار اجرا می‌شود.

requestPictureInPicture استاندارد وب#

video.requestPictureInPicture() — Picture-in-Picture

نمونه

API استاندارد تصویر در تصویر: یک ویدیو را در پنجره کوچک شناور کنید در حالی که کاربر کار دیگری می‌کند. Chrome در اندروید آن را دارد و WebView اندروید ندارد؛ در اپلیکیشن ویدیو پنجره شناور را پر می‌کند، همان‌طور که در Chrome.

خروجی: یک Promise برای یک PictureInPictureWindow (رویدادهای width، height، resize). ویدیو enterpictureinpicture و سپس leavepictureinpicture را می‌گیرد وقتی کاربر پنجره را می‌بندد یا بزرگ می‌کند؛ document.pictureInPictureElement در این فاصله همان ویدیو است. بدون لمس NotAllowedError رد می‌کند، پیش از بارگذاری فراداده ویدیو یا وقتی disablePictureInPicture دارد InvalidStateError رد می‌کند. نیازمندی: تصویر در تصویر در ساخت روشن باشد (وقتی کد شما requestPictureInPicture را فراخوانی کند خودش روشن می‌شود)، اندروید ۸ یا جدیدتر.

<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) وقتی اندروید پنجره را پذیرفت "ok" برمی‌گرداند (با نسبت ابعاد ویدیو)، در غیر این صورت "unsupported" / "failed". باز و بسته شدن پنجره به‌صورت appmint:pip با {inPip} می‌رسد. __pipAvailable() وقتی ساخت تصویر در تصویر را روی اندروید ۸ به بالا دارد درست است. نیازمندی: تصویر در تصویر در ساخت روشن باشد.

از 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) وقتی اندروید پنجره را پذیرفت "ok" برمی‌گرداند (با نسبت ابعاد ویدیو)، در غیر این صورت "unsupported" / "failed". باز و بسته شدن پنجره به‌صورت appmint:pip با {inPip} می‌رسد. __pipAvailable() وقتی ساخت تصویر در تصویر را روی اندروید ۸ به بالا دارد درست است. نیازمندی: تصویر در تصویر در ساخت روشن باشد.

از 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) وقتی اندروید پنجره را پذیرفت "ok" برمی‌گرداند (با نسبت ابعاد ویدیو)، در غیر این صورت "unsupported" / "failed". باز و بسته شدن پنجره به‌صورت appmint:pip با {inPip} می‌رسد. __pipAvailable() وقتی ساخت تصویر در تصویر را روی اندروید ۸ به بالا دارد درست است. نیازمندی: تصویر در تصویر در ساخت روشن باشد.

از 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) وقتی اندروید پنجره را پذیرفت "ok" برمی‌گرداند (با نسبت ابعاد ویدیو)، در غیر این صورت "unsupported" / "failed". باز و بسته شدن پنجره به‌صورت appmint:pip با {inPip} می‌رسد. __pipAvailable() وقتی ساخت تصویر در تصویر را روی اندروید ۸ به بالا دارد درست است. نیازمندی: تصویر در تصویر در ساخت روشن باشد.

از 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 …) یا URL از نوع `data:` صوتی به اپلیکیشن‌های صوتی پیشنهاد می‌شود، هر چیز دیگر - فایل‌های ویدیو، مانیفست‌های پخش زنده، URLهای بدون پسوند - به اپلیکیشن‌های ویدیو. این یک تحویل است، نه نشست پخش: اپلیکیشن کنترل پخش را نگه نمی‌دارد و فراخوانی برگشتی وجود ندارد. صفحه‌ای که نشست واقعی می‌خواهد باید به‌جای آن از Cast SDK در جاوااسکریپت استفاده کند.

نمونه

یک نشانی ویدیو یا صوت را به اپلیکیشن‌های روی گوشی که می‌توانند آن را روی صفحه دیگر پخش کنند (اپلیکیشن Chromecast، اپلیکیشن Smart TV، پخش‌کننده ویدیو) از طریق انتخابگر «Cast to...» اندروید پیشنهاد می‌کند.

مقدار برگشتی: هیچ، و بدون callback. نیازمندی: چیزی برای روشن کردن نیست. گوشی به اپلیکیشنی نیاز دارد که پیوند را بپذیرد.

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

یک قسمت پادکست را از یک فهرست به یک بلندگو یا اپلیکیشن تلویزیون بفرستید (پیوند .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
});

نکته‌ها:

  • این یک واگذاری است، نه یک نشست cast: پس از انتخاب اپلیکیشن توسط کاربر، صفحهٔ شما پخش را کنترل نمی‌کند.
  • نشانی باید یک پیوند کامل https:// باشد که اپلیکیشن‌های دیگر بتوانند باز کنند. فایلی داخل ZIP اپلیکیشن شما، یک URL از نوع blob: یا نشانی صفحه وب پخش نمی‌شود.
  • نوع از روی پیوند می‌آید: فایل صوتی (.mp3، .m4a، .aac، .ogg، .opus، .wav، .flac …) یا URL از نوع audio/ data: به اپلیکیشن‌های صوتی پیشنهاد می‌شود؛ هر چیز دیگر - فایل‌های ویدیو، پخش‌های .m3u8، پیوندهای بدون پسوند فایل - به اپلیکیشن‌های ویدیو.

clearMediaNotification پل#

window.WebToApk.clearMediaNotification()

به محتوای وب اجازه می‌دهد اعلان سبک رسانهٔ Android را کنترل کند و callbackهای کنترل پخش (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() Callback از Android ← JS: window.WebToApkOnMediaAction('play'|'pause'|'next'|'prev'|'stop')

با گروه خودش، پل اعلان رسانه، توضیح داده شده است، نه به‌تنهایی.

نمونه

اعلان رسانه و کنترل‌های صفحه قفل آن را حذف می‌کند، مثلاً وقتی کاربر پخش‌کننده شما را می‌بندد.

خروجی: هیچ. نیازمندی: هنگام ساخت، اعلان رسانه را در مرحله ۴ (دسترسی) روشن کنید. بدون آن، فراخوانی کاری نمی‌کند.

فراخوانی استاندارد وب برای این وجود ندارد، پس از پل استفاده کنید:

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

وقتی آخرین آهنگ یک فهرست پخش تمام شد اعلان را حذف کنید، به‌جای اینکه دکمه پخش بی‌فایده بماند:

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, ...) را بفرستید تا دکمهٔ پخش بماند). تنظیم navigator.mediaSession.metadata = null اعلان را حذف نمی‌کند. setMediaMetadata / setPlaybackState بعدی (یا فراداده / وضعیت جدید navigator.mediaSession) اعلان و کنترل‌های صفحهٔ قفل را برمی‌گرداند.

setMediaMetadata پل#

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

به محتوای وب اجازه می‌دهد اعلان سبک رسانهٔ Android را کنترل کند و callbackهای کنترل پخش (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() Callback از Android ← JS: window.WebToApkOnMediaAction('play'|'pause'|'next'|'prev'|'stop')

با گروه خودش، پل اعلان رسانه، توضیح داده شده است، نه به‌تنهایی.

نمونه

عنوان، هنرمند و تصویر جلد اعلان رسانه و پخش‌کنندهٔ صفحهٔ قفل را تنظیم می‌کند. صفحه‌ها معمولاً navigator.mediaSession.metadata را تنظیم می‌کنند که این را برای شما صدا می‌زند.

خروجی: هیچ. نیازمندی: هنگام ساخت، اعلان رسانه را در مرحله ۴ (دسترسی) روشن کنید. بدون آن، فراخوانی کاری نمی‌کند.

روش عمومی (از این استفاده کنید):

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:// در اینترنت باشد. اپلیکیشن آن را دانلود می‌کند (محدودیت ۴ ثانیه)؛ اگر شکست خورد، آیکون اپلیکیشن نشان داده می‌شود.
  • فشار دکمه‌ها را با 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 را کنترل کند و callbackهای کنترل پخش (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() Callback از Android ← JS: window.WebToApkOnMediaAction('play'|'pause'|'next'|'prev'|'stop')

با گروه خودش، پل اعلان رسانه، توضیح داده شده است، نه به‌تنهایی.

نمونه

به اعلان رسانه می‌گوید در حال پخش هستید یا متوقف، و کجای آهنگ هستید. صفحه‌ها معمولاً navigator.mediaSession.playbackState را تنظیم و setPositionState را فراخوانی می‌کنند که این را برایتان فراخوانی می‌کنند.

خروجی: هیچ. نیازمندی: هنگام ساخت، اعلان رسانه را در مرحله ۴ (دسترسی) روشن کنید. بدون آن، فراخوانی کاری نمی‌کند.

روش عمومی (از این استفاده کنید):

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

پس از جستجوی کاربر موقعیت جدید را گزارش کنید و وقتی آهنگ تمام شد اعلان را به «پخش» برگردانید:

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 دکمهٔ مکث را نشان می‌دهد و مانع کنار زدن اعلان می‌شود؛ false دکمهٔ پخش را نشان می‌دهد. ابتدا setMediaMetadata را صدا بزنید تا اعلان عنوان داشته باشد. برای حذف اعلان هنگام توقف کاربر، صدا بزنید clearMediaNotification().