واجهة JavaScript bridge API
الوسائط والبث
إشعار الوسائط على شاشة القفل الذي تتحكم به صفحتك، وتمرير فيديو إلى التلفاز.
requestPictureInPicture معيار الويب#
video.requestPictureInPicture() — Picture-in-Picture
مثال
واجهة الصورة داخل الصورة القياسية: اجعل فيديو يطفو في نافذة صغيرة بينما يفعل المستخدم شيئًا آخر. Chrome على Android يدعمها وWebView في Android لا؛ وفي التطبيق يملأ الفيديو النافذة العائمة كما في Chrome.
يعيد: Promise لـ PictureInPictureWindow (أحداث width وheight وresize). يتلقى الفيديو enterpictureinpicture، ثم leavepictureinpicture عندما يغلق المستخدم النافذة أو يوسّعها؛ وdocument.pictureInPictureElement هو الفيديو في هذه الأثناء. يُرفض بـ NotAllowedError دون نقرة، وInvalidStateError قبل تحميل بيانات الفيديو الوصفية أو عندما يكون disablePictureInPicture. يتطلب: تفعيل الصورة داخل الصورة عند الإنشاء (تُفعَّل تلقائيًا عندما تستدعي شيفرتك 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 الجسر (bridge)#
window.WebToApk.__pipAvailable(): Boolean
هل يمكن أن يعمل video.requestPictureInPicture(): وضع PiP مفعّل في النسخة المبنية، وAndroid 8 أو أحدث.
مثال
موثَّق مع WebToApk.__pipEnter - والمثال هناك يعرض هذا أيضًا.
نقل داخلي خلف video.requestPictureInPicture() وdocument.exitPictureInPicture(). تستخدم الصفحات الواجهة القياسية؛ ولا تستدعِ هذه مباشرة، فطرق __ قد تتغير دون إشعار.
يعيد: يعيد __pipEnter(width, height) القيمة "ok" عندما قبل Android النافذة (بنسبة أبعاد الفيديو)، وإلا "unsupported" / "failed". يصل فتح النافذة وإغلاقها كـ appmint:pip مع {inPip}. و__pipAvailable() صحيح عندما يكون الإنشاء مفعّلًا فيه الصورة داخل الصورة على Android 8+. يتطلب: تفعيل الصورة داخل الصورة عند الإنشاء.
استخدم الواجهة العامة:
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 الجسر (bridge)#
window.WebToApk.__pipEnter(width: Int, height: Int): String
يفتح نافذة PiP بنسبة أبعاد فيديو [width]×[height]: "ok"، أو "unsupported" / "failed". يصل تغيير الوضع على شكل `appmint:pip`.
مثال
نقل داخلي خلف video.requestPictureInPicture() وdocument.exitPictureInPicture(). تستخدم الصفحات الواجهة القياسية؛ ولا تستدعِ هذه مباشرة، فطرق __ قد تتغير دون إشعار.
يعيد: يعيد __pipEnter(width, height) القيمة "ok" عندما قبل Android النافذة (بنسبة أبعاد الفيديو)، وإلا "unsupported" / "failed". يصل فتح النافذة وإغلاقها كـ appmint:pip مع {inPip}. و__pipAvailable() صحيح عندما يكون الإنشاء مفعّلًا فيه الصورة داخل الصورة على Android 8+. يتطلب: تفعيل الصورة داخل الصورة عند الإنشاء.
استخدم الواجهة العامة:
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 الجسر (bridge)#
window.WebToApk.__pipExit()
document.exitPictureInPicture(): يعيد التطبيق إلى ملء الشاشة.
مثال
موثَّق مع WebToApk.__pipEnter - والمثال هناك يعرض هذا أيضًا.
نقل داخلي خلف video.requestPictureInPicture() وdocument.exitPictureInPicture(). تستخدم الصفحات الواجهة القياسية؛ ولا تستدعِ هذه مباشرة، فطرق __ قد تتغير دون إشعار.
يعيد: يعيد __pipEnter(width, height) القيمة "ok" عندما قبل Android النافذة (بنسبة أبعاد الفيديو)، وإلا "unsupported" / "failed". يصل فتح النافذة وإغلاقها كـ appmint:pip مع {inPip}. و__pipAvailable() صحيح عندما يكون الإنشاء مفعّلًا فيه الصورة داخل الصورة على Android 8+. يتطلب: تفعيل الصورة داخل الصورة عند الإنشاء.
استخدم الواجهة العامة:
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 الجسر (bridge)#
window.WebToApk.__pipPrepare()
الصفحة على وشك وضع فيديو بملء الشاشة لأجل PiP: لا تفرض الوضع الأفقي لذلك.
مثال
موثَّق مع WebToApk.__pipEnter - والمثال هناك يعرض هذا أيضًا.
نقل داخلي خلف video.requestPictureInPicture() وdocument.exitPictureInPicture(). تستخدم الصفحات الواجهة القياسية؛ ولا تستدعِ هذه مباشرة، فطرق __ قد تتغير دون إشعار.
يعيد: يعيد __pipEnter(width, height) القيمة "ok" عندما قبل Android النافذة (بنسبة أبعاد الفيديو)، وإلا "unsupported" / "failed". يصل فتح النافذة وإغلاقها كـ appmint:pip مع {inPip}. و__pipAvailable() صحيح عندما يكون الإنشاء مفعّلًا فيه الصورة داخل الصورة على Android 8+. يتطلب: تفعيل الصورة داخل الصورة عند الإنشاء.
استخدم الواجهة العامة:
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 الجسر (bridge)#
window.WebToApk.castMedia(mediaUrl: String)
يعرض [mediaUrl] على أي شيء في هذا الهاتف يستطيع تشغيله على شاشة أخرى - Chromecast، تطبيق تلفاز ذكي، مشغّل محلي - عبر قائمة اختيار النظام. يُؤخذ النوع من العنوان ([CastMediaType]): ملف صوتي (.mp3، .m4a، .aac، .ogg، .opus، .wav، .flac …) أو عنوان `data:` صوتي يُعرض على تطبيقات الصوت، وأي شيء آخر - ملفات الفيديو، قوائم البث، العناوين بلا امتداد - على تطبيقات الفيديو. هذا تسليم وليس جلسة بث: لا يبقى التطبيق متحكمًا بالتشغيل، ولا يوجد رد نداء. الصفحة التي تحتاج جلسة حقيقية يجب أن تستخدم Cast SDK في JavaScript بدلًا من ذلك.
مثال
يعرض عنوان فيديو أو صوت على التطبيقات في الهاتف القادرة على تشغيله على شاشة أخرى (تطبيق Chromecast، تطبيق تلفاز ذكي، مشغّل فيديو)، عبر قائمة اختيار "بث إلى..." في Android.
القيمة المُعادة: لا شيء، ولا دالة رد. المتطلبات: لا شيء لتفعيله. يحتاج الهاتف إلى تطبيق يقبل الرابط.
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
});ملاحظات:
- هذا تمرير وليس جلسة بث: صفحتك لا تتحكم في التشغيل بعد أن يختار المستخدم تطبيقًا.
- يجب أن يكون العنوان رابط
https://كاملًا تستطيع تطبيقات أخرى فتحه. ملف داخل ZIP تطبيقك أو عنوانblob:أو عنوان صفحة ويب لن يعمل. - يأتي النوع من الرابط: ملف صوتي (
.mp3،.m4a،.aac،.ogg،.opus،.wav،.flac…) أو عنوانaudio/data:يُعرض على تطبيقات الصوت؛ وأي شيء آخر - ملفات الفيديو، بث.m3u8، الروابط بلا امتداد ملف - على تطبيقات الفيديو.
clearMediaNotification الجسر (bridge)#
window.WebToApk.clearMediaNotification()
يتيح لمحتوى الويب التحكم في إشعار Android بنمط الوسائط وتلقي استدعاءات أزرار التحكم (تشغيل/إيقاف مؤقت/التالي/السابق) من شاشة القفل ولوحة الإشعارات. الاستخدام من JavaScript: window.WebToApk.setMediaMetadata('Song Title', 'Artist Name', 'https://…/art.jpg') window.WebToApk.setPlaybackState(true, 30000, 240000) // playing, pos ms, dur ms window.WebToApk.clearMediaNotification() الاستدعاء من Android → JS: window.WebToApkOnMediaAction('play'|'pause'|'next'|'prev'|'stop')
موصوف ضمن مجموعته، جسر إشعارات الوسائط، وليس وحده.
مثال
يزيل إشعار الوسائط وعناصر تحكم شاشة القفل، مثلًا عندما يغلق المستخدم مشغّلك.
يعيد: لا شيء. يتطلب: فعّل إشعار الوسائط في الخطوة 4 (الوصول) عند الإنشاء. بدونه لا يفعل الاستدعاء شيئًا.
لا يوجد استدعاء ويب قياسي لهذا، فاستخدم الجسر:
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 الجسر (bridge)#
window.WebToApk.setMediaMetadata(title: String, artist: String, artworkUrl: String)
يتيح لمحتوى الويب التحكم في إشعار Android بنمط الوسائط وتلقي استدعاءات أزرار التحكم (تشغيل/إيقاف مؤقت/التالي/السابق) من شاشة القفل ولوحة الإشعارات. الاستخدام من JavaScript: window.WebToApk.setMediaMetadata('Song Title', 'Artist Name', 'https://…/art.jpg') window.WebToApk.setPlaybackState(true, 30000, 240000) // playing, pos ms, dur ms window.WebToApk.clearMediaNotification() الاستدعاء من Android → JS: window.WebToApkOnMediaAction('play'|'pause'|'next'|'prev'|'stop')
موصوف ضمن مجموعته، جسر إشعارات الوسائط، وليس وحده.
مثال
يضبط العنوان والفنان وصورة الغلاف لإشعار الوسائط ومشغّل شاشة القفل. تضبط الصفحات عادةً navigator.mediaSession.metadata، الذي يستدعي هذا نيابةً عنك.
يعيد: لا شيء. يتطلب: فعّل إشعار الوسائط في الخطوة 4 (الوصول) عند الإنشاء. بدونه لا يفعل الاستدعاء شيئًا.
الطريقة العامة (استخدم هذه):
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 الجسر (bridge)#
window.WebToApk.setPlaybackState(isPlaying: Boolean, positionMs: Long, durationMs: Long)
يتيح لمحتوى الويب التحكم في إشعار Android بنمط الوسائط وتلقي استدعاءات أزرار التحكم (تشغيل/إيقاف مؤقت/التالي/السابق) من شاشة القفل ولوحة الإشعارات. الاستخدام من JavaScript: window.WebToApk.setMediaMetadata('Song Title', 'Artist Name', 'https://…/art.jpg') window.WebToApk.setPlaybackState(true, 30000, 240000) // playing, pos ms, dur ms window.WebToApk.clearMediaNotification() الاستدعاء من Android → JS: window.WebToApkOnMediaAction('play'|'pause'|'next'|'prev'|'stop')
موصوف ضمن مجموعته، جسر إشعارات الوسائط، وليس وحده.
مثال
يخبر إشعار الوسائط ما إذا كنت تشغّل أو متوقفًا مؤقتًا، وأين أنت في المقطع. تضبط الصفحات عادة navigator.mediaSession.playbackState وتستدعي setPositionState، وهما يستدعيان هذا نيابةً عنك.
يعيد: لا شيء. يتطلب: فعّل إشعار الوسائط في الخطوة 4 (الوصول) عند الإنشاء. بدونه لا يفعل الاستدعاء شيئًا.
الطريقة العامة (استخدم هذه):
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().