Media Session API を使用してメディア通知と再生コントロールをカスタマイズする

ハードウェアのメディアキーを統合する方法、メディア通知をカスタマイズする方法など。

François Beaufort
François Beaufort

ユーザーがブラウザで現在再生中のコンテンツを確認して、その起動元のページに戻らずに再生を制御できるように、Media Session API が導入されました。ウェブ デベロッパーは、カスタム メディア通知のメタデータ、再生、一時停止、シーク、トラック変更などのメディア イベント、マイクのミュート / ミュート解除、カメラのオンとオフの切り替え、電話終了などのビデオ会議イベントのメタデータを使用して、このエクスペリエンスをカスタマイズできます。こうしたカスタマイズは、デスクトップのメディアハブ、モバイルのメディア通知、さらにはウェアラブル デバイスなど、いくつかのコンテキストで利用できます。この記事ではこれらのカスタマイズについて説明します

メディア セッションのコンテキストのスクリーンショット。
パソコンのメディアハブ、モバイルのメディア通知、ウェアラブル デバイス。

Media Session API について

Media Session API には次のような利点と機能があります。

  • ハードウェア メディアキーがサポートされています。
  • メディア通知は、モバイル、パソコン、ペア設定されたウェアラブル デバイスでカスタマイズできます。
  • メディアハブはパソコンで利用できます。
  • ロック画面のメディア コントロールは ChromeOS とモバイルで利用できます。
  • ピクチャー イン ピクチャー ウィンドウのコントロールは、音声の再生ビデオ会議スライドのプレゼンテーションに使用できます。
  • アシスタントをモバイルで統合できます。

対応ブラウザ

  • 73
  • 79
  • 82
  • 15

ソース

これらのポイントについて、いくつか例を挙げて説明します。

例 1: ユーザーがキーボードの「次のトラック」のメディアキーを押すと、ウェブ デベロッパーは、ブラウザがフォアグラウンドとバックグラウンドのどちらにあるかにかかわらず、このユーザー アクションを処理できます。

例 2: デバイスの画面がロックされているときにウェブでポッドキャストを聴いている場合でも、ロック画面のメディア コントロールから「後方に移動」アイコンを押すと、ウェブ デベロッパーは再生時間を数秒前に戻すことができます。

例 3: 音声を再生しているタブがある場合、パソコンの場合、メディアハブからの再生を簡単に停止できます。これにより、ウェブ デベロッパーは状態をクリアできます。

例 4: ビデオ通話中、ピクチャー イン ピクチャー ウィンドウの「マイクの切り替え」コントロールを押すと、ウェブサイトによるマイクデータの受信を停止できます。

これは、MediaSession インターフェースと MediaMetadata インターフェースという 2 つの異なるインターフェースを介して行われます。1 つ目は、再生中のコンテンツをユーザーがコントロールできるようにする方法です。2 つ目は、何を制御する必要があるかを MediaSession に伝える方法です。

下の図は、これらのインターフェースが特定のメディア コントロール(この場合はモバイルのメディア通知)とどのように関連しているかを示しています。

メディア セッションのインターフェースのイラスト。
モバイルでのメディア通知の構造。

再生中の曲をユーザーに教える

ウェブサイトで音声または動画を再生すると、モバイルでは通知トレイまたはパソコンのメディアハブに、メディア通知が自動的に表示されます。ブラウザは、ドキュメントのタイトルと、検出可能な最大のアイコン画像を使用して、適切な情報を表示します。Media Session API を使用すると、以下に示すように、タイトル、アーティスト名、アルバム名、アートワークなどのリッチメディア メタデータを使用して、メディア通知をカスタマイズできます。

Chrome は、メディア時間が 5 秒以上の場合にのみ、メディア通知を表示するように「フル」の音声フォーカスをリクエストします。これにより、呼び出し音などの偶発的な音で通知が表示されなくなります。

// After media (video or audio) starts playing
await document.querySelector("video").play();

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://via.placeholder.com/96',   sizes: '96x96',   type: 'image/png' },
      { src: 'https://via.placeholder.com/128', sizes: '128x128', type: 'image/png' },
      { src: 'https://via.placeholder.com/192', sizes: '192x192', type: 'image/png' },
      { src: 'https://via.placeholder.com/256', sizes: '256x256', type: 'image/png' },
      { src: 'https://via.placeholder.com/384', sizes: '384x384', type: 'image/png' },
      { src: 'https://via.placeholder.com/512', sizes: '512x512', type: 'image/png' },
    ]
  });

  // TODO: Update playback state.
}

