要完成客户端/服务器设计,您必须构建一个包含 UI 代码的活动组件、一个关联的 MediaController 和一个 MediaBrowser。
MediaBrowser 执行两个重要功能:它连接到 MediaBrowserService,并在连接后为您的 UI 创建 MediaController。
注意: MediaBrowser 的推荐实现是 MediaBrowserCompat
,它在 Media-Compat 支持库 中定义。在本页中,“MediaBrowser” 指的是 MediaBrowserCompat 的实例。
连接到 MediaBrowserService
创建客户端活动时,它会连接到 MediaBrowserService。这其中涉及一些握手和协调操作。请按如下所示修改活动的生命周期回调
onCreate()
构造一个 MediaBrowserCompat。传入您的 MediaBrowserService 的名称和您已定义的 MediaBrowserCompat.ConnectionCallback。onStart()
连接到 MediaBrowserService。这就是 MediaBrowserCompat.ConnectionCallback 的神奇之处。如果连接成功,onConnect() 回调将创建媒体控制器,将其链接到媒体会话,将您的 UI 控件链接到 MediaController,并注册控制器以接收来自媒体会话的回调。onResume()
设置音频流,以便您的应用响应设备上的音量控制。onStop()
在您的活动停止时断开您的 MediaBrowser 的连接并注销 MediaController.Callback。
Kotlin
class MediaPlayerActivity : AppCompatActivity() { private lateinit var mediaBrowser: MediaBrowserCompat override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ... // Create MediaBrowserServiceCompat mediaBrowser = MediaBrowserCompat( this, ComponentName(this, MediaPlaybackService::class.java), connectionCallbacks, null // optional Bundle ) } public override fun onStart() { super.onStart() mediaBrowser.connect() } public override fun onResume() { super.onResume() volumeControlStream = AudioManager.STREAM_MUSIC } public override fun onStop() { super.onStop() // (see "stay in sync with the MediaSession") MediaControllerCompat.getMediaController(this)?.unregisterCallback(controllerCallback) mediaBrowser.disconnect() } }
Java
public class MediaPlayerActivity extends AppCompatActivity { private MediaBrowserCompat mediaBrowser; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // ... // Create MediaBrowserServiceCompat mediaBrowser = new MediaBrowserCompat(this, new ComponentName(this, MediaPlaybackService.class), connectionCallbacks, null); // optional Bundle } @Override public void onStart() { super.onStart(); mediaBrowser.connect(); } @Override public void onResume() { super.onResume(); setVolumeControlStream(AudioManager.STREAM_MUSIC); } @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); } mediaBrowser.disconnect(); } }
自定义 MediaBrowserCompat.ConnectionCallback
当您的活动构造 MediaBrowserCompat 时,您必须创建一个 ConnectionCallback 实例。修改其 onConnected()
方法以从 MediaBrowserService 中检索媒体会话令牌,并使用该令牌创建 MediaControllerCompat。
使用便捷方法 MediaControllerCompat.setMediaController()
保存指向控制器的链接。这使得处理 媒体按钮 成为可能。它还允许您调用 MediaControllerCompat.getMediaController()
在构建传输控件时检索控制器。
以下代码示例演示如何修改 onConnected()
方法。
Kotlin
private val connectionCallbacks = object : MediaBrowserCompat.ConnectionCallback() { override fun onConnected() { // Get the token for the MediaSession mediaBrowser.sessionToken.also { token -> // Create a MediaControllerCompat val mediaController = MediaControllerCompat( this@MediaPlayerActivity, // Context token ) // Save the controller MediaControllerCompat.setMediaController(this@MediaPlayerActivity, mediaController) } // Finish building the UI buildTransportControls() } override fun onConnectionSuspended() { // The Service has crashed. Disable transport controls until it automatically reconnects } override fun onConnectionFailed() { // The Service has refused our connection } }
Java
private final MediaBrowserCompat.ConnectionCallback connectionCallbacks = new MediaBrowserCompat.ConnectionCallback() { @Override public void onConnected() { // Get the token for the MediaSession MediaSessionCompat.Token token = mediaBrowser.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
Kotlin
fun buildTransportControls() { val mediaController = MediaControllerCompat.getMediaController(this@MediaPlayerActivity) // Grab the view for the play/pause button playPause = findViewById<ImageView>(R.id.play_pause).apply { setOnClickListener { // Since this is a play/pause button, you'll need to test the current state // and choose the action accordingly val pbState = mediaController.playbackState.state if (pbState == PlaybackStateCompat.STATE_PLAYING) { mediaController.transportControls.pause() } else { mediaController.transportControls.play() } } } // Display the initial state val metadata = mediaController.metadata val pbState = mediaController.playbackState // Register a Callback to stay in sync mediaController.registerCallback(controllerCallback) }
Java
void buildTransportControls() { // Grab the view for the play/pause button playPause = (ImageView) findViewById(R.id.play_pause); // Attach a listener to the button playPause.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
,其中包含这两个方法
Kotlin
private var controllerCallback = object : MediaControllerCompat.Callback() { override fun onMetadataChanged(metadata: MediaMetadataCompat?) {} override fun onPlaybackStateChanged(state: PlaybackStateCompat?) {} }
Java
MediaControllerCompat.Callback controllerCallback = new MediaControllerCompat.Callback() { @Override public void onMetadataChanged(MediaMetadataCompat metadata) {} @Override public void onPlaybackStateChanged(PlaybackStateCompat state) {} };
在构建传输控件时注册回调(参见 buildTransportControls()
方法),并在活动停止时注销它(在活动的 onStop()
生命周期方法中)。
媒体会话被销毁时断开连接
如果媒体会话变为无效,则会发出 onSessionDestroyed()
回调。发生这种情况时,会话在 MediaBrowserService
的生命周期内将无法再次运行。尽管与 MediaBrowser
相关的功能可能会继续工作,但用户无法查看或控制来自已销毁媒体会话的播放,这可能会降低应用程序的价值。
因此,当会话被销毁时,您必须通过调用 disconnect()
从 MediaBrowserService
断开连接。这确保浏览器服务没有绑定客户端,并且 可以被操作系统销毁。如果您以后需要重新连接到 MediaBrowserService
(例如,如果您的应用程序想要保持与媒体应用程序的持久连接),请创建一个 MediaBrowser
的新实例,而不是重用旧实例。
以下代码片段演示了一个回调实现,该实现会在媒体会话被销毁时从浏览器服务断开连接
Kotlin
private var controllerCallback = object : MediaControllerCompat.Callback() { override fun onSessionDestroyed() { mediaBrowser.disconnect() // maybe schedule a reconnection using a new MediaBrowser instance } }
Java
MediaControllerCompat.Callback controllerCallback = new MediaControllerCompat.Callback() { @Override public void onSessionDestroyed() { mediaBrowser.disconnect(); // maybe schedule a reconnection using a new MediaBrowser instance } };