התאמה אישית של התראות מדיה וטיפול בפלייליסטים

François Beaufort
François Beaufort

באמצעות Media Session API, מעכשיו אפשר להתאים אישית התראות על מדיה על ידי הוספת מטא-נתונים למדיה שאפליקציית האינטרנט שלך מפעילה. הוא גם מאפשר לטפל באירועים שקשורים למדיה, כמו חיפוש או מעקב אחר שינויים שעשויים להגיע מהתראות או ממפתחות מדיה. מגניב, נכון? נסה את הדוגמאות הרשמיות של הפעלות מדיה.

ה-Media Session API נתמך ב-Chrome 57 (בטא בפברואר 2017, יציב במרץ 2017).

אמ;לק: סשן מדיה
תמונה מאת מייקל אלו-נילסן / CC BY 2.0

אני רוצה לראות את מה שאני רוצה

אתם כבר מכירים את ה-MediaSession 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 עושה כמיטב יכולתו כדי להציג מידע מתאים באמצעות כותרת המסמך ותמונת הסמל הגדולה ביותר שהוא יכול למצוא.

ללא סשן מדיה
ללא סשן מדיה
עם סשן מדיה
עם סשן מדיה

הגדרת מטא-נתונים

בואו נראה איך להתאים אישית את התראת המדיה על ידי הגדרת מטא-נתונים של הפעלת מדיה, כמו הכותרת, האומן, שם האלבום והגרפיקה באמצעות ה-Media Session API.

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

לתשומת ליבכם: רכיבי ה-handler של הפעולות במדיה יישארו בתוקף. הדבר דומה מאוד לדפוס של מאזין האירועים, למעט העובדה שטיפול באירוע גורם לדפדפן להפסיק לפעול כברירת מחדל ולהשתמש בכך כאות לכך שאפליקציית האינטרנט שלכם תומכת בפעולה של מדיה. לכן, פקדי הפעולות במדיה לא יוצגו אלא אם תגדירו את ה-handler המתאים לפעולות.

דרך אגב, ביטול ההגדרה של ה-handler של הפעולות במדיה פשוט מאוד, ממש כמו הקצאתו ל-null.

הרצה אחורה / הרצה קדימה

ממשק ה-API של הסשן של המדיה מאפשר להציג את סמלי ההתראות של המדיה 'דילוג אחורה' ו'הרצה קדימה', כדי לקבוע כמה זמן מדלגים.

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

הודעות בכל מקום

היתרון המגניב ב-MediaSession API הוא שמגש ההתראות הוא לא המקום היחיד שבו מוצגים המטא-נתונים ואמצעי הבקרה של המדיה. ההתראה במדיה מסונכרנת באופן אוטומטי עם כל מכשיר לביש מותאם. והוא מופיע גם במסכי הנעילה.

מסך הנעילה
מסך נעילה - תמונה מאת מייקל אלו-נילסן / CC BY 2.0
התראה מ-Wear
התראות Wear

כיף לשחק גם אופליין

אני יודע מה אתה חושב עכשיו. Service worker תצלו!

נכון, אבל בראש ובראשונה חשוב לוודא שכל הפריטים ברשימת המשימות הזו מסומנים:

  • כל קובצי המדיה והגרפיקה מוצגים עם כותרת ה-HTTP המתאימה Cache-Control. כך הדפדפן יוכל לשמור במטמון ולהשתמש במשאבים שאוחזרו בעבר. לעיונכם ברשימת המשימות לשמירה במטמון.
  • ודאו שכל קובצי המדיה והגרפיקה מוצגים באמצעות כותרת ה-HTTP Allow-Control-Allow-Origin: *. זה יאפשר לאפליקציות אינטרנט של צד שלישי לאחזר ולצרוך תגובות HTTP משרת האינטרנט שלכם.

אסטרטגיית השמירה במטמון של Service Worker

לגבי קובצי מדיה, אני ממליץ על אסטרטגיית "מטמון, חזרה לרשת" כפי שמתואר על ידי ג'ייק ארצ'יבלד.

עם זאת, לגבי גרפיקה, אהיה מעט יותר ספציפי ואבחר את הגישה הבאה:

  • פריט גרפיקה אחד (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 של blob ובכתובות URL של נתונים.
  • אם לא הוגדרה גרפיקה ויש תמונת סמל בגודל הרצוי, התראות המדיה ישתמשו בה.
  • גודל הגרפיקה של ההתראות ב-Chrome ל-Android הוא 512x512. במכשירים פשוטים, היעד הוא 256x256.
  • סגירת התראות מדיה עם audio.src = ''.
  • מכיוון ש-Web Audio API לא מבקש 'התמקדות באודיו של Android' מסיבות היסטוריות, הדרך היחידה להפעיל אותו עם ה-MediaSession API היא להשתמש ברכיב <audio> כמקור הקלט ל-Web Audio API. אני מקווה ש-Web Audio Focus API ישפר את המצב בעתיד הקרוב.
  • קריאות לסשן מדיה ישפיעו על התראות המדיה רק אם הן מגיעות מאותה מסגרת כמו משאב המדיה. מידע נוסף זמין בקטע הקוד שבהמשך.
<iframe id="iframe">
  <audio>...</audio>
</iframe>
<script>
  iframe.contentWindow.navigator.mediaSession.metadata = new MediaMetadata({
    title: 'Never Gonna Give You Up',
    ...
  });
</script>

תמיכה

נכון למועד כתיבת הנתונים, Chrome ל-Android היא הפלטפורמה היחידה שתומכת ב-MediaSession API. מידע עדכני יותר על סטטוס ההטמעה של הדפדפן זמין בסטטוס הפלטפורמה של Chrome.

טעימות והדגמות

אתם מוזמנים לעיין בדוגמאות הרשמיות שלנו לסשנים של מדיה ב-Chrome, שמציגות את Blender Foundation והיצירה של יאן מורגנסטרן.

משאבים

מפרטי סשן מדיה: wicg.github.io/mediasession

בעיות במפרט: github.com/WICG/mediasession/issues

באגים ב-Chrome: crbug.com