メディア ブラウザ サービスの構築

アプリでは、マニフェスト内の 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() を呼び出すコードを含める必要があります。これにより、サービスが開始状態となり、すべての UI MediaBrowser アクティビティとのバインドが解除された場合でも実行が継続されます。
  • 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 に代入しています。