如何将媒体播放器应用分为媒体控制器(用于界面)和媒体会话(用于实际播放器)来解决音频app开发中遇到的后台播放,数据传输,播放控制等问题
首先看一下整体架构简图
和我们用浏览器访问网站的模式类似,先打开页面链接上MediaBrowserService服务,链接成功后通过MediaController来控制播放/暂停/上下一首,MediaSession来相应对应的控制回调,播放器的回调会通过MediaSession.setPlaybackState()更新给客户端.
如上:有四个核心类
MediaBrowser
媒体浏览器,用来连接MediaBrowserService和订阅数据,通过它的回调接口我们可以获取和Service的连接状态以及获取在Service中异步获取的音乐库数据。也就是我们的浏览器端.
MediaBrowserService
浏览器服务,提供onGetRoot(控制客户端媒体浏览器的连接请求,通过返回值决定是否允许该客户端连接服务)和onLoadChildren(媒体浏览器向Service发送数据订阅时调用,一般在这执行异步获取数据的操作,最后将数据发送至媒体浏览器的回调接口中)这两个抽象方法 同时MediaBrowserService还作为承载媒体播放器(如MediaPlayer、ExoPlayer等)和MediaSession的容器。也就是我们的音乐后台服务
MediaSession
媒体会话,即受控端,通过设置MediaSessionCompat.Callback回调来接收媒体控制器MediaController发送的指令,当收到指令时会触发Callback中各个指令对应的回调方法(回调方法中会执行播放器相应的操作,如播放、暂停等)。Session一般在Service.onCreate方法中创建,最后需调用setSessionToken方法设置用于和控制器配对的令牌并通知浏览器连接服务成功,主要通过MediaSession和客户端的MediaController交互
MediaController
媒体控制器,在客户端中开发者不仅可以使用控制器向Service中的受控端发送指令,还可以通过设置MediaControllerCompat.Callback回调方法接收受控端的状态,从而根据相应的状态刷新界面UI。MediaController的创建需要受控端的配对令牌,因此需在浏览器成功连接服务的回调执行创建的操作.主要发送各种指令跟MediaSession来交互.
在MediaBrowserServiceCompat的onCreate中来创建MediaSessionCompat
@Override public void onCreate() { super.onCreate(); // 创建新的MediaSessionCompat mSession = new MediaSessionCompat(this, "MusicService"); // 创建MediaSessionCallback mCallback = new MediaSessionCallback(); // 客户端的指令到达mCallback mSession.setCallback(mCallback); mSession.setFlags( MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); // 设置token setSessionToken(mSession.getSessionToken()); mMediaNotificationManager = new MediaNotificationManager(this); mPlayback = new MediaPlayerAdapter(this, new MediaPlayerListener()); Log.d(TAG, "onCreate: MusicService creating MediaSession, and MediaNotificationManager"); }
我们来看看MediaSessionCallback中的回调及主要方法
public class MediaSessionCallback extends MediaSessionCompat.Callback { /** * 客户端请求添加播放队列 * @param description */ @Override public void onAddQueueItem(MediaDescriptionCompat description) { mPlaylist.add(new MediaSessionCompat.QueueItem(description, description.hashCode())); mQueueIndex = (mQueueIndex == -1) ? 0 : mQueueIndex; mSession.setQueue(mPlaylist); } /** * 客户端请求移除播放队列 * @param description */ @Override public void onRemoveQueueItem(MediaDescriptionCompat description) { mPlaylist.remove(new MediaSessionCompat.QueueItem(description, description.hashCode())); mQueueIndex = (mPlaylist.isEmpty()) ? -1 : mQueueIndex; mSession.setQueue(mPlaylist); } @Override public void onPrepare() { if (mQueueIndex < 0 && mPlaylist.isEmpty()) { // Nothing to play. return; } final String mediaId = mPlaylist.get(mQueueIndex).getDescription().getMediaId(); mPreparedMedia = MusicLibrary.getMetadata(MusicService.this, mediaId); mSession.setMetadata(mPreparedMedia); if (!mSession.isActive()) { mSession.setActive(true); } } @Override public void onPlay() { if (!isReadyToPlay()) { // Nothing to play. return; } if (mPreparedMedia == null) { onPrepare(); } mPlayback.playFromMedia(mPreparedMedia); Log.d(TAG, "onPlayFromMediaId: MediaSession active"); } @Override public void onPause() { mPlayback.pause(); } @Override public void onStop() { mPlayback.stop(); mSession.setActive(false); } @Override public void onSkipToNext() { mQueueIndex = (++mQueueIndex % mPlaylist.size()); mPreparedMedia = null; onPlay(); } @Override public void onSkipToPrevious() { mQueueIndex = mQueueIndex > 0 ? mQueueIndex - 1 : mPlaylist.size() - 1; mPreparedMedia = null; onPlay(); } @Override public void onSeekTo(long pos) { mPlayback.seekTo(pos); } private boolean isReadyToPlay() { return (!mPlaylist.isEmpty()); } }
连接MediaBrowserServiceCompat
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ... mMediaBrowserHelper = new MediaBrowserConnection(this); mMediaBrowserHelper.registerCallback(new MediaBrowserListener()); } @Override public void onStart() { super.onStart(); mMediaBrowserHelper.onStart(); } public void onStart() { if (mMediaBrowser == null) { mMediaBrowser = new MediaBrowserCompat( mContext, new ComponentName(mContext, mMediaBrowserServiceClass), mMediaBrowserConnectionCallback, null); mMediaBrowser.connect(); } Log.d(TAG, "onStart: Creating MediaBrowser, and connecting"); }
界面端发送服务指令
// 播放 mMediaBrowserHelper.getTransportControls().play(); // 暂停 mMediaBrowserHelper.getTransportControls().pause(); // 上一首 mMediaBrowserHelper.getTransportControls().skipToPrevious(); // 下一首 mMediaBrowserHelper.getTransportControls().skipToNext();
监听服务的变化
/** * Implementation of the {@link MediaControllerCompat.Callback} methods we're interested in. ** Here would also be where one could override * {@code onQueueChanged(List
queue)} to get informed when items * are added or removed from the queue. We don't do this here in order to keep the UI * simple. */ private class MediaBrowserListener extends MediaControllerCompat.Callback { /** * 播放状态变化 * * @param playbackState */ @Override public void onPlaybackStateChanged(PlaybackStateCompat playbackState) { mIsPlaying = playbackState != null && playbackState.getState() == PlaybackStateCompat.STATE_PLAYING; mMediaControlsImage.setPressed(mIsPlaying); } /** * 声源变化 * @param mediaMetadata */ @Override public void onMetadataChanged(MediaMetadataCompat mediaMetadata) { if (mediaMetadata == null) { return; } mTitleTextView.setText( mediaMetadata.getString(MediaMetadataCompat.METADATA_KEY_TITLE)); mArtistTextView.setText( mediaMetadata.getString(MediaMetadataCompat.METADATA_KEY_ARTIST)); mAlbumArt.setImageBitmap(MusicLibrary.getAlbumBitmap( MainActivity.this, mediaMetadata.getString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID))); } /** * session销毁 */ @Override public void onSessionDestroyed() { super.onSessionDestroyed(); } /** * 队列变化 * @param queue */ @Override public void onQueueChanged(List queue) { super.onQueueChanged(queue); } }
客户端 | 服务端 |
---|---|
MediaBrowser.ConnectionCallback----连接结果回调 | public BrowserRoot onGetRoot----判断是否允许客户端连接) |
MediaBrowser.SubscriptionCallback----订阅信息回调 | public void onLoadChildren(...)----订阅信息处理并发送 |
MediaController.Callback——服务回调 | MediaSession——媒体回话,一般用于返回播放结果 |
MediaController.getTransportControls()——对服务端发送控制指令 | MediaSession.Callback----控制指令送达位置 |
参考博客:
1.https://www.jianshu.com/p/a6c2a3ed842d
2.https://blog.csdn.net/weixin_42229694/article/details/89315026