アプリでは、マニフェスト内の intent-filter で MediaBrowserService
を宣言する必要があります。サービス名は独自に指定できます。次の例では「MediaPlaybackService」です。
<service android:name=".MediaPlaybackService">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
注: MediaBrowserService
の推奨される実装は、
MediaBrowserServiceCompat
です。
で定義されます。
media-compat サポート ライブラリ。
このページ全体を通して、「MediaBrowserService」という用語はこれは Pod 内で
/MediaBrowserServiceCompat
。
メディア セッションの初期化
サービスで onCreate()
ライフサイクル コールバック メソッドを受信したら、以下の手順を行います。
- メディア セッションを作成して初期化します。
- メディア セッション コールバックを設定します。
- メディア セッション トークンを設定します。
次の onCreate()
コードは、この手順を示しています。
Kotlin
private const val MY_MEDIA_ROOT_ID = "media_root_id" private const val MY_EMPTY_MEDIA_ROOT_ID = "empty_root_id" class MediaPlaybackService : MediaBrowserServiceCompat() { private var mediaSession: MediaSessionCompat? = null private lateinit var stateBuilder: PlaybackStateCompat.Builder override fun onCreate() { super.onCreate() // Create a MediaSessionCompat mediaSession = MediaSessionCompat(baseContext, LOG_TAG).apply { // Enable callbacks from MediaButtons and TransportControls setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS ) // Set an initial PlaybackState with ACTION_PLAY, so media buttons can start the player stateBuilder = PlaybackStateCompat.Builder() .setActions(PlaybackStateCompat.ACTION_PLAY or PlaybackStateCompat.ACTION_PLAY_PAUSE ) setPlaybackState(stateBuilder.build()) // MySessionCallback() has methods that handle callbacks from a media controller setCallback(MySessionCallback()) // Set the session's token so that client activities can communicate with it. setSessionToken(sessionToken) } } }
Java
public class MediaPlaybackService extends MediaBrowserServiceCompat { private static final String MY_MEDIA_ROOT_ID = "media_root_id"; private static final String MY_EMPTY_MEDIA_ROOT_ID = "empty_root_id"; private MediaSessionCompat mediaSession; private PlaybackStateCompat.Builder stateBuilder; @Override public void onCreate() { super.onCreate(); // Create a MediaSessionCompat mediaSession = new MediaSessionCompat(context, LOG_TAG); // Enable callbacks from MediaButtons and TransportControls mediaSession.setFlags( MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); // Set an initial PlaybackState with ACTION_PLAY, so media buttons can start the player stateBuilder = new PlaybackStateCompat.Builder() .setActions( PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PLAY_PAUSE); mediaSession.setPlaybackState(stateBuilder.build()); // MySessionCallback() has methods that handle callbacks from a media controller mediaSession.setCallback(new MySessionCallback()); // Set the session's token so that client activities can communicate with it. setSessionToken(mediaSession.getSessionToken()); } }
クライアント接続の管理
MediaBrowserService
には、クライアント接続を処理する 2 つのメソッドがあります。
onGetRoot()
個のコントロール
サービスへのアクセス権
onLoadChildren()
は、クライアントが MediaBrowserService
のコンテンツ階層のメニューを作成して表示する機能を提供します。
onGetRoot()
を使用したクライアント接続の制御
onGetRoot()
メソッドからは、コンテンツ階層のルートノードを返します。もし
メソッドが null を返すと、接続は拒否されます。
クライアントがサービスに接続してメディア コンテンツをブラウジングできるようにするには、 onGetRoot() は null 以外の BrowserRoot を返す必要がある。 コンテンツ階層を表します
クライアントがブラウジングせずに MediaSession に接続できるようにするには、onGetRoot() null 以外の BrowserRoot が返される必要がありますが、ルート ID は 空のコンテンツ階層を作成します。
onGetRoot()
の典型的な実装は次のようになります。
Kotlin
override fun onGetRoot( clientPackageName: String, clientUid: Int, rootHints: Bundle? ): MediaBrowserServiceCompat.BrowserRoot { // (Optional) Control the level of access for the specified package name. // You'll need to write your own logic to do this. return if (allowBrowsing(clientPackageName, clientUid)) { // Returns a root ID that clients can use with onLoadChildren() to retrieve // the content hierarchy. MediaBrowserServiceCompat.BrowserRoot(MY_MEDIA_ROOT_ID, null) } else { // Clients can connect, but this BrowserRoot is an empty hierarchy // so onLoadChildren returns nothing. This disables the ability to browse for content. MediaBrowserServiceCompat.BrowserRoot(MY_EMPTY_MEDIA_ROOT_ID, null) } }
Java
@Override public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) { // (Optional) Control the level of access for the specified package name. // You'll need to write your own logic to do this. if (allowBrowsing(clientPackageName, clientUid)) { // Returns a root ID that clients can use with onLoadChildren() to retrieve // the content hierarchy. return new BrowserRoot(MY_MEDIA_ROOT_ID, null); } else { // Clients can connect, but this BrowserRoot is an empty hierarchy // so onLoadChildren returns nothing. This disables the ability to browse for content. return new BrowserRoot(MY_EMPTY_MEDIA_ROOT_ID, null); } }
接続できるユーザーを制御し、
MediaBrowserService
に送信します。その方法の一つは、アクセス制御リスト(ACL)を
許可する接続を指定するか、ファイアウォール ルールで
拒否する接続を指定します。ACL の実装方法の例については、
接続を許可する場合は、このモジュールの
PackageValidator
Universal Android Music Player クラス
サンプルアプリです。
要件に応じて異なるコンテンツ階層を用意し、
どのような種類のクライアントが
クエリを行っているのかがわかります特に、Android Auto では、
オーディオ アプリを操作します。詳しくは、
自動。マイページ
接続時に clientPackageName
を確認してクライアントを判断できる
クライアント(または rootHints
)に応じて異なる BrowserRoot
。
onLoadChildren()
を使用したコンテンツとの通信
接続後のクライアントでは、MediaBrowserCompat.subscribe()
を繰り返し呼び出して UI のローカル表現を作成することにより、コンテンツ階層を走査できます。この subscribe()
メソッドからは onLoadChildren()
コールバックがサービスに送信され、MediaBrowser.MediaItem
オブジェクトのリストが返されます。
各 MediaItem には、不透明なトークンである固有の ID 文字列があります。クライアントがサブメニューを開いたり、アイテムを再生したりする際には、その ID が渡されます。サービスは、ID を適切なメニューノードまたはコンテンツ アイテムに関連付ける役割を担います。
onLoadChildren()
の簡単な実装例を以下に示します。
Kotlin
override fun onLoadChildren( parentMediaId: String, result: MediaBrowserServiceCompat.Result<List<MediaBrowserCompat.MediaItem>> ) { // Browsing not allowed if (MY_EMPTY_MEDIA_ROOT_ID == parentMediaId) { result.sendResult(null) return } // Assume for example that the music catalog is already loaded/cached. val mediaItems = emptyList<MediaBrowserCompat.MediaItem>() // Check if this is the root menu: if (MY_MEDIA_ROOT_ID == parentMediaId) { // Build the MediaItem objects for the top level, // and put them in the mediaItems list... } else { // Examine the passed parentMediaId to see which submenu we're at, // and put the children of that menu in the mediaItems list... } result.sendResult(mediaItems) }
Java
@Override public void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result) { // Browsing not allowed if (TextUtils.equals(MY_EMPTY_MEDIA_ROOT_ID, parentMediaId)) { result.sendResult(null); return; } // Assume for example that the music catalog is already loaded/cached. List<MediaItem> mediaItems = new ArrayList<>(); // Check if this is the root menu: if (MY_MEDIA_ROOT_ID.equals(parentMediaId)) { // Build the MediaItem objects for the top level, // and put them in the mediaItems list... } else { // Examine the passed parentMediaId to see which submenu we're at, // and put the children of that menu in the mediaItems list... } result.sendResult(mediaItems); }
注: MediaBrowserService によって配信される MediaItem
オブジェクト
アイコンのビットマップは含めないでください。代わりに Uri
を使用する:
setIconUri()
各アイテムのMediaDescription
作成時。
onLoadChildren()
を実装する方法の例については、Universal Android Music Player サンプルアプリをご覧ください。
メディア ブラウザ サービスのライフサイクル
Android サービスの動作は、「開始」されたのか、1 つ以上のクライアントに「バインド」されたのかによって異なります。サービスを作成したら、それを開始することも、バインドすることも、その両方を行うことも可能です。いずれの状態でも、サービスは完全に機能し、設計されたタスクの実施が可能です。違いは、サービスの存続期間です。バインドされたサービスは、すべてのバインド相手のクライアントからバインド解除されるまで破棄されません。開始されたサービスは、明示的に停止と破棄を行えます(どのクライアントにもバインドされていない場合)。
別のアクティビティで実行されている MediaBrowser
から MediaBrowserService
への接続がなされると、そのアクティビティがサービスにバインドされ、サービスがバインドされた(開始はされていない)状態になります。このデフォルトの動作は、MediaBrowserServiceCompat
クラスに組み込まれています。
すべてのクライアントでバインドが解除されると、バインドのみされた(開始はされていない)状態のサービスは破棄されます。この時点で UI アクティビティが切断されると、サービスは破棄されます。これは、まだ音楽が再生されていない場合は問題ではありません。ただし、再生が開始されていたら、おそらくユーザーはアプリ切り替え後も聴き続けられることを期待しています。よって、別のアプリでの作業のために UI のバインドを解除しても、プレーヤーは破棄しないほうがよいでしょう。
このため、サービスが開始したときに確実に開始されるようにする必要があります。
startService()
を呼び出して再生します。
開始されたサービスは、バインドされているかどうかにかかわらず、明示的に停止する必要があります。この
コントロール UI が表示されてもプレーヤーが動作を継続できるようにする
アクティビティのバインドが解除されます。
開始状態のサービスを停止するには、Context.stopService()
または stopSelf()
を呼び出します。これにより、サービスはシステムによってできるだけ早く停止され、破棄されます。ただし、まだサービスにバインドしているクライアントがある場合は、そのすべてのバインドが解除されるまで、サービス停止の呼び出しは遅延します。
MediaBrowserService
のライフサイクルは、その作成方法、バインドされているクライアントの数、メディア セッション コールバックから受ける呼び出しによって制御されます。まとめ
- サービスは、メディアボタンへの応答として開始されたとき、またはアクティビティに(
MediaBrowser
を介しての接続後に)バインドされたときに作成されます。 - メディア セッションの
onPlay()
コールバックには、startService()
を呼び出すコードを含める必要があります。これにより、サービスが開始状態となり、すべての UIMediaBrowser
アクティビティとのバインドが解除された場合でも実行が継続されます。 onStop()
コールバックではstopSelf()
を呼び出します。開始状態のサービスは、これにより停止します。また、バインドされているアクティビティがない場合、サービスは破棄されます。それ以外の場合は、サービスはすべてのアクティビティのバインドが解除されるまでバインド状態のままになります(サービスが破棄される前に次のstartService()
呼び出しを受けると、保留中の停止はキャンセルされます)。
次のフローチャートは、サービスのライフサイクルがどのように管理されるかを示しています。変数 counter は、バインドされたクライアントの数を表します。
フォアグラウンド サービスによる MediaStyle 通知の使用
再生中のサービスは、フォアグラウンドで実行されるようにします。これにより、そのサービスが有用な機能を実行中であり、利用できるメモリが少ない場合でも強制終了すべきではないことを、システムに対して通知できます。フォアグラウンド サービスでは、ユーザーが状況を把握し、必要に応じて制御できるよう、通知を表示する必要があります。onPlay()
コールバックでは、サービスをフォアグラウンドに配置するようにします(ここでは「フォアグラウンド」を特別な意味で使用しています。ユーザーから見ると、再生はバックグラウンドのプレーヤーで行われており、「フォアグラウンド」にあるのは別のアプリですが、Android では、プロセス管理の観点から、このサービスはフォアグラウンドにあると見なされます)。
サービスがフォアグラウンドで実行される場合、(理想的には 1 つ以上のトランスポート コントロールを備えた)通知を表示する必要があります。通知には、セッションのメタデータからの有用な情報も含めるようにします。
通知を作成して表示するのは、プレーヤーで再生を開始するときです。これを行うのに最適な場所は、MediaSessionCompat.Callback.onPlay()
メソッド内です。
以下の例では、
NotificationCompat.MediaStyle
,
これはメディアアプリ用に設計されていますメタデータとトランスポート コントロールを表示する通知の作成方法を示しています。このコンビニエンス メソッド
getController()
メディア セッションから直接メディア コントローラを作成できます。
Kotlin
// Given a media session and its context (usually the component containing the session) // Create a NotificationCompat.Builder // Get the session's metadata val controller = mediaSession.controller val mediaMetadata = controller.metadata val description = mediaMetadata.description val builder = NotificationCompat.Builder(context, channelId).apply { // Add the metadata for the currently playing track setContentTitle(description.title) setContentText(description.subtitle) setSubText(description.description) setLargeIcon(description.iconBitmap) // Enable launching the player by clicking the notification setContentIntent(controller.sessionActivity) // Stop the service when the notification is swiped away setDeleteIntent( MediaButtonReceiver.buildMediaButtonPendingIntent( context, PlaybackStateCompat.ACTION_STOP ) ) // Make the transport controls visible on the lockscreen setVisibility(NotificationCompat.VISIBILITY_PUBLIC) // Add an app icon and set its accent color // Be careful about the color setSmallIcon(R.drawable.notification_icon) color = ContextCompat.getColor(context, R.color.primaryDark) // Add a pause button addAction( NotificationCompat.Action( R.drawable.pause, getString(R.string.pause), MediaButtonReceiver.buildMediaButtonPendingIntent( context, PlaybackStateCompat.ACTION_PLAY_PAUSE ) ) ) // Take advantage of MediaStyle features setStyle(android.support.v4.media.app.NotificationCompat.MediaStyle() .setMediaSession(mediaSession.sessionToken) .setShowActionsInCompactView(0) // Add a cancel button .setShowCancelButton(true) .setCancelButtonIntent( MediaButtonReceiver.buildMediaButtonPendingIntent( context, PlaybackStateCompat.ACTION_STOP ) ) ) } // Display the notification and place the service in the foreground startForeground(id, builder.build())
Java
// Given a media session and its context (usually the component containing the session) // Create a NotificationCompat.Builder // Get the session's metadata MediaControllerCompat controller = mediaSession.getController(); MediaMetadataCompat mediaMetadata = controller.getMetadata(); MediaDescriptionCompat description = mediaMetadata.getDescription(); NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId); builder // Add the metadata for the currently playing track .setContentTitle(description.getTitle()) .setContentText(description.getSubtitle()) .setSubText(description.getDescription()) .setLargeIcon(description.getIconBitmap()) // Enable launching the player by clicking the notification .setContentIntent(controller.getSessionActivity()) // Stop the service when the notification is swiped away .setDeleteIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_STOP)) // Make the transport controls visible on the lockscreen .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) // Add an app icon and set its accent color // Be careful about the color .setSmallIcon(R.drawable.notification_icon) .setColor(ContextCompat.getColor(context, R.color.primaryDark)) // Add a pause button .addAction(new NotificationCompat.Action( R.drawable.pause, getString(R.string.pause), MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_PLAY_PAUSE))) // Take advantage of MediaStyle features .setStyle(new MediaStyle() .setMediaSession(mediaSession.getSessionToken()) .setShowActionsInCompactView(0) // Add a cancel button .setShowCancelButton(true) .setCancelButtonIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_STOP))); // Display the notification and place the service in the foreground startForeground(id, builder.build());
MediaStyle 通知を使用する場合は、これらの通知の動作に NotificationCompat の設定:
setContentIntent()
を使用すると、通知を受信したときにサービスが自動的に開始されます。 クリックします。- 「信頼できない」状況
の場合、通知コンテンツのデフォルトの公開設定は
VISIBILITY_PRIVATE
です。おそらく最新の トランスポート コントロールを画面に表示し、VISIBILITY_PUBLIC
を選択できるようにしました。 - 背景色を設定する場合は注意が必要です。通常の通知では Android バージョン 5.0 以降の場合、色は 小さなアプリアイコンです。ただし、Android 7.0 より前の MediaStyle 通知では、 通知のバックグラウンド全体に使用されます。背景色はテストするようにしてください。経路 極端に明るい色や蛍光色にしないでください。
以下の設定は、NotificationCompat.MediaStyle を使用する場合にのみ利用可能です。
setMediaSession()
を使用する 通知をセッションに関連付けます。これにより、サードパーティ製アプリは コンパニオン デバイスを使用してセッションにアクセスし、操作することができます。setShowActionsInCompactView()
を使用すると、表示するアクションを最大 3 つ追加できます 通知の標準サイズの contentView を使用します。(この一時停止ボタンは 表示されます)。- Android 5.0(API レベル 21)以降では、通知をスワイプすると、
アプリがフォアグラウンドで実行されなくなった場合、できない
これは以前のバージョンでは確認できません。ユーザーが通知を削除して再生を停止できるようにする
Android 5.0(API レベル 21)よりも前の場合は、画面の右上にキャンセル ボタンを追加できます。
setShowCancelButton(true)
とsetCancelButtonIntent()
を呼び出して通知を受け取ります。
一時停止ボタンとキャンセル ボタンを追加する場合は、
追加できます。メソッド MediaButtonReceiver.buildMediaButtonPendingIntent()
は、
PlaybackState アクションを PendingIntent に代入しています。