Android媒体应用(四)--构建音频应用程序
2017-09-20 10:20
525 查看
原文地址:https://developer.android.google.cn/guide/topics/media-apps/audio-app/building-an-audio-app.html
音频应用程序的首选架构是客户端/服务器设计。播放器及其media session 在MediaBrowserService中实现,UI、media controller 与MediaBrowser都在Android Activity中。
MediaBrowserService提供两个主要功能:
当您使用MediaBrowserService时,MediaBrowser的其他组件和应用程序可以发现您的服务,创建自己的media controller,连接到media session并控制播放器。这正是Android Wear和Android
Auto Applications获取到您的媒体应用程序的权限的原因。
它还提供可选的浏览API。应用程序不必使用此功能。浏览API允许客户端查询服务并构建其内容层次结构的表示,其可以表示播放列表,媒体库或其他类型的集合。
注意:像media session和media controller一样, media browser services
and media browsers 的推荐实现是MediaBrowserServiceCompat和MediaBrowserCompat,它们在media-compat支持库中定义。它们替换了在API 21中引入的MediaBrowserService和MediaBrowser类的早期版本。为简洁起见,术语“MediaBrowserService”和“MediaBrowser”分别指MediaBrowserServiceCompat和MediaBrowserCompat的实例。
您的应用程序必须在其清单中声明具有意图过滤器的MediaBrowserService。 您可以选择自己的服务名称; 在下面的例子中,它是“MediaPlaybackService”。
初始化media session
当服务收到onCreate()生命周期回调方法时,它应该执行以下步骤:
创建并初始化media session
设置media session回调
设置media session token
下面的onCreate()代码演示了以下步骤:
要允许客户端连接到您的服务并浏览其媒体内容,onGetRoot()必须返回一个非空的BrowserRoot,它是一个代表您的内容层次结构的根ID。
要允许客户端连接到MediaSession而不浏览,onGetRoot()仍然必须返回一个非空的BrowserRoot,但是根ID应该表示一个空的内容层次结构。
onGetRoot()的典型实现可能如下所示:
在某些情况下,您可能需要实施一个白名单/黑名单方案来控制连接。有关白名单的示例,请参阅通用Universal Android Music Player 的PackageValidator类。
注意:您应该考虑根据客户端进行查询的类型提供不同的内容层次结构。特别是,Android Auto限制用户如何与音频应用程序交互。有关详细信息,请参阅为自动播放音频。您可以在连接时查看clientPackageName以确定客户端类型,并根据客户端(或rootHints(如果有))返回不同的BrowserRoot。
每个MediaItem都有唯一的ID字符串,它是不透明的令牌。当客户端打开子菜单或播放项目时,会传递ID。service负责将ID与相应的菜单节点或内容项相关联。
onLoadChildren()的简单实现可能如下所示:
注意:MediaBrowserService传递的MediaItem对象不应包含图标位图。当为每个项目构建MediaDescription时,使用Uri代替调用setIconUri()。
有关如何实现onLoadChildren()的示例,请参阅MediaBrowserService和Universal
Android Music Player示例应用程序。
当在另一个Activity中运行的MediaBrowser连接到MediaBrowserService时,它将活动绑定到服务,使服务受限(但未启动)。此默认行为内置于MediaBrowserServiceCompat类中。
当service所有客户端解除绑定时,只有绑定(而不是启动)的servcie会被销毁。如果您的UI Activity在此时断开连接,则该service将被销毁。如果您还没有播放任何音乐,这不是问题。但是,播放开始时,即使切换应用程序,用户也可能希望继续聆听。当使用其他应用时,并不能销毁播放器。
因此,您需要确保通过调用startService()启动service。必须明确停止启动的服务,无论是否绑定。这样可以确保即使控制UI活动解除绑定,您的播放器也会继续执行。
要停止启动的服务,请调用Context.stopService()或stopSelf()。系统尽快停止并破坏服务。但是,如果一个或多个客户端仍然绑定到服务,则停止服务的呼叫将被延迟,直到其所有客户端解除绑定。
MediaBrowserService的生命周期由创建的方式控制,客户端的数量以及从媒体会话回调接收的呼叫。总结:
service是在响应媒体按钮启动时创建的,或者当活动绑定到媒体按钮(通过其MediaBrowser连接后)时创建。
media session 的onPlay()回调应该包括调用startService()的代码。即使在绑定到它的所有UI MediaBrowser活动解除绑定的情况下,也可以确保服务启动并继续运行。
onStop()回调应该调用stopSelf()。如果服务已启动,则会停止该服务。此外,如果没有Activity绑定service,service将被销毁。否则,service将保持绑定,直到其所有Activity解除绑定。 (如果在service销毁之前接收到后续的startService()调用,则取消挂起的停止。)
以下流程图演示了如何管理服务的生命周期。可变计数器跟踪绑定的客户端数量:
使用具有前台service的MediaStyle通知
当一个服务正在播放时,它应该在前台运行。这使系统知道服务正在执行有用的功能,如果系统内存不足,则不应该被杀死。前台服务必须显示通知,以便用户知道该通知,并且可以选择性地控制它。 onPlay()回调应该把服务放在前台。 (请注意,这里“前台”的含义有些特殊。虽然Android将Service置为前台视为进程管理的目的,但是对于用户而言,播放器正在后台播放,而其他应用程序在前台。)
当service在前台运行时,它必须显示一个通知,理想情况下是使用一个或多个传输控件。该通知还应包括会话元数据中的有用信息。
播放器开始播放时构建并显示通知。最好的方法是在MediaSessionCompat.Callback.onPlay()方法中。
以下示例使用NotificationCompat.MediaStyle,它专为媒体应用程序而设计。它显示了如何构建显示元数据和传输控件的通知。 getController()方便方法可以直接从media session创建 media controller 。
使用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()在通知的右上角添加取消按钮。
当您添加暂停和取消按钮时,您需要一个PendingIntent才能附加到播放动作。 MediaButtonReceiver.buildMediaButtonPendingIntent()方法将PlaybackState操作转换为PendingIntent。
service进行连接和通信。
要完成客户端/服务器设计,您必须构建一个包含您的UI代码的活动组件,一个关联的MediaController和一个MediaBrowser。
MediaBrowser执行两个重要功能:它连接到MediaBrowserService,连接后,将为您的UI创建MediaController。
注意:MediaBrowser的推荐实现是MediaBrowserCompat,它是在Media-Compat支持库中定义的。在这个页面中,术语“MediaBrowser”是指MediaBrowserCompat的一个实例。
连接到MediaBrowserService
创建客户端活动后,它将连接到MediaBrowserService。有一个握手和舞蹈涉及。修改活动的lifecyle回调如下:
onCreate()构造一个MediaBrowserCompat。以您定义的MediaBrowserService和MediaBrowserCompat.ConnectionCallback的名称传递。
onStart()连接到MediaBrowserService。 MediaBrowserCompat.ConnectionCallback的魔法来源于此。如果连接成功,onConnect()回调将创建媒体控制器,将其链接到媒体会话,将UI控件链接到MediaController,并注册控制器以接收来自媒体会议。
当您的活动停止时,onStop()将断开MediaBrowser并取消注册MediaController.Callback。
当您的活动构建MediaBrowserCompat时,必须创建一个ConnectionCallback的实例。 修改其onConnected()方法以从MediaBrowserService检索媒体会话令牌,并使用令牌创建MediaControllerCompat。
使用MediaControllerCompat.setMediaController()方便的方法来保存控制器的链接。 这样可以处理媒体按钮。 它还允许您在构建传输控件时调用MediaControllerCompat.getMediaController()来检索控制器。
以下代码示例显示如何修改onConnected()方法。
将您的UI连接到媒体控制器
在上面的ConnectionCallback示例代码中,包含一个调用buildTransportControls()来显示你的UI。 您需要为控制播放器的UI元素设置onClickListeners。 为每个选择适当的MediaControllerCompat.TransportControls方法。
你的代码看起来像这样,每个按钮都有一个onClickListener:
与媒体会话保持同步
UI应显示媒体会话的当前状态,如其PlaybackState和Metadata所描述的。 创建传输控件时,您可以获取会话的当前状态,将其显示在UI中,并根据状态及其可用操作启用和禁用传输控件。
要在每次其状态或元数据更改时从媒体会话接收回调,请使用以下两种方法定义MediaControllerCompat.Callback:
在构建传输控件时注册回调(请参阅buildTransportControls()方法),并在活动停止时取消注册(在活动的onStop()生命周期方法中)。
您的媒体会话回调在多个API中调用方法来控制播放器,管理音频焦点,以及与媒体会话和媒体浏览器服务进行通信。
以下是回调的示例框架:
注意:如果您使用必要的回调创建您的MediaSession,使用Google Assistant的人可以使用语音命令控制您的应用。 Google Assistant文档中说明了要求。
音频应用程序的首选架构是客户端/服务器设计。播放器及其media session 在MediaBrowserService中实现,UI、media controller 与MediaBrowser都在Android Activity中。
MediaBrowserService提供两个主要功能:
当您使用MediaBrowserService时,MediaBrowser的其他组件和应用程序可以发现您的服务,创建自己的media controller,连接到media session并控制播放器。这正是Android Wear和Android
Auto Applications获取到您的媒体应用程序的权限的原因。
它还提供可选的浏览API。应用程序不必使用此功能。浏览API允许客户端查询服务并构建其内容层次结构的表示,其可以表示播放列表,媒体库或其他类型的集合。
注意:像media session和media controller一样, media browser services
and media browsers 的推荐实现是MediaBrowserServiceCompat和MediaBrowserCompat,它们在media-compat支持库中定义。它们替换了在API 21中引入的MediaBrowserService和MediaBrowser类的早期版本。为简洁起见,术语“MediaBrowserService”和“MediaBrowser”分别指MediaBrowserServiceCompat和MediaBrowserCompat的实例。
1.构建Media Browser Service
如何创建包含media session的media browser service,管理客户端连接,并在播放音频时成为前台服务。您的应用程序必须在其清单中声明具有意图过滤器的MediaBrowserService。 您可以选择自己的服务名称; 在下面的例子中,它是“MediaPlaybackService”。
<service android:name=".MediaPlaybackService"> <intent-filter> <action android:name="android.media.browse.MediaBrowserService" /> </intent-filter> </service>注意:MediaBrowserService的推荐实现是MediaBrowserServiceCompat。 这在media-compat支持库中定义。 在此页面中,“MediaBrowserService”是指MediaBrowserServiceCompat的一个实例.
初始化media session
当服务收到onCreate()生命周期回调方法时,它应该执行以下步骤:
创建并初始化media session
设置media session回调
设置media session token
下面的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()); } }
管理客户端连接
MediaBrowserService有两种处理客户端连接的方法:onGetRoot()控制对服务的访问,而onLoadChildren()提供了客户端构建和显示MediaBrowserService内容层次结构菜单的功能。使用onGetRoot()控制客户端连接
onGetRoot()方法返回内容层次结构的根节点。 如果方法返回null,则拒绝连接。要允许客户端连接到您的服务并浏览其媒体内容,onGetRoot()必须返回一个非空的BrowserRoot,它是一个代表您的内容层次结构的根ID。
要允许客户端连接到MediaSession而不浏览,onGetRoot()仍然必须返回一个非空的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以确定客户端类型,并根据客户端(或rootHints(如果有))返回不同的BrowserRoot。
与onLoadChildren()通信内容
客户端连接后,可以通过重复调用MediaBrowserCompat.subscribe()来构建UI的本地表示,从而遍历内容层次。 subscribe()方法将回调的onLoadChildren()发送到service,该service返回MediaBrowser.MediaItem对象的列表。每个MediaItem都有唯一的ID字符串,它是不透明的令牌。当客户端打开子菜单或播放项目时,会传递ID。service负责将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); }
注意:MediaBrowserService传递的MediaItem对象不应包含图标位图。当为每个项目构建MediaDescription时,使用Uri代替调用setIconUri()。
有关如何实现onLoadChildren()的示例,请参阅MediaBrowserService和Universal
Android Music Player示例应用程序。
media browser service 生命周期
Android service的行为取决于是启动还是绑定到一个或多个客户端。创建service后,可以启动,绑定或同时用两种。在所有这些状态中,它都是功能齐全的,可以执行其设计的工作。不同的是service的将存在多长时间。绑定的service在所有绑定的客户端解除绑定之前不会被销毁。启动的service可以被显式地停止和销毁(假设它不再绑定到任何客户端)。当在另一个Activity中运行的MediaBrowser连接到MediaBrowserService时,它将活动绑定到服务,使服务受限(但未启动)。此默认行为内置于MediaBrowserServiceCompat类中。
当service所有客户端解除绑定时,只有绑定(而不是启动)的servcie会被销毁。如果您的UI Activity在此时断开连接,则该service将被销毁。如果您还没有播放任何音乐,这不是问题。但是,播放开始时,即使切换应用程序,用户也可能希望继续聆听。当使用其他应用时,并不能销毁播放器。
因此,您需要确保通过调用startService()启动service。必须明确停止启动的服务,无论是否绑定。这样可以确保即使控制UI活动解除绑定,您的播放器也会继续执行。
要停止启动的服务,请调用Context.stopService()或stopSelf()。系统尽快停止并破坏服务。但是,如果一个或多个客户端仍然绑定到服务,则停止服务的呼叫将被延迟,直到其所有客户端解除绑定。
MediaBrowserService的生命周期由创建的方式控制,客户端的数量以及从媒体会话回调接收的呼叫。总结:
service是在响应媒体按钮启动时创建的,或者当活动绑定到媒体按钮(通过其MediaBrowser连接后)时创建。
media session 的onPlay()回调应该包括调用startService()的代码。即使在绑定到它的所有UI MediaBrowser活动解除绑定的情况下,也可以确保服务启动并继续运行。
onStop()回调应该调用stopSelf()。如果服务已启动,则会停止该服务。此外,如果没有Activity绑定service,service将被销毁。否则,service将保持绑定,直到其所有Activity解除绑定。 (如果在service销毁之前接收到后续的startService()调用,则取消挂起的停止。)
以下流程图演示了如何管理服务的生命周期。可变计数器跟踪绑定的客户端数量:
使用具有前台service的MediaStyle通知
当一个服务正在播放时,它应该在前台运行。这使系统知道服务正在执行有用的功能,如果系统内存不足,则不应该被杀死。前台服务必须显示通知,以便用户知道该通知,并且可以选择性地控制它。 onPlay()回调应该把服务放在前台。 (请注意,这里“前台”的含义有些特殊。虽然Android将Service置为前台视为进程管理的目的,但是对于用户而言,播放器正在后台播放,而其他应用程序在前台。)
当service在前台运行时,它必须显示一个通知,理想情况下是使用一个或多个传输控件。该通知还应包括会话元数据中的有用信息。
播放器开始播放时构建并显示通知。最好的方法是在MediaSessionCompat.Callback.onPlay()方法中。
以下示例使用NotificationCompat.MediaStyle,它专为媒体应用程序而设计。它显示了如何构建显示元数据和传输控件的通知。 getController()方便方法可以直接从media session创建 media controller 。
// 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个操作以显示在通知的标准大小的contentView中。 (这里指定了暂停按钮。)
在Android 5.0(API级别21)及更高版本中,一旦服务不再在前台运行,您可以滑动通知来停止播放器。您不能在早期版本中执行此操作。要允许用户在Android 5.0(API级别21)之前删除通知并停止播放,您可以通过调用setShowCancelButton(true)和setCancelButtonIntent()在通知的右上角添加取消按钮。
当您添加暂停和取消按钮时,您需要一个PendingIntent才能附加到播放动作。 MediaButtonReceiver.buildMediaButtonPendingIntent()方法将PlaybackState操作转换为PendingIntent。
2.构建Media Browser客户端
如何创建包含UI和media controller的media browser客户端活动,并与media browserservice进行连接和通信。
要完成客户端/服务器设计,您必须构建一个包含您的UI代码的活动组件,一个关联的MediaController和一个MediaBrowser。
MediaBrowser执行两个重要功能:它连接到MediaBrowserService,连接后,将为您的UI创建MediaController。
注意:MediaBrowser的推荐实现是MediaBrowserCompat,它是在Media-Compat支持库中定义的。在这个页面中,术语“MediaBrowser”是指MediaBrowserCompat的一个实例。
连接到MediaBrowserService
创建客户端活动后,它将连接到MediaBrowserService。有一个握手和舞蹈涉及。修改活动的lifecyle回调如下:
onCreate()构造一个MediaBrowserCompat。以您定义的MediaBrowserService和MediaBrowserCompat.ConnectionCallback的名称传递。
onStart()连接到MediaBrowserService。 MediaBrowserCompat.ConnectionCallback的魔法来源于此。如果连接成功,onConnect()回调将创建媒体控制器,将其链接到媒体会话,将UI控件链接到MediaController,并注册控制器以接收来自媒体会议。
当您的活动停止时,onStop()将断开MediaBrowser并取消注册MediaController.Callback。
public class MediaPlayerActivity extends AppCompatActivity { private MediaBrowserCompat mMediaBrowser; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // ... // Create MediaBrowserServiceCompat mMediaBrowser = new MediaBrowserCompat(this, new ComponentName(this, MediaPlaybackService.class), mConnectionCallbacks, null); // optional Bundle } @Override public void onStart() { super.onStart(); mMediaBrowser.connect(); } @Override public void onStop() { super.onStop(); // (see "stay in sync with the MediaSession") if (MediaControllerCompat.getMediaController(MediaPlayerActivity.this) != null) { MediaControllerCompat.getMediaController(MediaPlayerActivity.this).unregisterCallback(controllerCallback); } mMediaBrowser.disconnect(); } }自定义MediaBrowserCompat.ConnectionCallback
当您的活动构建MediaBrowserCompat时,必须创建一个ConnectionCallback的实例。 修改其onConnected()方法以从MediaBrowserService检索媒体会话令牌,并使用令牌创建MediaControllerCompat。
使用MediaControllerCompat.setMediaController()方便的方法来保存控制器的链接。 这样可以处理媒体按钮。 它还允许您在构建传输控件时调用MediaControllerCompat.getMediaController()来检索控制器。
以下代码示例显示如何修改onConnected()方法。
private final MediaBrowserCompat.ConnectionCallback mConnectionCallbacks = new MediaBrowserCompat.ConnectionCallback() { @Override public void onConnected() { // Get the token for the MediaSession MediaSessionCompat.Token token = mMediaBrowser.getSessionToken(); // Create a MediaControllerCompat MediaControllerCompat mediaController = new MediaControllerCompat(MediaPlayerActivity.this, // Context token); // Save the controller MediaControllerCompat.setMediaController(MediaPlayerActivity.this, mediaController); // Finish building the UI buildTransportControls(); } @Override public void onConnectionSuspended() { // The Service has crashed. Disable transport controls until it automatically reconnects } @Override public void onConnectionFailed() { // The Service has refused our connection } };
将您的UI连接到媒体控制器
在上面的ConnectionCallback示例代码中,包含一个调用buildTransportControls()来显示你的UI。 您需要为控制播放器的UI元素设置onClickListeners。 为每个选择适当的MediaControllerCompat.TransportControls方法。
你的代码看起来像这样,每个按钮都有一个onClickListener:
void buildTransportControls() { // Grab the view for the play/pause button mPlayPause = (ImageView) findViewById(R.id.play_pause); // Attach a listener to the button mPlayPause.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // Since this is a play/pause button, you'll need to test the current state // and choose the action accordingly int pbState = MediaControllerCompat.getMediaController(MediaPlayerActivity.this).getPlaybackState().getState(); if (pbState == PlaybackStateCompat.STATE_PLAYING) { MediaControllerCompat.getMediaController(MediaPlayerActivity.this).getTransportControls().pause(); } else { MediaControllerCompat.getMediaController(MediaPlayerActivity.this).getTransportControls().play(); } }); MediaControllerCompat mediaController = MediaControllerCompat.getMediaController(MediaPlayerActivity.this); // Display the initial state MediaMetadataCompat metadata = mediaController.getMetadata(); PlaybackStateCompat pbState = mediaController.getPlaybackState(); // Register a Callback to stay in sync mediaController.registerCallback(controllerCallback); }TransportControls方法将回调发送到您的服务的媒体会话。 确保您已经为每个控件定义了相应的MediaSessionCompat.Callback方法。
与媒体会话保持同步
UI应显示媒体会话的当前状态,如其PlaybackState和Metadata所描述的。 创建传输控件时,您可以获取会话的当前状态,将其显示在UI中,并根据状态及其可用操作启用和禁用传输控件。
要在每次其状态或元数据更改时从媒体会话接收回调,请使用以下两种方法定义MediaControllerCompat.Callback:
MediaControllerCompat.Callback controllerCallback = new MediaControllerCompat.Callback() { @Override public void onMetadataChanged(MediaMetadataCompat metadata) {} @Override public void onPlaybackStateChanged(PlaybackStateCompat state) {} };
在构建传输控件时注册回调(请参阅buildTransportControls()方法),并在活动停止时取消注册(在活动的onStop()生命周期方法中)。
3.Media Session回调
介绍 media session回调方法如何管理 media session, media browser service以及其他应用程序组件,如通知和广播接收器。您的媒体会话回调在多个API中调用方法来控制播放器,管理音频焦点,以及与媒体会话和媒体浏览器服务进行通信。
onPlay() | onPause() | onStop() | |
Audio Focus | requestFocus()passing in your OnAudioFocusChangeListener. Always call requestFocus()first, proceed only if focus is granted. | abandonAudioFocus() | |
Service | startService() | stopSelf() | |
Media Session | setActive(true) - Update metadata and state | - Update metadata and state | setActive(false) - Update metadata and state |
Player Implementation | Start the player | Pause the player | Stop the player |
Becoming Noisy | Register your BroadcastReceiver | Unregister your BroadcastReceiver | |
Notifications | startForeground(notification) | stopForeground(false) | stopForeground(false) |
onPlay() | onPause() | onStop() | |
Audio Focus | requestFocus()passing in your OnAudioFocusChangeListener. Always call requestFocus()first, proceed only if focus is granted. | abandonAudioFocus() | |
Service | startService() | stopSelf() | |
Media Session | setActive(true) - Update metadata and state | - Update metadata and state | setActive(false) - Update metadata and state |
Player Implementation | Start the player | Pause the player | Stop the player |
Becoming Noisy | Register your BroadcastReceiver | Unregister your BroadcastReceiver | |
Notifications | startForeground(notification) | stopForeground(false) | stopForeground(false) |
private IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY); // Defined elsewhere... private AudioManager.OnAudioFocusChangeListener afChangeListener; private BecomingNoisyReceiver myNoisyAudioStreamReceiver = new BecomingNoisyReceiver(); private MediaStyleNotification myPlayerNotification; private MediaSessionCompat mediaSession; private MediaBrowserService service; private SomeKindOfPlayer player; MediaSessionCompat.Callback callback = new MediaSessionCompat.Callback() { @Override public void onPlay() { AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE); // Request audio focus for playback, this registers the afChangeListener int result = am.requestAudioFocus(afChangeListener, // Use the music stream. AudioManager.STREAM_MUSIC, // Request permanent focus. AudioManager.AUDIOFOCUS_GAIN); if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { // Start the service service.start(); // Set the session active (and update metadata and state) mediaSession.setActive(true); // start the player (custom call) player.start(); // Register BECOME_NOISY BroadcastReceiver registerReceiver(myNoisyAudioStreamReceiver, intentFilter); // Put the service in the foreground, post notification service.startForeground(myPlayerNotification); } } @Override public void onStop() { AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE); // Abandon audio focus am.abandonAudioFocus(afChangeListener); unregisterReceiver(myNoisyAudioStreamReceiver); // Start the service service.stop(self); // Set the session inactive (and update metadata and state) mediaSession.setActive(false); // stop the player (custom call) player.stop(); // Take the service out of the foreground service.stopForeground(false); } @Override public void onPause() { AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE); // Update metadata and state // pause the player (custom call) player.pause(); // unregister BECOME_NOISY BroadcastReceiver unregisterReceiver(myNoisyAudioStreamReceiver, intentFilter); // Take the service out of the foreground, retain the notification service.stopForeground(false); } }
注意:如果您使用必要的回调创建您的MediaSession,使用Google Assistant的人可以使用语音命令控制您的应用。 Google Assistant文档中说明了要求。
相关文章推荐
- Android媒体应用(五)--构建视频应用程序
- Android API Guide for Media Apps(三)—— 构建音频应用(Building an Audio App)
- 【Android应用开发技术:媒体开发】音频
- Android媒体应用(七)--处理音频输出的变化
- 在Windows Azure上为iOS、 Android 和 Windows Phone构建可扩展的移动应用程序
- Android中的forceStopPackage—应用中关闭其他应用程序
- Android开发入门之一--在Windows上构建Android应用开发环境( SDK Anroid 2.3 + eclipse3.6.2 + ADT-10.0.0)
- 【Android】实验6 在应用程序中播放音频和视频 截止提交报告时间2016.4.21
- 【Android开发】多媒体应用开发-使用MediaPlayer播放音频
- 用于构建高级媒体应用程序的工具
- 基本的引导:用MVP构建你的Android应用架构part1
- Android 应用开发笔记 - 应用程序设计基础
- Android应用构建过程解析
- Android 应用程序窗体显示状态操作(requestWindowFeature()的应用)
- Android 应用程序窗体显示状态操作(requestWindowFeature()的应用)
- android应用框架构建------AppManager
- Android 应用程序窗体显示状态操作(requestWindowFeature()的应用)
- Android 应用程序窗体显示状态操作(requestWindowFeature()的应用)
- 使用Gradle构建Android应用
- 使用Gradle构建android应用