新しい Media Session API を使用すると、ウェブアプリで再生しているメディアのメタデータを指定して、メディア通知をカスタマイズできるようになりました。また、通知やメディア キーから発生するシークやトラックの変更など、メディア関連のイベントを処理することもできます。それにはまず、公式のメディア セッション サンプルをお試しください。
Media Session API は Chrome 57 でサポートされています(2017 年 2 月はベータ版、2017 年 3 月は安定版)。
Gimme what I want
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
は Android 版 Chrome のオーディオ要素では無効になっています。つまり、オーディオ要素の 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) });
});
最初の操作の直後に音声を再生したくない場合は、audio 要素の load()
メソッドを使用することをおすすめします。これは、ユーザーが要素を操作したかどうかをブラウザが追跡する 1 つの方法です。コンテンツがすでに読み込まれているため、再生がスムーズになることもあります。
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 の優れた点は、メディアのメタデータとコントロールを表示できる場所が通知トレイだけではないことです。メディア通知は、ペア設定されたウェアラブル デバイスに自動的に同期されます。ロック画面にも表示されます。
オフラインでも快適に再生できるようにする
ご心配のことと存じます。サービス ワーカーを活用する
確かにそうですが、まず、このチェックリストのすべての項目がチェックされていることを確認してください。
- すべてのメディア ファイルとアートワーク ファイルは、適切な
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); });
実装上の注意
- Android 版 Chrome は、メディア ファイルの長さが5 秒以上の場合にのみ、メディア通知を表示するために「完全な」オーディオ フォーカスをリクエストします。
- 通知アートワークは、blob URL とデータ URL をサポートしています。
- アートワークが定義されておらず、適切なサイズのアイコン画像がある場合は、メディア通知でその画像が使用されます。
- Chrome for Android の通知アートワークのサイズは
512x512
です。ローエンドのデバイスの場合は256x256
です。 audio.src = ''
を使用してメディア通知を閉じる。- Web Audio API は歴史的な理由から Android のオーディオ フォーカスをリクエストしないため、Media Session API で動作させる唯一の方法は、
<audio>
要素を入力ソースとして Web Audio API にフックすることです。提案されている Web AudioFocus API により、近い将来この状況が改善されることを願っています。 - Media Session 呼び出しがメディア通知に影響するのは、メディア リソースと同じフレームから呼び出された場合に限られます。以下のスニペットを参照してください。
<iframe id="iframe">
<audio>...</audio>
</iframe>
<script>
iframe.contentWindow.navigator.mediaSession.metadata = new MediaMetadata({
title: 'Never Gonna Give You Up',
...
});
</script>
サポート
執筆時点では、Media Session API をサポートしているプラットフォームは Android 版 Chrome のみです。ブラウザの実装ステータスの最新情報については、Chrome プラットフォームのステータスをご覧ください。
サンプルとデモ
Blender Foundation と Jan Morgenstern の作品をフィーチャーした、Chrome の公式のメディア セッション サンプルをご覧ください。
リソース
メディア セッション仕様: wicg.github.io/mediasession
仕様に関する問題: github.com/WICG/mediasession/issues
Chrome のバグ: crbug.com