Android音頻框架MediaSession接入

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回調方法接收受控端的狀態,從而根據相應的狀態刷新界面UIMediaController的創建需要受控端的配對令牌,因此需在瀏覽器成功連接服務的回調執行創建的操作.主要發送各種指令跟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

發佈了22 篇原創文章 · 獲贊 2 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章