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

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

François Beaufort
François Beaufort

ブラウザで現在再生されているコンテンツをユーザーに知らせ、再生を開始したページに戻ることなくそのコンテンツを操作できるようにするため、Media Session API が導入されました。これにより、ウェブ デベロッパーは、カスタム メディア通知のメタデータ、メディア イベント(再生、一時停止、シーク、トラックの変更など)、ビデオ会議イベント(マイクのオンとオフ、カメラのオンとオフ、通話の終了など)を通じて、このエクスペリエンスをカスタマイズできます。これらのカスタマイズは、パソコンのメディアハブ、モバイルのメディア通知、ウェアラブル デバイスなど、さまざまなコンテキストで利用できます。この記事では、これらのカスタマイズについて説明します。

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

Media Session API について

Media session API には次のようなメリットと機能があります。

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

対応ブラウザ

  • Chrome: 73。
  • Edge: 79.
  • Firefox: 82.
  • Safari: 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.
});

対応ブラウザ

  • Chrome: 111。
  • Edge: 111.
  • Firefox: サポートされていません。
  • Safari: サポートされていません。

次のスライド

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

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

対応ブラウザ

  • Chrome: 111.
  • Edge: 111。
  • Firefox: サポートされていません。
  • Safari: サポートされていません。

サンプル

Blender FoundationJan Morgenstern の作品をフィーチャーしたメディア セッションのサンプルをご覧ください。

Media Session API を説明するスクリーンキャスト。

リソース