تخصيص إشعارات الوسائط والتعامل مع قوائم التشغيل

François Beaufort
François Beaufort

باستخدام واجهة برمجة التطبيقات Media Session API الجديدة، يمكنك الآن تخصيص إشعارات الوسائط من خلال توفير بيانات وصفية للوسائط التي يشغّلها تطبيق الويب لديك. ويتيح لك أيضًا التعامل مع الأحداث المرتبطة بالوسائط، مثل تقديم أو تتبُّع التغييرات التي قد تنتج من الإشعارات أو مفاتيح الوسائط. هل أنت متحمس؟ ويمكنك تجربة نماذج جلسات الوسائط الرسمية.

يتم دعم واجهة برمجة تطبيقات جلسات الوسائط في Chrome 57 (إصدار تجريبي في شباط (فبراير) 2017، ثابت في مارس 2017).

النص المختصر (TL;DR) لجلسة الوسائط
صورة من إعداد "مايكل ألو نيلسن" / CC BY 2.0

إعطائي ما أريد

هل سبق أن اطّلعت على واجهة برمجة تطبيقات Media Session API ولكنّك تعودت ببساطة إلى نسخ ولصق بعض الرموز النموذجية بدون أي خجل؟ ها هو ذا.

if ('mediaSession' in navigator) {

    navigator.mediaSession.metadata = new MediaMetadata({
    title: 'Never Gonna Give You Up',
    artist: 'Rick Astley',
    album: 'Whenever You Need Somebody',
    artwork: [
        { src: 'https://dummyimage.com/96x96',   sizes: '96x96',   type: 'image/png' },
        { src: 'https://dummyimage.com/128x128', sizes: '128x128', type: 'image/png' },
        { src: 'https://dummyimage.com/192x192', sizes: '192x192', type: 'image/png' },
        { src: 'https://dummyimage.com/256x256', sizes: '256x256', type: 'image/png' },
        { src: 'https://dummyimage.com/384x384', sizes: '384x384', type: 'image/png' },
        { src: 'https://dummyimage.com/512x512', sizes: '512x512', type: 'image/png' },
    ]
    });

    navigator.mediaSession.setActionHandler('play', function() {});
    navigator.mediaSession.setActionHandler('pause', function() {});
    navigator.mediaSession.setActionHandler('seekbackward', function() {});
    navigator.mediaSession.setActionHandler('seekforward', function() {});
    navigator.mediaSession.setActionHandler('previoustrack', function() {});
    navigator.mediaSession.setActionHandler('nexttrack', function() {});
}

المشاركة في الرمز

لنبدأ اللعب 🎷

يمكنك إضافة عنصر <audio> بسيط إلى صفحة الويب وتحديد عدة مصادر للوسائط، لكي يتمكّن المتصفّح من اختيار مصدر الوسائط الأفضل أداءً.

<audio controls>
    <source src="audio.mp3" type="audio/mp3"/>
    <source src="audio.ogg" type="audio/ogg"/>
</audio>

كما تعلم، تم إيقاف autoplay للعناصر الصوتية على Chrome لـ Android مما يعني أنه ينبغي علينا استخدام طريقة play() للعنصر الصوتي. يجب تفعيل هذه الطريقة من خلال إيماءة المستخدم، مثل اللمس أو النقر بالماوس. وهذا يعني الاستماع إلى فعاليات pointerup وclick وtouchend. بمعنى آخر، يجب على المستخدم النقر فوق زر قبل أن يتمكن تطبيق الويب الخاص بك من إحداث ضجيج بالفعل.

playButton.addEventListener('pointerup', function(event) {
    let audio = document.querySelector('audio');

    // User interacted with the page. Let's play audio...
    audio.play()
    .then(_ => { /* Set up media session... */ })
    .catch(error => { console.log(error) });
});

إذا كنت لا تريد تشغيل الصوت بعد التفاعل الأول مباشرةً، أنصحك باستخدام طريقة load() للعنصر الصوتي. هذه إحدى الطرق للمتصفح لتتبع ما إذا كان المستخدم قد تفاعل مع العنصر أم لا. لاحظ أنه قد يساعد أيضًا في تسهيل التشغيل لأن المحتوى سيتم تحميله بالفعل.

let audio = document.querySelector('audio');

welcomeButton.addEventListener('pointerup', function(event) {
  // User interacted with the page. Let's load audio...
  <strong>audio.load()</strong>
  .then(_ => { /* Show play button for instance... */ })
  .catch(error => { console.log(error) });
});

// Later...
playButton.addEventListener('pointerup', function(event) {
  <strong>audio.play()</strong>
  .then(_ => { /* Set up media session... */ })
  .catch(error => { console.log(error) });
});

