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

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

フランソワ ボーフォール
François Beaufort

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

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

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>

再生中のコンテンツをユーザーが制御できるようにする

メディア セッション アクションとは、ユーザーが現在のメディア再生を操作したときに、ウェブサイトが処理できるアクション(「再生」、「一時停止」など)です。アクションはイベントに似ており、同じように機能します。イベントと同様に、アクションは適切なオブジェクト(この場合は 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 を示すスクリーンキャスト。

関連情報