【Audio&Video】构建媒体浏览器服务(6)

您的应用必须MediaBrowserService在其清单中声明带有意图过滤器。你可以选择你自己的服务名称; 在以下示例中,它是“MediaPlaybackService”。

<service android:name=".MediaPlaybackService">
  <intent-filter>
    <action android:name="android.media.browse.MediaBrowserService" />
  </intent-filter>
</service>

注意:推荐的实现MediaBrowserService 是MediaBrowserServiceCompat。这是在media-compat支持库中定义的 。在此页面中,术语“MediaBrowserService”指的是MediaBrowserServiceCompat。的一个实例。

初始化媒体会话


当服务收到onCreate()生命周期回调方法时,它应该执行这些步骤:

创建并初始化媒体会话
设置媒体会话回调
设置媒体会话令牌
onCreate()下面的代码演示了这些步骤:

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 mMediaSession;
    private PlaybackStateCompat.Builder mStateBuilder;

    @Override
    public void onCreate() {
        super.onCreate();

        // Create a MediaSessionCompat
        mMediaSession = new MediaSessionCompat(context, LOG_TAG);

        // Enable callbacks from MediaButtons and TransportControls
        mMediaSession.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
        mStateBuilder = new PlaybackStateCompat.Builder()
                            .setActions(
                                PlaybackStateCompat.ACTION_PLAY |
                                PlaybackStateCompat.ACTION_PLAY_PAUSE);
        mMediaSession.setPlaybackState(mStateBuilder.build());

        // MySessionCallback() has methods that handle callbacks from a media controller
        mMediaSession.setCallback(new MySessionCallback());

        // Set the session's token so that client activities can communicate with it.
        setSessionToken(mMediaSession.getSessionToken());
    }
}

管理客户端连接


A MediaBrowserService有两种处理客户端连接的方法: onGetRoot()控制对服务的访问,并 onLoadChildren() 提供客户端构建和显示MediaBrowserService内容层次结构菜单的能力。

用控制客户端连接 onGetRoot()
该onGetRoot()方法返回内容层次结构的根节点。如果该方法返回null,则连接被拒绝。

要允许客户端连接到您的服务并浏览其媒体内容,onGetRoot()必须返回一个非空BrowserRoot,它是一个表示您的内容层次结构的根ID。

为了允许客户端在不浏览的情况下连接到MediaSession,onGetRoot()仍然必须返回一个非null的BrowserRoot,但根ID应该代表一个空的内容层次结构。

典型的实现onGetRoot()可能如下所示:

@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 hierachy
        // so onLoadChildren returns nothing. This disables the ability to browse for content.
        return new BrowserRoot(MY_EMPTY_MEDIA_ROOT_ID, null);
    }
}

在某些情况下,您可能需要实施白/黑名单模式来控制连接。有关白名单的示例,请参阅Universal Android Music Player示例应用程序中的PackageValidator类。

注意:您应该考虑提供不同的内容层次结构,具体取决于进行查询的客户端类型。特别是,Android Auto会限制用户与音频应用进行交互的方式。有关更多信息,请参阅播放自动音频。您可以查看clientPackageName连接时确定客户端类型,并BrowserRoot根据客户端(或rootHints 任何)返回不同的值。

与内容沟通 onLoadChildren()
客户端连接后,它可以遍历内容层次结构,方法是重复调用MediaBrowserCompat.subscribe()以构建UI的本地表示形式。该subscribe()方法将回调发送onLoadChildren()给服务,该服务返回MediaBrowser.MediaItem对象列表。

每个MediaItem都有一个唯一的ID字符串,这是一个不透明的标记。当客户想要打开一个子菜单或播放一个项目时,它会通过该ID。您的服务负责将ID与适当的菜单节点或内容项目相关联。

一个简单的实现onLoadChildren()可能看起来像这样:

