1.媒体应用架构概览
如何将媒体播放器应用分为媒体控制器(用于界面)和媒体会话(用于实际播放器)来解决音频app开发中遇到的后台播放,数据传输,播放控制等问题
2.使用
首先看一下整体架构简图

和我们用浏览器访问网站的模式类似,先打开页面链接上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来交互.
2.1服务端
在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());
}
}
2.2界面端
连接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.
* <p>
* Here would also be where one could override
* {@code onQueueChanged(List<MediaSessionCompat.QueueItem> 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<MediaSessionCompat.QueueItem> queue) {
super.onQueueChanged(queue);
}
}3.最后来对应一下主要的客户端和服务端的函数
| 客户端 | 服务端 |
|---|---|
| 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