再生が終了すると、通知は自動的に消えるため、メディア セッションを「解放」する必要はありません。navigator.mediaSession.metadata は、次の再生が開始されるときに使用されることに注意してください。そのため、メディア再生ソースが変更されたときに更新し、関連情報がメディア通知に表示されるようにすることが重要です。

メディア メタデータについて、いくつか注意すべき点があります。

  • 通知アートワークの配列は、blob URL とデータ URL をサポートしています。
  • アートワークが定義されておらず、適切なサイズのアイコン画像(<link rel=icon> を使用して指定)がある場合は、メディア通知で使用されます。
  • Chrome for Android の通知アートワークのターゲット サイズは 512x512 です。ローエンド デバイスの場合は 256x256 です。
  • メディア HTML 要素の title 属性は、「この曲なに?」の macOS ウィジェットで使用されます。
  • メディア リソースが(iframe などに)埋め込まれている場合、Media Session API の情報を埋め込みコンテキストから設定する必要があります。以下のスニペットをご覧ください。
<iframe id="iframe">
  <video>...</video>
</iframe>
<script>
  iframe.contentWindow.navigator.mediaSession.metadata = new MediaMetadata({
    title: 'Never Gonna Give You Up',
    ...
  });
</script>

また、セクションのタイトル、タイムスタンプ、スクリーンショット画像などのチャプター情報をメディア メタデータに追加することもできます。これにより、ユーザーはメディアのコンテンツ内を移動できるようになります。

navigator.mediaSession.metadata = new MediaMetadata({
  // title, artist, album, artwork, ...
  chapterInfo: [{
    title: 'Chapter 1',
    startTime: 0,
    artwork: [
      { src: 'https://via.placeholder.com/128', sizes: '128x128', type: 'image/png' },
      { src: 'https://via.placeholder.com/512', sizes: '512x512', type: 'image/png' },
    ]
  }, {
    title: 'Chapter 2',
    startTime: 42,
    artwork: [
      { src: 'https://via.placeholder.com/128', sizes: '128x128', type: 'image/png' },
      { src: 'https://via.placeholder.com/512', sizes: '512x512', type: 'image/png' },
    ]
  }]
});
ChromeOS のメディア通知に表示されるチャプター情報。
ChromeOS のチャプターを紹介するメディア通知。

再生中の曲をユーザーが操作できるようにする

メディア セッション アクションとは、ユーザーが現在のメディア再生を操作したときに、ウェブサイトが処理できるアクション(「再生」や「一時停止」など)です。アクションはイベントに類似しており、ほぼ同じように機能します。イベントと同様に、アクションは適切なオブジェクト(この場合は MediaSession のインスタンス)にハンドラを設定することで実装されます。アクションの中には、ユーザーがヘッドセット、別のリモート デバイス、キーボードからボタンを押したとき、またはメディア通知を操作したときにトリガーされるものがあります。

Windows 10 のメディア通知のスクリーンショット。
Windows 10 のカスタマイズされたメディア通知

一部のメディア セッション アクションはサポートされていない場合があるため、設定時には try…catch ブロックを使用することをおすすめします。

const actionHandlers = [
  ['play',          () => { /* ... */ }],
  ['pause',         () => { /* ... */ }],
  ['previoustrack', () => { /* ... */ }],
  ['nexttrack',     () => { /* ... */ }],
  ['stop',          () => { /* ... */ }],
  ['seekbackward',  (details) => { /* ... */ }],
  ['seekforward',   (details) => { /* ... */ }],
  ['seekto',        (details) => { /* ... */ }],
  /* Video conferencing actions */
  ['togglemicrophone', () => { /* ... */ }],
  ['togglecamera',     () => { /* ... */ }],
  ['hangup',           () => { /* ... */ }],
  /* Presenting slides actions */
  ['previousslide', () => { /* ... */ }],
  ['nextslide',     () => { /* ... */ }],
];

for (const [action, handler] of actionHandlers) {
  try {
    navigator.mediaSession.setActionHandler(action, handler);
  } catch (error) {
    console.log(`The media session action "${action}" is not supported yet.`);
  }
}

メディア セッション アクション ハンドラの設定を解除するには、null に設定するだけです。

try {
  // Unset the "nexttrack" action handler at the end of a playlist.
  navigator.mediaSession.setActionHandler('nexttrack', null);
} catch (error) {
  console.log(`The media session action "nexttrack" is not supported yet.`);
}