@Override
public void onLoadChildren(final String parentMediaId,
    final Result<List<MediaItem>> result) {

    //  Browsing not allowed
    if (TextUtils.equals(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);
}

注意:MediaItem由MediaBrowserService传递的对象不应包含图标位图。 当您为每个项目构建时,请使用Uri调用 。 setIconUri()MediaDescription

有关如何实现的示例onLoadChildren(),请参阅MediaBrowserService和Universal Android音乐播放器示例应用程序。

媒体浏览器服务生命周期


Android 服务的行为取决于它是启动还是绑定到一个或多个客户端。创建服务后,可以启动,绑定或两者。在所有这些州中,它都是功能齐全的,可以执行其设计的工作。不同的是服务将存在多长时间。绑定的服务在所有绑定的客户端解除绑定之后才被销毁。已启动的服务可以明确停止并销毁(假设它不再绑定到任何客户端)。

当MediaBrowser另一个活动中的一个运行连接到一个活动时MediaBrowserService,它将活动绑定到服务,使服务绑定(但未启动)。这个默认行为是内置到MediaBrowserServiceCompat类中的。

当所有客户端解除绑定时,仅绑定(并且未启动)的服务会被销毁。如果您的UI活动此时断开连接,则该服务将被销毁。如果您还没有播放任何音乐,这不是问题。但是,当开始播放时,用户可能希望即使在切换应用程序后也能继续收听。当您解除绑定UI以与其他应用程序一起工作时,您不想销毁播放器。

出于这个原因,您需要确保服务在通过调用开始播放时开始startService()。无论是否绑定,启动的服务都必须明确停止。这可确保即使控制UI活动解除绑定,您的播放器也能继续执行。

要停止启动的服务,请致电Context.stopService()或stopSelf()。系统会尽快停止并销毁服务。但是,如果一个或多个客户端仍然绑定到该服务,则停止该服务的呼叫将被推迟,直到其所有客户端解除绑定。

它的生命周期MediaBrowserService由它的创建方式,拥有它的客户数量以及从媒体会话回调接收到的呼叫来控制。总结:

  • 该服务在响应媒体按钮启动时或在活动绑定到该活动时(通过其连接后MediaBrowser)创建。
  • 媒体会话onPlay()回调应包含调用的代码startService()。这可确保服务启动并继续运行,即使MediaBrowser绑定到它的所有UI 活动都解除绑定。
  • 该onStop()回调应该调用stopSelf()。如果服务已启动,则停止该服务。另外,如果没有绑定的活动,服务就会被销毁。否则,该服务将保持绑定状态,直到其所有活动解除绑定。(如果startService()在服务销毁之前收到后续呼叫,则挂起的停止被取消。)

以下流程图演示了如何管理服务的生命周期。变量计数器跟踪绑定客户端的数量:

【Audio&Video】构建媒体浏览器服务(6)

将MediaStyle通知与前台服务一起使用


当一个服务正在播放时,它应该在前台运行。这让系统知道该服务正在执行有用的功能,并且如果系统内存不足,则不应该被终止。前台服务必须显示通知,以便用户知道该通知并可以选择对其进行控制。该onPlay()回调应该把服务的前景。(请注意,这是“前景”的特殊含义。尽管Android将过程管理视为前台服务,但对于用户来说,播放器在后台播放,而某些其他应用在“前台”可见屏幕。)

当一个服务在前台运行时,它必须显示一个通知,理想情况下使用一个或多个传输控件。通知还应包含会话元数据中的有用信息。

当玩家开始玩时建立并显示通知。做这件事的最好的地方是在这个MediaSessionCompat.Callback.onPlay()方法里面。

下面的例子使用了 NotificationCompat.MediaStyle为媒体应用程序设计的。它显示了如何构建显示元数据和传输控件的通知。便捷方法 getController() 允许您直接从媒体会话创建媒体控制器。

// 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);

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(this,
       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(this, R.color.primaryDark))

    // Add a pause button
    .addAction(new NotificationCompat.Action(
        R.drawable.pause, getString(R.string.pause),
        MediaButtonReceiver.buildMediaButtonPendingIntent(this,
            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(this,
           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分所示的动作。(这里指定了暂停按钮。)
在Android 5.0(API级别21)和更高版本中,一旦服务不再在前台运行,就可以刷新通知以停止播放器。您不能在早期版本中执行此操作。要允许用户在Android 5.0(API级别21)之前删除通知并停止播放,可以通过调用setShowCancelButton(true)和在通知的右上角添加取消按钮setCancelButtonIntent()。
当您添加暂停和取消按钮时,您需要一个PendingIntent附加到播放动作。该方法MediaButtonReceiver.buildMediaButtonPendingIntent()执行将PlaybackState操作转换为PendingIntent的工作。

联系我

QQ:94297366
微信打赏:https://pan.baidu.com/s/1dSBXk3eFZu3mAMkw3xu9KQ

公众号推荐:

【Audio&Video】构建媒体浏览器服务(6)

猜你喜欢

转载自blog.51cto.com/4789781/2129731