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