一度設定すると、メディア セッション アクション ハンドラはメディアの再生後も維持されます。これはイベント リスナー パターンに似ていますが、イベントを処理すると、ブラウザがデフォルトの動作を停止し、ウェブサイトがメディア アクションをサポートしていることを示すシグナルとして使用する点が異なります。そのため、適切なアクション ハンドラが設定されていなければ、メディア アクション コントロールは表示されません。

macOS Big Sur の「この曲なに?」ウィジェットのスクリーンショット。
macOS Big Sur の「この曲なに?」ウィジェット。

再生 / 一時停止

"play" アクションはユーザーがメディア再生を再開することを示し、"pause" はメディア再生の一時停止を示します。

メディア通知には「再生/一時停止」アイコンが常に表示され、関連するメディア イベントはブラウザによって自動的に処理されます。デフォルトの動作をオーバーライドするには、次のようにメディア アクション「再生」と「一時停止」を処理します。

ブラウザは、たとえばシークや読み込みの際に、ウェブサイトがメディアを再生していないとみなすことがあります。その場合は、navigator.mediaSession.playbackState"playing" または "paused" に設定して、この動作をオーバーライドして、ウェブサイトの UI がメディア通知コントロールと同期するようにします。

const video = document.querySelector('video');

navigator.mediaSession.setActionHandler('play', async () => {
  // Resume playback
  await video.play();
});

navigator.mediaSession.setActionHandler('pause', () => {
  // Pause active playback
  video.pause();
});

video.addEventListener('play', () => {
  navigator.mediaSession.playbackState = 'playing';
});

video.addEventListener('pause', () => {
  navigator.mediaSession.playbackState = 'paused';
});

前の曲

"previoustrack" アクションは、メディア再生に開始の概念がある場合はユーザーが現在のメディア再生を最初から開始し、メディア再生にプレイリストの概念がある場合はプレイリスト内の前のアイテムに移動したいことを示します。

navigator.mediaSession.setActionHandler('previoustrack', () => {
  // Play previous track.
});

次の曲

"nexttrack" アクションは、メディア再生に再生リストの概念がある場合、ユーザーがメディア再生を再生リストの次の項目に移動したいことを示します。

navigator.mediaSession.setActionHandler('nexttrack', () => {
  // Play next track.
});

停止

"stop" アクションは、ユーザーがメディア再生を停止し、必要に応じて状態をクリアすることを示します。

navigator.mediaSession.setActionHandler('stop', () => {
  // Stop playback and clear state if appropriate.
});

巻き戻し / 早送り

"seekbackward" アクションは、ユーザーがメディア再生時間を少しだけ早戻しすることを示し、"seekforward" はメディア再生時間を短時間早送りすることを希望しています。どちらの場合も、短い期間は数秒を意味します。

アクション ハンドラで指定される seekOffset 値は、メディアの再生時間を移動する秒数です。指定されていない場合は(undefined など)、適切な時間(10 ~ 30 秒など)を使用してください。

const video = document.querySelector('video');
const defaultSkipTime = 10; /* Time to skip in seconds by default */

navigator.mediaSession.setActionHandler('seekbackward', (details) => {
  const skipTime = details.seekOffset || defaultSkipTime;
  video.currentTime = Math.max(video.currentTime - skipTime, 0);
  // TODO: Update playback state.
});

navigator.mediaSession.setActionHandler('seekforward', (details) => {
  const skipTime = details.seekOffset || defaultSkipTime;
  video.currentTime = Math.min(video.currentTime + skipTime, video.duration);
  // TODO: Update playback state.
});

特定の時間までシークする

"seekto" アクションは、ユーザーがメディアの再生時間を特定の時間に移動したいことを示します。

アクション ハンドラで指定される seekTime 値は、メディア再生時間の移動先となる秒数です。

アクションがシーケンスの一部として複数回呼び出され、これがそのシーケンスの最後の呼び出しではない場合、アクション ハンドラで指定される fastSeek ブール値は true になります。

const video = document.querySelector('video');

navigator.mediaSession.setActionHandler('seekto', (details) => {
  if (details.fastSeek && 'fastSeek' in video) {
    // Only use fast seek if supported.
    video.fastSeek(details.seekTime);
    return;
  }
  video.currentTime = details.seekTime;
  // TODO: Update playback state.
});

再生位置を設定する

メディアの再生位置を通知に正確に表示するには、以下に示すように、適切なタイミングで位置の状態を設定するだけです。位置の状態は、メディアの再生速度、再生時間、現在の時刻を組み合わせたものです。