تخصيص الإشعار

عندما يشغّل تطبيق الويب الصوت، يمكنك رؤية إشعار وسائط في قائمة الإشعارات. على نظام التشغيل Android، يبذل Chrome قصارى جهده لعرض المعلومات المناسبة باستخدام عنوان المستند وأكبر صورة رمز يمكن العثور عليها.

بدون جلسة وسائط
بدون جلسة وسائط
بعد جلسة وسائط
مع جلسة وسائط

ضبط البيانات الوصفية

لنتعرّف على كيفية تخصيص إشعار الوسائط هذا من خلال إعداد بعض البيانات الوصفية لجلسة الوسائط، مثل العنوان والفنان واسم الألبوم والعمل الفني، باستخدام واجهة برمجة التطبيقات لجلسات الوسائط.

// When audio starts playing...
if ('mediaSession' in navigator) {

    navigator.mediaSession.metadata = new MediaMetadata({
    title: 'Never Gonna Give You Up',
    artist: 'Rick Astley',
    album: 'Whenever You Need Somebody',
    artwork: [
        { src: 'https://dummyimage.com/96x96',   sizes: '96x96',   type: 'image/png' },
        { src: 'https://dummyimage.com/128x128', sizes: '128x128', type: 'image/png' },
        { src: 'https://dummyimage.com/192x192', sizes: '192x192', type: 'image/png' },
        { src: 'https://dummyimage.com/256x256', sizes: '256x256', type: 'image/png' },
        { src: 'https://dummyimage.com/384x384', sizes: '384x384', type: 'image/png' },
        { src: 'https://dummyimage.com/512x512', sizes: '512x512', type: 'image/png' },
    ]
    });
}

بعد انتهاء التشغيل، لن تضطر إلى "إطلاق" جلسة تشغيل الوسائط لأنّ الإشعار سيختفي تلقائيًا. يُرجى العِلم أنّه سيتم استخدام navigator.mediaSession.metadata الحالي عند بدء أي عملية تشغيل. لهذا السبب، يجب تعديلها للتأكّد من أنّك تعرض دائمًا المعلومات ذات الصلة في إشعار الوسائط.

المقطع الصوتي السابق / التالي

إذا كان تطبيق الويب يوفّر قائمة تشغيل، قد تحتاج إلى السماح للمستخدم بالتنقل عبر قائمة التشغيل مباشرةً من إشعار الوسائط مع بعض رمزي "المقطع الصوتي السابق" و "المقطع الصوتي التالي".

let audio = document.createElement('audio');

let playlist = ['audio1.mp3', 'audio2.mp3', 'audio3.mp3'];
let index = 0;

navigator.mediaSession.setActionHandler('previoustrack', function() {
    // User clicked "Previous Track" media notification icon.
    index = (index - 1 + playlist.length) % playlist.length;
    playAudio();
});

navigator.mediaSession.setActionHandler('nexttrack', function() {
    // User clicked "Next Track" media notification icon.
    index = (index + 1) % playlist.length;
    playAudio();
});

function playAudio() {
    audio.src = playlist[index];
    audio.play()
    .then(_ => { /* Set up media session... */ })
    .catch(error => { console.log(error); });
}

playButton.addEventListener('pointerup', function(event) {
    playAudio();
});

يُرجى العلم أنّ معالِجات إجراءات الوسائط ستستمر في العمل. يشبه هذا إلى حدّ كبير نمط الاستماع إلى الحدث، باستثناء أنّ التعامل مع حدث ما يعني أنّ المتصفّح يتوقف عن تنفيذ أي سلوك تلقائي ويستخدم ذلك كإشارة إلى أنّ تطبيق الويب يتيح تنفيذ هذا الإجراء على الوسائط. بالتالي، لن يتم عرض عناصر التحكم في الإجراءات إلا في حال ضبط معالج الإجراءات المناسب.

يُرجى العِلم أنّ إلغاء ضبط معالِج إجراءات على الوسائط عملية سهلة مثل تعيينه لـ "null".

ترجيع / تقديم إلى الأمام

تسمح لك واجهة برمجة تطبيقات جلسات الوسائط بعرض رمزي إشعارات الوسائط "الرجوع إلى الخلف" و "الترجيع إلى الأمام" إذا كنت تريد التحكّم في مقدار الوقت الذي يتم تخطّيه.

let skipTime = 10; // Time to skip in seconds

navigator.mediaSession.setActionHandler('seekbackward', function() {
    // User clicked "Seek Backward" media notification icon.
    audio.currentTime = Math.max(audio.currentTime - skipTime, 0);
});

