ปรับแต่งการแจ้งเตือนสื่อและจัดการเพลย์ลิสต์

François Beaufort
François Beaufort

Media Session API ใหม่ช่วยให้คุณปรับแต่งการแจ้งเตือนสื่อได้ด้วยการระบุข้อมูลเมตาสำหรับสื่อที่เว็บแอปกำลังเล่นอยู่ ทั้งยังช่วยให้คุณจัดการเหตุการณ์ที่เกี่ยวข้องกับสื่อได้ เช่น การกรอวิดีโอหรือติดตามการเปลี่ยนแปลงที่อาจมาจากการแจ้งเตือนหรือคีย์สื่อ น่าสนใจใช่ไหม ลองใช้ตัวอย่างเซสชันสื่ออย่างเป็นทางการ

Media Session API ใช้งานได้ใน Chrome 57 (เบต้าในเดือนกุมภาพันธ์ 2017 และเสถียรในเดือนมีนาคม 2017)

สรุปเซสชันสื่อ;DR;
รูปภาพ โดย Michael Alø-Nielsen / 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 จะดำเนินการอย่างดีที่สุดในการแสดงข้อมูล ที่เหมาะสมโดยใช้ชื่อเอกสารและภาพไอคอนที่ใหญ่ที่สุดที่หาได้

ไม่มีเซสชันสื่อ
ไม่มีเซสชันสื่อ
มีเซสชันสื่อ
มีเซสชันสื่อ

ตั้งค่าข้อมูลเมตา

มาดูวิธีปรับแต่งการแจ้งเตือนสื่อด้วยการตั้งค่าข้อมูลเมตาเซสชันสื่อบางอย่าง เช่น ชื่อ ศิลปิน ชื่ออัลบั้ม และอาร์ตเวิร์กด้วย 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();
});

โปรดทราบว่าเครื่องจัดการการทำงานของสื่อจะยังคงอยู่ สิ่งนี้คล้ายกับรูปแบบ Listener เหตุการณ์อย่างมาก เพียงแต่ว่าการจัดการเหตุการณ์จะทำให้เบราว์เซอร์หยุดทำงานที่เป็นค่าเริ่มต้นและใช้สิ่งนี้เป็นสัญญาณว่าเว็บแอปของคุณรองรับการทำงานของสื่อ ดังนั้น การควบคุมการใช้สื่อจะไม่แสดงจนกว่าคุณจะตั้งค่าเครื่องจัดการการดำเนินการที่เหมาะสม

การยกเลิกการตั้งค่าเครื่องจัดการการดำเนินการสื่อนั้นง่ายพอๆ กับการกำหนดให้กับ 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...
});

การแจ้งเตือนทุกที่

ความเจ๋งของ Media Session API คือถาดการแจ้งเตือนไม่ได้เป็นเพียงที่เดียวที่แสดงข้อมูลเมตาและการควบคุมของสื่อได้ การแจ้งเตือนสื่อจะซิงค์กับอุปกรณ์ที่สวมใส่ได้ที่จับคู่ไว้โดยอัตโนมัติ และยังแสดงบนหน้าจอล็อกด้วย

หน้าจอล็อก
หน้าจอล็อก - รูปภาพ โดย Michael Alø-Nielsen / CC BY 2.0
การแจ้งเตือน Wear
การแจ้งเตือนการสวมใส่

ทำให้วิดีโอเล่นแบบออฟไลน์ได้อย่างสวยงาม

เราทราบดีว่าคุณกำลังคิดอะไรอยู่ Service Worker ช่วยเหลือคุณได้

จริง แต่สิ่งแรกที่สำคัญที่สุดคือคุณต้องตรวจสอบว่าได้ตรวจสอบรายการทั้งหมดในรายการตรวจสอบนี้แล้ว ดังนี้

  • ไฟล์สื่อและอาร์ตเวิร์กทั้งหมดจะแสดงพร้อมส่วนหัว HTTP Cache-Control ที่เหมาะสม ซึ่งจะช่วยให้เบราว์เซอร์แคชและนำทรัพยากรที่ดึงมาก่อนหน้านี้มาใช้ซ้ำได้ โปรดดูรายการตรวจสอบการแคช
  • ตรวจสอบว่าไฟล์สื่อและอาร์ตเวิร์กทั้งหมดแสดงด้วยส่วนหัว HTTP ของ Allow-Control-Allow-Origin: * การดำเนินการนี้จะอนุญาตให้เว็บแอปของบุคคลที่สามดึงและใช้การตอบสนอง HTTP จากเว็บเซิร์ฟเวอร์ของคุณได้

กลยุทธ์การแคช Service Worker

สำหรับไฟล์สื่อ เราขอแนะนำกลยุทธ์ "Cache, back to network" แบบง่ายๆ ตามภาพประกอบโดย Jake Archibald

สำหรับอาร์ตเวิร์ก ฉันจะระบุให้เฉพาะเจาะจงขึ้นอีกเล็กน้อย และเลือกวิธีด้านล่าง

  • อาร์ตเวิร์ก 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 วินาที
  • อาร์ตเวิร์กการแจ้งเตือนรองรับ BLOB URL และ URL ข้อมูล
  • หากไม่มีการกำหนดอาร์ตเวิร์กไว้และมีรูปภาพไอคอนในขนาดที่ต้องการ การแจ้งเตือนสื่อก็จะใช้อาร์ตเวิร์กนั้น
  • ขนาดของอาร์ตเวิร์กการแจ้งเตือนใน Chrome สำหรับ Android คือ 512x512 สำหรับอุปกรณ์ระดับโลว์เอนด์ ราคาจะอยู่ที่ 256x256
  • ปิดการแจ้งเตือนสื่อด้วย audio.src = ''
  • เนื่องจาก Web Audio API ไม่ได้ขอ Android Audio Focus ด้วยเหตุผลที่ผ่านมา วิธีเดียวที่จะทำให้ใช้งานกับ 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 เป็นแพลตฟอร์มเดียวที่สนับสนุน Media Session API ติดตามข้อมูลล่าสุดเกี่ยวกับสถานะการใช้งานเบราว์เซอร์ได้ที่สถานะแพลตฟอร์ม Chrome

ตัวอย่างและการสาธิต

ดูตัวอย่างเซสชันสื่ออย่างเป็นทางการของ Chrome ที่นำเสนอ Blender Foundation และผลงานของ Jan Morgenstern

แหล่งข้อมูล

ข้อมูลจำเพาะของเซสชันสื่อ: wicg.github.io/mediasession

ปัญหาเกี่ยวกับข้อมูลจำเพาะ: github.com/WICG/mediasession/issues

ข้อบกพร่องของ Chrome: crbug.com