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

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值