navigator.mediaSession.setActionHandler('seekforward', function() {
    // User clicked "Seek Forward" media notification icon.
    audio.currentTime = Math.min(audio.currentTime + skipTime, audio.duration);
});

التشغيل / الإيقاف المؤقت

يظهر الرمز "تشغيل/إيقاف مؤقت" دائمًا في إشعار الوسائط، ويعالج المتصفّح الأحداث ذات الصلة تلقائيًا. إذا لم يعمل السلوك الافتراضي لسبب ما، فيمكنك معالجة أحداث الوسائط "تشغيل" و"إيقاف مؤقت".

navigator.mediaSession.setActionHandler('play', function() {
    // User clicked "Play" media notification icon.
    // Do something more than just playing current audio...
});

navigator.mediaSession.setActionHandler('pause', function() {
    // User clicked "Pause" media notification icon.
    // Do something more than just pausing current audio...
});

الإشعارات في كل مكان

الأمر الرائع في واجهة برمجة التطبيقات لجلسات الوسائط هو أن شريط الإشعارات ليس المكان الوحيد الذي تظهر فيه البيانات الوصفية وعناصر التحكم للوسائط. تتم مزامنة إشعار الوسائط تلقائيًا مع أي جهاز قابل للارتداء مقترِن. ويظهر أيضًا على شاشات القفل.

شاشة القفل
شاشة القفل - صورة من إعداد مايكل ألو نيلسن / CC BY 2.0
إشعارات Wear
إشعار ارتداء الجهاز

استمتِع باللعب بلا إنترنت

أعلم ما تفكر فيه الآن. عامل الخدمة لإنقاذ الموقف.

صحيح، ولكن أولاً وقبل كل شيء، يجب التأكّد من وضع علامة في المربّع بجانب جميع العناصر المدرَجة في قائمة التحقّق هذه:

  • يتم عرض جميع ملفات الوسائط والأعمال الفنية باستخدام عنوان HTTP Cache-Control المناسب. وسيسمح ذلك للمتصفح بتخزين مؤقت وإعادة استخدام الموارد التي تم جلبها سابقًا. يُرجى الاطّلاع على قائمة التحقُّق الخاصة بالتخزين المؤقت.
  • تأكَّد من عرض جميع ملفات الوسائط والأعمال الفنية باستخدام عنوان HTTP Allow-Control-Allow-Origin: *. وسيسمح هذا لتطبيقات الويب التابعة لجهات خارجية بجلب استجابات HTTP واستخدامها من خادم الويب.

استراتيجية التخزين المؤقت للعاملين في الخدمات

في ما يتعلق بملفات الوسائط، أنصحك باتّباع استراتيجية بسيطة من أجل "ذاكرة التخزين المؤقت، الرجوع إلى استخدام الشبكة"، كما هو موضّح من خلال "جيك أرشيبالد".

بالنسبة للأعمال الفنية، سأكون أكثر تحديدًا وأختار النهج أدناه:

  • سبق أن تم إدراج عمل فني واحد (If) في ذاكرة التخزين المؤقت، ويمكنك عرضه من ذاكرة التخزين المؤقت.
  • Else جلب الأعمال الفنية من الشبكة
    • تم جلب If بنجاح، ويمكنك إضافة عمل فني للشبكة إلى ذاكرة التخزين المؤقت وعرضه.
    • Else يعرض العمل الفني الاحتياطي من ذاكرة التخزين المؤقت

بهذه الطريقة، ستحتوي إشعارات الوسائط دائمًا على رمز عمل فني رائع حتى عندما يتعذّر على المتصفّح استرجاعها. في ما يلي كيفية تنفيذ ذلك:

const FALLBACK_ARTWORK_URL = 'fallbackArtwork.png';

addEventListener('install', event => {
    self.skipWaiting();
    event.waitUntil(initArtworkCache());
});

function initArtworkCache() {
    caches.open('artwork-cache-v1')
    .then(cache => cache.add(FALLBACK_ARTWORK_URL));
}

addEventListener('fetch', event => {
    if (/artwork-[0-9]+\.png$/.test(event.request.url)) {
    event.respondWith(handleFetchArtwork(event.request));
    }
});

function handleFetchArtwork(request) {
    // Return cache request if it's in the cache already, otherwise fetch
    // network artwork.
    return getCacheArtwork(request)
    .then(cacheResponse => cacheResponse || getNetworkArtwork(request));
}

function getCacheArtwork(request) {
    return caches.open('artwork-cache-v1')
    .then(cache => cache.match(request));
}