ChromeOS のロック画面のメディア コントロールのスクリーンショット。
ChromeOS のロック画面のメディア コントロール

所要時間は正の値で指定する必要があります。位置は正の値で、期間よりも小さい値にする必要があります。再生速度は 0 より大きくする必要があります。

const video = document.querySelector('video');

function updatePositionState() {
  if ('setPositionState' in navigator.mediaSession) {
    navigator.mediaSession.setPositionState({
      duration: video.duration,
      playbackRate: video.playbackRate,
      position: video.currentTime,
    });
  }
}

// When video starts playing, update duration.
await video.play();
updatePositionState();

// When user wants to seek backward, update position.
navigator.mediaSession.setActionHandler('seekbackward', (details) => {
  /* ... */
  updatePositionState();
});

// When user wants to seek forward, update position.
navigator.mediaSession.setActionHandler('seekforward', (details) => {
  /* ... */
  updatePositionState();
});

// When user wants to seek to a specific time, update position.
navigator.mediaSession.setActionHandler('seekto', (details) => {
  /* ... */
  updatePositionState();
});

// When video playback rate changes, update position state.
video.addEventListener('ratechange', (event) => {
  updatePositionState();
});

位置の状態は、null に設定するだけで簡単にリセットできます。

// Reset position state when media is reset.
navigator.mediaSession.setPositionState(null);

ビデオ会議の操作

ユーザーがビデオ通話をピクチャー イン ピクチャー ウィンドウに配置すると、ブラウザにはマイクとカメラのコントロールと、電話を切るコントロールが表示されることがあります。ユーザーがこれらをクリックすると、ウェブサイトは以下のビデオ会議アクションを通じてアクションを処理します。例については、ビデオ会議のサンプルをご覧ください。

ピクチャー イン ピクチャー ウィンドウのビデオ会議コントロールのスクリーンショット。
ピクチャー イン ピクチャー ウィンドウでのビデオ会議のコントロール

マイクの切り替え

"togglemicrophone" アクションは、ユーザーがマイクをミュートまたはミュート解除することを示します。setMicrophoneActive(isActive) メソッドは、ウェブサイトが現在マイクをアクティブとみなしているかどうかをブラウザに伝えます。

let isMicrophoneActive = false;

navigator.mediaSession.setActionHandler('togglemicrophone', () => {
  if (isMicrophoneActive) {
    // Mute the microphone.
  } else {
    // Unmute the microphone.
  }
  isMicrophoneActive = !isMicrophoneActive;
  navigator.mediaSession.setMicrophoneActive(isMicrophoneActive);
});

カメラの切り替え

"togglecamera" アクションは、ユーザーがアクティブなカメラのオンとオフを切り替えることを示します。setCameraActive(isActive) メソッドは、ウェブサイトがアクティブであるとブラウザが認識しているかどうかを示します。

let isCameraActive = false;

navigator.mediaSession.setActionHandler('togglecamera', () => {
  if (isCameraActive) {
    // Disable the camera.
  } else {
    // Enable the camera.
  }
  isCameraActive = !isCameraActive;
  navigator.mediaSession.setCameraActive(isCameraActive);
});

通話を終了

"hangup" アクションは、ユーザーが通話を終了しようとしていることを示します。

navigator.mediaSession.setActionHandler('hangup', () => {
  // End the call.
});

プレゼンテーションに関するスライドの操作

ユーザーがスライド プレゼンテーションをピクチャー イン ピクチャー ウィンドウに配置すると、ブラウザにはスライド内を移動するためのコントロールが表示されることがあります。ユーザーがこれらをクリックすると、ウェブサイトは Media Session API を介してそれらを処理します。例については、スライドのプレゼンテーションのサンプルをご覧ください。

前のスライド

"previousslide" アクションは、ユーザーがスライドのプレゼンテーション中に前のスライドに戻る必要があることを示します。

navigator.mediaSession.setActionHandler('previousslide', () => {
  // Show previous slide.
});

対応ブラウザ

  • 111
  • 111
  • x
  • x

次のスライド

"nextslide" アクションは、ユーザーがスライドのプレゼンテーション中に次のスライドに移動することを示します。

navigator.mediaSession.setActionHandler('nextslide', () => {
  // Show next slide.
});

対応ブラウザ

  • 111
  • 111
  • x
  • x

サンプル

Blender FoundationJan Morgenstern の作品を取り上げた Media Session サンプルをご覧ください。

Media Session API を示すスクリーンキャスト。

リソース