您現在可以使用全新的 Media Session API,針對網頁應用程式播放的媒體提供中繼資料,自訂媒體通知。您也可以使用這個工具處理媒體相關事件,例如搜尋或追蹤可能會因為通知或媒體鍵而發生的異動。躍躍欲試嗎?請嘗試使用官方的媒體工作階段範例。
Chrome 57 支援 Media Session API (2017 年 2 月的 Beta 版,在 2017 年 3 月穩定)。
搜尋我想要的東西
您已經瞭解 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>
如您所知,在 Android 版 Chrome 的音訊元素中已停用 autoplay
,因此我們必須使用音訊元素的 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();
});
請注意,系統會保留媒體動作處理常式,這與事件監聽器模式非常類似,只不過處理事件代表瀏覽器停止執行任何預設行為,並以此做為信號,指出您的網頁應用程式支援媒體動作。因此,除非您設定適當的動作處理常式,否則系統不會顯示媒體動作控制項。
對了,取消媒體動作處理常式就像指派給 null
一樣簡單。
倒轉 / 快轉
如要控制略過的時間長度,Media Session 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 的一大優點是,通知匣不是唯一顯示媒體中繼資料和控制項的地方。媒體通知會自動同步到任何已配對的穿戴式裝置。它也會顯示在螢幕鎖定畫面上
離線播放優質內容
我知道你在想什麼。Service Worker 就能解決問題!
是的,但最重要的是,您必須確認這份檢查清單中的所有項目已勾選:
- 所有媒體和圖片檔案都會透過適當的
Cache-Control
HTTP 標頭提供。這可讓瀏覽器快取並重複使用先前擷取的資源。請參閱快取檢查清單。 - 確保所有媒體和圖片檔案都是透過
Allow-Control-Allow-Origin: *
HTTP 標頭提供。這會允許第三方網頁應用程式從網路伺服器擷取及使用 HTTP 回應。
Service Worker 快取策略
就媒體檔案而言,建議您如 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); });
實作注意事項
- 只有在媒體檔案持續時間至少 5 秒時,Chrome for Android 才會要求「完整」音訊焦點顯示媒體通知。
- 通知圖片支援 blob 網址和資料網址。
- 如果未定義任何圖片,且沒有符合理想大小的圖示圖片,則媒體通知會使用該圖片。
- Android 版 Google Chrome 的通知圖片大小為
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 for Android 是唯一支援 Media Session API 的平台。如需瀏覽器實作狀態的最新資訊,請參閱 Chrome 平台狀態。
範例與示範
歡迎參閱我們的官方 Chrome Media Session 範例,內容包括 Blender Foundation 和 Jan Morgenstern 的成果。
資源
Media Session Spec:wicg.github.io/mediasession
規格問題:github.com/WICG/mediasession/issues
Chrome 錯誤:crbug.com