function getNetworkArtwork(request) {
    // Fetch network artwork.
    return fetch(request)
    .then(networkResponse => {
    if (networkResponse.status !== 200) {
        return Promise.reject('Network artwork response is not valid');
    }
    // Add artwork to the cache for later use and return network response.
    addArtworkToCache(request, networkResponse.clone())
    return networkResponse;
    })
    .catch(error => {
    // Return cached fallback artwork.
    return getCacheArtwork(new Request(FALLBACK_ARTWORK_URL))
    });
}

function addArtworkToCache(request, response) {
    return caches.open('artwork-cache-v1')
    .then(cache => cache.put(request, response));
}

السماح للمستخدم بالتحكم في ذاكرة التخزين المؤقت

عندما يستخدم المستخدم المحتوى من تطبيق الويب، قد تشغل ملفات الوسائط والأعمال الفنية مساحة كبيرة على جهازه. وتقع على عاتقك مسؤولية عرض مقدار ذاكرة التخزين المؤقت المستخدمة ومنح المستخدمين إمكانية محوها. ولحسن حظنا، يمكنك إجراء ذلك بسهولة تامة باستخدام Cache API.

// Here's how I'd compute how much cache is used by artwork files...
caches.open('artwork-cache-v1')
.then(cache => cache.matchAll())
.then(responses => {
    let cacheSize = 0;
    let blobQueue = Promise.resolve();

    responses.forEach(response => {
    let responseSize = response.headers.get('content-length');
    if (responseSize) {
        // Use content-length HTTP header when possible.
        cacheSize += Number(responseSize);
    } else {
        // Otherwise, use the uncompressed blob size.
        blobQueue = blobQueue.then(_ => response.blob())
            .then(blob => { cacheSize += blob.size; blob.close(); });
    }
    });

    return blobQueue.then(_ => {
    console.log('Artwork cache is about ' + cacheSize + ' Bytes.');
    });
})
.catch(error => { console.log(error); });

// And here's how to delete some artwork files...
const artworkFilesToDelete = ['artwork1.png', 'artwork2.png', 'artwork3.png'];

caches.open('artwork-cache-v1')
.then(cache => Promise.all(artworkFilesToDelete.map(artwork => cache.delete(artwork))))
.catch(error => { console.log(error); });

ملاحظات التنفيذ

  • يطلب Chrome لنظام Android تركيز الصوت "الكامل" لعرض إشعارات الوسائط فقط عندما تكون مدة ملف الوسائط 5 ثوانٍ على الأقل.
  • يتوافق العمل الفني للإشعار مع عناوين URL للأخطاء العشوائية وعناوين URL للبيانات.
  • إذا لم يتم تحديد أي عمل فني وكانت هناك صورة رمز بالحجم المطلوب، ستستخدمه إشعارات الوسائط.
  • حجم العمل الفني للإشعارات في Chrome لنظام Android هو 512x512. بالنسبة إلى الأجهزة المنخفضة المستوى، تكون القيمة هي 256x256.
  • يمكنك إغلاق إشعارات الوسائط باستخدام "audio.src = ''".
  • بما أنّ Web Audio API لا تطلب ميزة "التركيز على الصوت من Android" لأسباب سابقة، فإنّ الطريقة الوحيدة لتمكينها من العمل مع واجهة برمجة التطبيقات Media Session API هي ربط العنصر <audio> كمصدر إدخال بواجهة Web Audio API. ونأمل أن تحسِّن Web AudioFocus API الوضع في المستقبل القريب.
  • لن تؤثر مكالمات "جلسة الوسائط" في إشعارات الوسائط إلا إذا كانت واردة من الإطار نفسه لمورد الوسائط. اطّلِع على المقتطف أدناه.
<iframe id="iframe">
  <audio>...</audio>
</iframe>
<script>
  iframe.contentWindow.navigator.mediaSession.metadata = new MediaMetadata({
    title: 'Never Gonna Give You Up',
    ...
  });
</script>

الدعم

في وقت كتابة هذا التقرير، كان Chrome لنظام Android هو النظام الأساسي الوحيد الذي يدعم واجهة برمجة تطبيقات جلسات الوسائط. ويمكن العثور على مزيد من المعلومات حول حالة تنفيذ المتصفّح على حالة نظام Chrome الأساسي.

العيّنات والعروض التوضيحية

يمكنك الاطّلاع على عيّنات جلسات الوسائط الرسمية في Chrome والتي تعرض Blender Foundation وأعمال "جان مورغنسترن".

المراجِع

مواصفات جلسة الوسائط: wicg.github.io/mediasession

المشاكل في المواصفات: github.com/WICG/mediasession/issues

أخطاء Chrome : crbug.com