アプリがフォアグラウンドにないときにメディアを再生することが望ましい場合がよくあります。たとえば、音楽プレーヤーは通常、ユーザーがデバイスをロックしているときや別のアプリを使用しているときでも音楽の再生を続けます。Media3 ライブラリには、バックグラウンド再生をサポートするためのインターフェースが用意されています。
MediaSessionService を使用する
バックグラウンド再生を有効にするには、Player
と MediaSession
を別の Service 内に含める必要があります。これにより、アプリがフォアグラウンドにない場合でも、デバイスはメディアの提供を継続できます。

MediaSessionService
により、メディア セッションをアプリのアクティビティとは別に実行できますService 内でプレーヤーをホストする場合は、MediaSessionService
を使用する必要があります。これを行うには、MediaSessionService
を拡張するクラスを作成し、その中にメディア セッションを作成します。
MediaSessionService
を使用すると、Google アシスタント、システム メディア コントロール、周辺機器のメディア ボタン、Wear OS などのコンパニオン デバイスといった外部クライアントから、アプリの UI アクティビティにまったくアクセスすることなく、サービスの検出、サービスへの接続、再生のコントロールを行うことができます。実際には、1 つの MediaSessionService
が同時に複数のクライアント アプリから接続されている可能性があり、各アプリには固有の MediaController
があります。
サービスのライフサイクルを実装する
サービスの 2 つのライフサイクル メソッドを実装する必要があります。
onCreate()
は、最初のコントローラが接続されようとしており、サービスがインスタンス化されて開始されたときに呼び出されます。Player
とMediaSession
を構築するのに最適な場所です。onDestroy()
は、サービスが停止されるときに呼び出されます。プレーヤーやセッションなどのすべてのリソースを解放する必要があります。
必要に応じて onTaskRemoved(Intent)
をオーバーライドして、ユーザーが最近使用したタスクからアプリを閉じるときに何が起こるかをカスタマイズできます。デフォルトでは、再生が進行中の場合はサービスは実行されたままになり、それ以外の場合は停止します。
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() } // 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(); } // 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?) { pauseAllPlayersAndStopSelf() }
Java
@Override public void onTaskRemoved(@Nullable Intent rootIntent) { pauseAllPlayersAndStopSelf(); }
onTaskRemoved
の手動実装では、isPlaybackOngoing()
を使用して、再生が継続中と見なされ、フォアグラウンド サービスが開始されたかどうかを確認できます。
メディア セッションへのアクセスを提供する
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
権限と FOREGROUND_SERVICE_MEDIA_PLAYBACK
権限が必要です。
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
また、MediaSessionService
のインテント フィルタと mediaPlayback
を含む foregroundServiceType
を使用して、マニフェストで Service
クラスを宣言する必要があります。
<service
android:name=".PlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
</service>
MediaController
を使用して再生を制御する
プレーヤー UI を含むアクティビティまたはフラグメントで、MediaController
を使用して UI とメディア セッション間のリンクを確立できます。UI はメディア コントローラを使用して、セッション内のプレーヤーに UI からコマンドを送信します。MediaController
の作成と使用の詳細については、MediaController
の作成ガイドをご覧ください。
MediaController
コマンドを処理する
MediaSession
は、MediaSession.Callback
を介してコントローラからコマンドを受信します。MediaSession
を初期化すると、MediaSession.Callback
のデフォルト実装が作成され、MediaController
がプレーヤーに送信するすべてのコマンドが自動的に処理されます。
通知
MediaSessionService
は、ほとんどの場合に機能する MediaNotification
を自動的に作成します。デフォルトでは、公開された通知は MediaStyle
通知であり、メディア セッションの最新情報で常に更新され、再生コントロールが表示されます。MediaNotification
はセッションを認識しており、同じセッションに接続されている他のアプリの再生を制御するために使用できます。
たとえば、MediaSessionService
を使用する音楽ストリーミング アプリは、MediaSession
構成に基づいて、現在再生中のメディア アイテムのタイトル、アーティスト、アルバムアートを再生コントロールとともに表示する MediaNotification
を作成します。
必要なメタデータは、メディアで提供するか、次のスニペットのようにメディア アイテムの一部として宣言できます。
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();
通知のライフサイクル
通知は、Player
のプレイリストに MediaItem
インスタンスが含まれるとすぐに作成されます。
すべての通知の更新は、Player
と MediaSession
の状態に基づいて自動的に行われます。
フォアグラウンド サービスの実行中は通知を削除できません。通知をすぐに削除するには、Player.release()
を呼び出すか、Player.clearMediaItems()
を使用してプレイリストをクリアする必要があります。
プレーヤーが 10 分以上一時停止、停止、または失敗し、ユーザー操作がそれ以上ない場合、サービスは自動的にフォアグラウンド サービスの状態から移行し、システムによって破棄されます。再生の再開を実装すると、ユーザーはサービス ライフサイクルを再開し、後で再生を再開できます。
通知のカスタマイズ
現在再生中のアイテムに関するメタデータは、MediaItem.MediaMetadata
を変更することでカスタマイズできます。既存のアイテムのメタデータを更新する場合は、Player.replaceMediaItem
を使用して、再生を中断せずにメタデータを更新できます。
Android メディア コントロールのカスタム メディアボタン設定を行うことで、通知に表示されるボタンの一部をカスタマイズすることもできます。詳しくは、Android のメディア コントロールをカスタマイズするをご覧ください。
通知自体をさらにカスタマイズするには、DefaultMediaNotificationProvider.Builder
を使用して MediaNotification.Provider
を作成するか、プロバイダ インターフェースのカスタム実装を作成します。setMediaNotificationProvider
を使用して、プロバイダを MediaSessionService
に追加します。
再生の再開
MediaSessionService
が終了した後、デバイスが再起動した後でも、再生の再開を提供して、ユーザーがサービスを再開し、中断したところから再生を再開できるようにすることができます。デフォルトでは、再生の再開はオフになっています。つまり、サービスが実行されていない場合、ユーザーは再生を再開できません。この機能を有効にするには、メディアボタン レシーバを宣言し、onPlaybackResumption
メソッドを実装する必要があります。
Media3 メディアボタン レシーバを宣言する
まず、マニフェストで 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 デバイスまたは 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, metadata (like title // and artwork) of the current item 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, metadata (like title // and artwork) of the current item and the start position to use here. MediaItemsWithStartPosition resumptionPlaylist = restorePlaylist(); settableFuture.set(resumptionPlaylist); }, MoreExecutors.directExecutor()); return settableFuture; }
再生速度、リピートモード、シャッフル モードなどの他のパラメータを保存している場合は、コールバックが完了したときに Media3 がプレーヤーを準備して再生を開始する前に、これらのパラメータでプレーヤーを構成するのに onPlaybackResumption()
が適しています。
このメソッドは、起動時に呼び出され、デバイスの再起動後に Android システム UI の再開通知を作成します。リッチ通知の場合は、ネットワーク アクセスがまだ利用できない可能性があるため、現在のアイテムの title
や artworkData
、artworkUri
などの MediaMetadata
フィールドをローカルで利用可能な値で入力することをおすすめします。MediaMetadata.extras
に MediaConstants.EXTRAS_KEY_COMPLETION_STATUS
と MediaConstants.EXTRAS_KEY_COMPLETION_PERCENTAGE
を追加して、再生再開位置を示すこともできます。
コントローラの高度な構成と下位互換性
一般的なシナリオは、アプリの UI で MediaController
を使用して再生を制御し、プレイリストを表示することです。同時に、セッションは、モバイルやテレビの Android メディア コントロールやアシスタント、スマートウォッチの Wear OS、自動車の Android Auto などの外部クライアントに公開されます。Media3 のセッション デモアプリは、このようなシナリオを実装するアプリの例です。
これらの外部クライアントは、以前の AndroidX ライブラリの MediaControllerCompat
や Android プラットフォームの android.media.session.MediaController
などの API を使用する場合があります。Media3 は、従来のライブラリとの完全な下位互換性があり、Android プラットフォーム API との相互運用性を提供します。
メディア通知コントローラを使用する
これらのレガシー コントローラとプラットフォーム コントローラは同じ状態を共有しており、コントローラごとに可視性をカスタマイズすることはできません(たとえば、利用可能な PlaybackState.getActions()
と PlaybackState.getCustomActions()
など)。メディア通知コントローラを使用して、これらのレガシー コントローラとプラットフォーム コントローラとの互換性を確保するために、プラットフォーム メディア セッションで設定された状態を構成できます。
たとえば、アプリは 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 button preferences and commands to configure the platform session. return AcceptedResultBuilder(session) .setMediaButtonPreferences( ImmutableList.of( createSeekBackwardButton(customCommandSeekBackward), createSeekForwardButton(customCommandSeekForward)) ) .setAvailablePlayerCommands(playerCommands) .setAvailableSessionCommands(sessionCommands) .build() } // Default commands with default button preferences 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 button preferences and commands to configure the platform session. return new AcceptedResultBuilder(session) .setMediaButtonPreferences( ImmutableList.of( createSeekBackwardButton(customCommandSeekBackward), createSeekForwardButton(customCommandSeekForward))) .setAvailablePlayerCommands(playerCommands) .setAvailableSessionCommands(sessionCommands) .build(); } // Default commands with default button preferences 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 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 for all other controllers. return new AcceptedResultBuilder(session).build(); }
セッションのデモアプリには、個別の APK を必要とする Automotive OS のサポートを示す自動車モジュールがあります。