MediaSessionService によるバックグラウンド再生

アプリがフォアグラウンドにないときにメディアを再生することが望ましい場合がよくあります。対象 たとえば、ユーザーがロック状態になっても、通常音楽プレーヤーは音楽を再生し続けます。 別のアプリを使用しているかどうかがわかります。Media3 ライブラリには、 インターフェースをいくつか用意しました。

MediaSessionService を使用する

バックグラウンド再生を有効にするには、Player と 別の Service 内の MediaSession。 これにより、アプリがインストールされていない場合でも、デバイスはメディアの提供を継続できます。 使用できます。

<ph type="x-smartling-placeholder">
</ph> MediaSessionService を使用するとメディア セッションを個別に実行できます。
  アプリのアクティビティから
図 1: MediaSessionService によってメディアが許可される アプリのアクティビティとは別に実行する

Service 内でプレーヤーをホストする場合は、MediaSessionService を使用する必要があります。 そのためには、MediaSessionService` を拡張するクラスを作成し、 移動することもできます

MediaSessionService を使用すると、Google などの外部クライアントが使用できるようになります。 検出に使用するアシスタント、システム メディア コントロール、Wear OS などのコンパニオン デバイス サービスへの接続、再生の制御といった操作のすべてを、 UI アクティビティを除外できます実際、複数のクライアント アプリを 同じ MediaSessionService に同時に適用し、各アプリは独自の MediaController

サービス ライフサイクルを実装する

サービスの 3 つのライフサイクル メソッドを実装する必要があります。

  • onCreate() は、最初のコントローラが接続しようとしたときに呼び出され、 サービスがインスタンス化され、開始されます。Player を構築するのに最適な場所であり、 MediaSession
  • onTaskRemoved(Intent) は、ユーザーが できます。再生が進行中の場合、アプリはサービスを維持することを選択できます。 実行されています。プレーヤーが一時停止している場合、サービスは 停止する必要があります。
  • onDestroy() は、サービスが停止しているときに呼び出されます。すべてのリソース (プレーヤーやセッションを含む)を解放する必要があります。
で確認できます。

Kotlin

class PlaybackService : MediaSessionService() {
  private var mediaSession: MediaSession? = null

  // Create your player and media session in the onCreate lifecycle event
  override fun onCreate() {
    super.onCreate()
    val player = ExoPlayer.Builder(this).build()
    mediaSession = MediaSession.Builder(this, player).build()
  }

  // The user dismissed the app from the recent tasks
  override fun onTaskRemoved(rootIntent: Intent?) {
    val player = mediaSession?.player!!
    if (!player.playWhenReady
        || player.mediaItemCount == 0
        || player.playbackState == Player.STATE_ENDED) {
      // Stop the service if not playing, continue playing in the background
      // otherwise.
      stopSelf()
    }
  }

  // Remember to release the player and media session in onDestroy
  override fun onDestroy() {
    mediaSession?.run {
      player.release()
      release()
      mediaSession = null
    }
    super.onDestroy()
  }
}

Java

public class PlaybackService extends MediaSessionService {
  private MediaSession mediaSession = null;

  // Create your Player and MediaSession in the onCreate lifecycle event
  @Override
  public void onCreate() {
    super.onCreate();
    ExoPlayer player = new ExoPlayer.Builder(this).build();
    mediaSession = new MediaSession.Builder(this, player).build();
  }

  // The user dismissed the app from the recent tasks
  @Override
  public void onTaskRemoved(@Nullable Intent rootIntent) {
    Player player = mediaSession.getPlayer();
    if (!player.getPlayWhenReady()
        || player.getMediaItemCount() == 0
        || player.getPlaybackState() == Player.STATE_ENDED) {
      // Stop the service if not playing, continue playing in the background
      // otherwise.
      stopSelf();
    }
  }

  // Remember to release the player and media session in onDestroy
  @Override
  public void onDestroy() {
    mediaSession.getPlayer().release();
    mediaSession.release();
    mediaSession = null;
    super.onDestroy();
  }
}

バックグラウンドで再生を続ける代わりに、アプリで以下の操作を行えます。 ユーザーがアプリを閉じたときにサービスを停止できます。

Kotlin

override fun onTaskRemoved(rootIntent: Intent?) {
  val player = mediaSession.player
  if (player.playWhenReady) {
    // Make sure the service is not in foreground.
    player.pause()
  }
  stopSelf()
}

Java

@Override
public void onTaskRemoved(@Nullable Intent rootIntent) {
  Player player = mediaSession.getPlayer();
  if (player.getPlayWhenReady()) {
    // Make sure the service is not in foreground.
    player.pause();
  }
  stopSelf();
}

メディア セッションへのアクセスを提供する

onGetSession() メソッドをオーバーライドして、他のクライアントがメディアにアクセスできるようにします。 セッションを作成します。

Kotlin

class PlaybackService : MediaSessionService() {
  private var mediaSession: MediaSession? = null
  // [...] lifecycle methods omitted

  override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? =
    mediaSession
}

Java

public class PlaybackService extends MediaSessionService {
  private MediaSession mediaSession = null;
  // [...] lifecycle methods omitted

  @Override
  public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) {
    return mediaSession;
  }
}

マニフェストでサービスを宣言する

アプリにフォアグラウンド サービスを実行する権限が必要です。追加 FOREGROUND_SERVICE 権限をマニフェストに付与します。また、API 34 と API 34 をターゲットにしている場合は、 上記の FOREGROUND_SERVICE_MEDIA_PLAYBACK も参照してください。

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />

また、インテント フィルタを使用してマニフェストで Service クラスを宣言することも必要です。 /MediaSessionService

<service
    android:name=".PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="true">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaSessionService"/>
    </intent-filter>
</service>

まず、 foregroundServiceType Android を搭載したデバイスでアプリが実行されている場合は、mediaPlayback を含む 10(API レベル 29)以降です。

MediaController を使用して再生を制御する

プレーヤー UI を含むアクティビティまたはフラグメントで、リンクを確立できます。 MediaController を使用して、UI とメディア セッションの間の接続を作成できます。UI で使われているデータ を使用して UI から Pod 内のプレーヤーにコマンドを あります。詳しくは、 MediaController を作成する 作成と使用に関する詳細ガイドをMediaControllerご覧ください。

UI コマンドを処理する

MediaSession は、コントローラを介してコマンドをコントローラから受け取り、 MediaSession.CallbackMediaSession を初期化すると、デフォルトの すべての処理を自動的に処理する MediaSession.Callback の実装 MediaController がプレーヤーに送信するコマンドです。

通知

MediaSessionService は、対象の MediaNotification を自動的に作成します。 ほとんどのケースで機能しますデフォルトでは、公開された通知は MediaStyle の通知 最新情報を入手できます 再生コントロールを表示します。MediaNotification はセッションを認識し、他のアプリの再生を制御するのに使用できます。 接続されています

たとえば、MediaSessionService を使用する音楽ストリーミング アプリは、 タイトル、アーティスト、アルバムアートを表示する MediaNotification 現在のメディア アイテムが、再生コントロールとともに MediaSession の構成。

必要なメタデータは、メディアで提供することも、コンテンツの一部として宣言することもできます。 次のスニペットのようになります。

Kotlin

val mediaItem =
    MediaItem.Builder()
      .setMediaId("media-1")
      .setUri(mediaUri)
      .setMediaMetadata(
        MediaMetadata.Builder()
          .setArtist("David Bowie")
          .setTitle("Heroes")
          .setArtworkUri(artworkUri)
          .build()
      )
      .build()

mediaController.setMediaItem(mediaItem)
mediaController.prepare()
mediaController.play()

Java

MediaItem mediaItem =
    new MediaItem.Builder()
        .setMediaId("media-1")
        .setUri(mediaUri)
        .setMediaMetadata(
            new MediaMetadata.Builder()
                .setArtist("David Bowie")
                .setTitle("Heroes")
                .setArtworkUri(artworkUri)
                .build())
        .build();

mediaController.setMediaItem(mediaItem);
mediaController.prepare();
mediaController.play();

アプリで Android メディア コントロールのコマンドボタンをカスタマイズできます。詳細 Android Media のカスタマイズについて 設定をご覧ください。

通知のカスタマイズ

通知をカスタマイズするには、 MediaNotification.Provider 参加者: DefaultMediaNotificationProvider.Builder プロバイダ インターフェースのカスタム実装を作成します。を プロバイダを MediaSessionService に接続し、 setMediaNotificationProvider

再生の再開

メディアボタンは、Android デバイスやその他の周辺機器に搭載されているハードウェア ボタンです。 Bluetooth ヘッドセットの再生ボタンや一時停止ボタンなどから操作することもできます。メディア 3 は、サービスの実行中にメディアボタンの入力を処理します。

Media3 メディアボタン レシーバを宣言する

Media3 にはユーザーが再開するための API が用意されている アプリが終了した後や、デバイスが再起動された後でも再生できる 確認します。デフォルトでは、再生の再開はオフになっています。つまり、ユーザーは は、サービスが実行されていないときに再生を再開できません。オプトインするには、まず 次のように、マニフェストで MediaButtonReceiver を宣言します。

<receiver android:name="androidx.media3.session.MediaButtonReceiver"
  android:exported="true">
  <intent-filter>
    <action android:name="android.intent.action.MEDIA_BUTTON" />
  </intent-filter>
</receiver>

再生再開コールバックを実装する

Bluetooth デバイスまたは Bluetooth デバイスによって再生の再開が Android システム UI の再開機能 onPlaybackResumption() コールバックメソッドが呼び出されます

Kotlin

override fun onPlaybackResumption(
    mediaSession: MediaSession,
    controller: ControllerInfo
): ListenableFuture<MediaItemsWithStartPosition> {
  val settable = SettableFuture.create<MediaItemsWithStartPosition>()
  scope.launch {
    // Your app is responsible for storing the playlist and the start position
    // to use here
    val resumptionPlaylist = restorePlaylist()
    settable.set(resumptionPlaylist)
  }
  return settable
}

Java

@Override
public ListenableFuture<MediaItemsWithStartPosition> onPlaybackResumption(
    MediaSession mediaSession,
    ControllerInfo controller
) {
  SettableFuture<MediaItemsWithStartPosition> settableFuture = SettableFuture.create();
  settableFuture.addListener(() -> {
    // Your app is responsible for storing the playlist and the start position
    // to use here
    MediaItemsWithStartPosition resumptionPlaylist = restorePlaylist();
    settableFuture.set(resumptionPlaylist);
  }, MoreExecutors.directExecutor());
  return settableFuture;
}

再生速度、繰り返しモード、 シャッフル モード(onPlaybackResumption() はプレーヤーを構成するのに適した場所です) これらのパラメータを渡すことで、Media3 がプレーヤーを準備し コールバックが完了します。

コントローラの高度な構成と下位互換性

一般的なシナリオでは、アプリの UI で MediaController を使用して 再生リストの再生と表示を行えます。同時にセッションが公開されます モバイルやテレビで Android のメディア コントロールやアシスタントなどの外部クライアントに提供できるため、 スマートウォッチ向けの Wear OS と自動車の Android Auto。Media3 セッション デモアプリ は、そのようなシナリオを実装するアプリの一例です。

これらの外部クライアントは、以前の MediaControllerCompat AndroidX ライブラリ、または Android の android.media.session.MediaController 説明します。Media3 は従来のライブラリとの完全な下位互換性があり、 Android Framework API との相互運用性を提供します。

メディア通知コントローラを使用する

重要なのは、これらのレガシー コントローラやフレームワーク コントローラが フレームワーク PlaybackState.getActions() と同じ値と PlaybackState.getCustomActions()。特定のタイプのアクションとカスタム アクションを フレームワーク セッションを使用している場合、アプリはメディア通知コントローラを使用して、 使用可能なコマンドとカスタム レイアウトを設定できます。サービスによってメディアが接続されます。 通知コントローラをセッションに追加し、セッションは コールバックの onConnect() から返された ConnectionResult フレームワーク セッションのカスタム アクションとアクションを定義します。

モバイルのみのシナリオでは、アプリで以下の実装を提供できます。 MediaSession.Callback.onConnect(): 使用可能なコマンドを設定します。 フレームワーク セッション専用のカスタム レイアウトを次のように作成します。

Kotlin

override fun onConnect(
  session: MediaSession,
  controller: MediaSession.ControllerInfo
): ConnectionResult {
  if (session.isMediaNotificationController(controller)) {
    val sessionCommands =
      ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
        .add(customCommandSeekBackward)
        .add(customCommandSeekForward)
        .build()
    val playerCommands =
      ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
        .remove(COMMAND_SEEK_TO_PREVIOUS)
        .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
        .remove(COMMAND_SEEK_TO_NEXT)
        .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
        .build()
    // Custom layout and available commands to configure the legacy/framework session.
    return AcceptedResultBuilder(session)
      .setCustomLayout(
        ImmutableList.of(
          createSeekBackwardButton(customCommandSeekBackward),
          createSeekForwardButton(customCommandSeekForward))
      )
      .setAvailablePlayerCommands(playerCommands)
      .setAvailableSessionCommands(sessionCommands)
      .build()
  }
  // Default commands with default custom layout for all other controllers.
  return AcceptedResultBuilder(session).build()
}

Java

@Override
public ConnectionResult onConnect(
    MediaSession session, MediaSession.ControllerInfo controller) {
  if (session.isMediaNotificationController(controller)) {
    SessionCommands sessionCommands =
        ConnectionResult.DEFAULT_SESSION_COMMANDS
            .buildUpon()
            .add(customCommandSeekBackward)
            .add(customCommandSeekForward)
            .build();
    Player.Commands playerCommands =
        ConnectionResult.DEFAULT_PLAYER_COMMANDS
            .buildUpon()
            .remove(COMMAND_SEEK_TO_PREVIOUS)
            .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
            .remove(COMMAND_SEEK_TO_NEXT)
            .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
            .build();
    // Custom layout and available commands to configure the legacy/framework session.
    return new AcceptedResultBuilder(session)
        .setCustomLayout(
            ImmutableList.of(
                createSeekBackwardButton(customCommandSeekBackward),
                createSeekForwardButton(customCommandSeekForward)))
        .setAvailablePlayerCommands(playerCommands)
        .setAvailableSessionCommands(sessionCommands)
        .build();
  }
  // Default commands without default custom layout for all other controllers.
  return new AcceptedResultBuilder(session).build();
}

Android Auto にカスタム コマンドの送信を許可する

MediaLibraryService を使用する場合 モバイルアプリで Android Auto をサポートするには Android Auto コントローラを 適切なコマンドが必要で、指定されていない場合、Media3 は 受信するカスタムコマンド:

Kotlin

override fun onConnect(
  session: MediaSession,
  controller: MediaSession.ControllerInfo
): ConnectionResult {
  val sessionCommands =
    ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon()
      .add(customCommandSeekBackward)
      .add(customCommandSeekForward)
      .build()
  if (session.isMediaNotificationController(controller)) {
    // [...] See above.
  } else if (session.isAutoCompanionController(controller)) {
    // Available session commands to accept incoming custom commands from Auto.
    return AcceptedResultBuilder(session)
      .setAvailableSessionCommands(sessionCommands)
      .build()
  }
  // Default commands with default custom layout for all other controllers.
  return AcceptedResultBuilder(session).build()
}

Java

@Override
public ConnectionResult onConnect(
    MediaSession session, MediaSession.ControllerInfo controller) {
  SessionCommands sessionCommands =
      ConnectionResult.DEFAULT_SESSION_COMMANDS
          .buildUpon()
          .add(customCommandSeekBackward)
          .add(customCommandSeekForward)
          .build();
  if (session.isMediaNotificationController(controller)) {
    // [...] See above.
  } else if (session.isAutoCompanionController(controller)) {
    // Available commands to accept incoming custom commands from Auto.
    return new AcceptedResultBuilder(session)
        .setAvailableSessionCommands(sessionCommands)
        .build();
  }
  // Default commands without default custom layout for all other controllers.
  return new AcceptedResultBuilder(session).build();
}

セッションのデモアプリには 自動車モジュール、 個別の APK を必要とする Automotive OS のサポートを示しています。