原理分析

申明下述代码都是基于android10
上图
大体架构

createSession时序:
在这里插入图片描述

从类开始分析

MediaSession

MediaSession与其管理的播放器共存。您应该在媒体会话及其关联播放器所属 Activity 或服务的 onCreate() 方法中创建并初始化MediaSession

mPlaybackState = new PlaybackStateCompat.Builder()
          .setState(PlaybackStateCompat.STATE_NONE,0,1.0f)
          .build();
mediaSession = new MediaSessionCompat(this, LOG_TAG);
// Enable callbacks from MediaButtons and TransportControls
mediaSession.setPlaybackState(mPlaybackState);

请看下面这张媒体应用架构图
在这里插入图片描述
MediaSession的作用是控制播放状态的实现部分,从上面类图可以看到.MediaSession创建之后会有生成一个MediaSessionRecord对象,这个对象提供了很多Binder,SessionStub,ControllerStub,SessionCb
SessionCb 这个是给Service用的,MediaSessionService 是系统Service,拿MediaMutton来说,最终MediaSessionService会调用当前active的SessionCb接口,这个接口的结果是给SessionStub的,SessionStub是MediaSession持有的,可以这么说,MediaSession就是媒体播放器的服务端
再来看ControllerStub,这个是通过Token传递给MediaController,下面会单独对MediaController做解析,这地方先不说了

MediaController

MediaSession创建的时候会创建一个Token对象,这个对象很重要,他是MediaController能和MediaSession交互的基石

MediaController初始化

mController = new MediaControllerCompat(mContext,mBrowser.getSessionToken());
// Save the controller
MediaControllerCompat.setMediaController(getActivity(), mController);
mController.registerCallback(ControllerCallback);

我写的代码有MediaBrowser的,所有Token是从MediaBrowser获取到的,但是基本原理都是一样的

MediaBrowser

android提供这个类的目的为了提供一个类.在服务端有一个类专门去获取数据的,然后获取到之后传到界面显示,这个类就提供了这个功能
有个MediaBrowserService与之对应,通过连接Service,并且订阅,这两个就可以交互了,针对MediaBrowserService在下面讲
MediaBrowser初始化

mBrowser = new MediaBrowserCompat(getActivity(),
        new ComponentName(getActivity(), musicService.getClass()),//绑定浏览器服务
        BrowserConnectionCallback,//设置连接回调
        null
);

MediaBrowserService

请看官方架构图
使用MediaSessionService
activity侧初始化MediaBrowser,并且连接MediaBrowserService,连接时候,MediaBrowserService初始化,创建MediaSession以及Player,然后activity端订阅当前的Service,获取到MediaSession的token对象,创建MediaController,这样两边的MediaSession和MediaController就能交互了.中间的连接过程还有一些细节,比如获取内容信息等等
注册Service

<service android:name=".Service.MusicService">
      <intent-filter>
            <action android:name="android.media.browse.MediaBrowserService" />
      </intent-filter>
</service>

MediaBrowserService他也是继承Service的,具有和Service一样的功能,也能bind,也能start,但是他有一些特殊方法,最重要的两个必须实现的方法

public BrowserRoot onGetRoot(@NonNull String clientPackageName,
                                 int clientUid, @Nullable Bundle rootHints) {
    return  new BrowserRoot(MY_MEDIA_ROOT_ID, null);
}

如果想客户端成功连接这个Service,必须在onGetRoot方法中返回一个BrowserRoot对象,并且不能有耗时操作

public void onLoadChildren(@NonNull String parentId,
                               @NonNull Result<List<MediaBrowserCompat.MediaItem>> result) {
    if(mCursor != null) {
        while (mCursor.moveToNext()) {
             MediaMetadataCompat metadata = new MediaMetadataCompat.Builder()
                   .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, "" + mCursor.getLong(1))
                   .putString(MediaMetadataCompat.METADATA_KEY_TITLE, mCursor.getString(0))
                   .putLong(MediaMetadataCompat.METADATA_KEY_DURATION,mCursor.getLong(2))
                   .build();
             Log.v(LOG_TAG, "id" + mCursor.getLong(1) + " title:" + mCursor.getString(0));
             mediaItems.add(createMediaItem(metadata));
             mInfoMap.put(mediaItems.size()-1,metadata);
            }
        }
    result.sendResult(mediaItems);
}

这部分可以做耗时操作,返回一系列mediaItems对象,这个完成之后,如果你在客户端注册了
SubscriptionCallback这个callback会回调其onChildrenLoaded()方法,返回的信息就是上面onLoadChildren的时候获取到的信息,这样客户端也就有了相关的音乐信息了

MediaSessionManager

这个类是系统类,android系统的,他的主要作用就是能和MediaSessionSession交互,类似android manager用法都差不多,这里就不说了,直接看Service

MediaSessionService

这里面有个 SessionManagerImpl 类,他是继承ISessionManager.Stub的,MediaSessionManager其实就是和它交互的,直接看createSession方法

public ISession createSession(String packageName, ISessionCallback cb, String tag,
                Bundle sessionInfo, int userId) throws RemoteException {
        final int pid = Binder.getCallingPid();
        final int uid = Binder.getCallingUid();
        final long token = Binder.clearCallingIdentity();
        try {
            enforcePackageName(packageName, uid)
            int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
                   false /* allowAll */, true /* requireFull */, "createSession", packageName);
            if (cb == null) {
                throw new IllegalArgumentException("Controller callback cannot be nul");
            }
            return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag,
                    sessionInfo).getSessionBinder();
        } finally {
            Binder.restoreCallingIdentity(token);
        }
 }

这地方就直接到MediaSessionService去了,调用createSessionInternal方法,到加锁的createSessionLocked方法,

private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
            String callerPackageName, ISessionCallback cb, String tag, Bundle sessionInfo) {
	FullUserRecord user = getFullUserRecordLocked(userId);
	if (user == null) {
	    Log.w(TAG, "Request from invalid user: " +  userId + ", pkg=" + callerPackageName);
	    throw new RuntimeException("Session request from invalid user.");
	}
	
	final MediaSessionRecord session = new MediaSessionRecordz(callerPid, callerUid, userId,
	        callerPackageName, cb, tag, sessionInfo, this, mHandler.getLooper());
	try {
	    cb.asBinder().linkToDeath(session, 0);
	} catch (RemoteException e) {
	    throw new RuntimeException("Media Session owner died prematurely.", e);
	}
	
	user.mPriorityStack.addSession(session);
	mHandler.postSessionsChanged(userId);
	
	if (DEBUG) {
	    Log.d(TAG, "Created session for " + callerPackageName + " with tag " + tag);
	}
	return session;
}

这个方法里面就很重要了,首先它拿了一个FullUserRecord对象,这个里面有个 MediaSessionStack记录了所有的创建的MediaSessionRecord对象,每次创建完成之后都会
user.mPriorityStack.addSession(session);
add到对应userid的MediaSessionStack中,然后系统有啥信息,比如说mediakey派发,他就会拿到当前userid对应的stack去做事情
下面是mediakey派发

private void dispatchMediaKeyEventLocked(String packageName, int pid, int uid,
                boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
   MediaSessionRecord session = mCurrentFullUserRecord.getMediaButtonSessionLocked();
   if (session != null) {
       if (DEBUG_KEY_EVENT) {
           Log.d(TAG, "Sending " + keyEvent + " to " + session);
       }
       if (needWakeLock) {
           mKeyEventReceiver.aquireWakeLockLocked();
       }
       // If we don't need a wakelock use -1 as the id so we won't release it later.
       session.sendMediaButton(packageName, pid, uid, asSystemService, keyEvent,
               needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
               mKeyEventReceiver);
       if (mCurrentFullUserRecord.mCallback != null) {
           try {
               mCurrentFullUserRecord.mCallback.onMediaKeyEventDispatchedToMediaSession(
                       keyEvent, session.getSessionToken());
           } catch (RemoteException e) {
               Log.w(TAG, "Failed to send callback", e);
           }
       }
   } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null
           || mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
       if (needWakeLock) {
           mKeyEventReceiver.aquireWakeLockLocked();
       }
       Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
       mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
       mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
       // TODO: Find a way to also send PID/UID in secure way.
       String callerPackageName =
               (asSystemService) ? mContext.getPackageName() : packageName;
       mediaButtonIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callerPackageName);
       try {
           if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
               PendingIntent receiver = mCurrentFullUserRecord.mLastMediaButtonReceiver;
               if (DEBUG_KEY_EVENT) {
                   Log.d(TAG, "Sending " + keyEvent
                           + " to the last known PendingIntent " + receiver);
               }
               receiver.send(mContext,
                       needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
                       mediaButtonIntent, mKeyEventReceiver, mHandler);
               if (mCurrentFullUserRecord.mCallback != null) {
                   ComponentName componentName = mCurrentFullUserRecord
                           .mLastMediaButtonReceiver.getIntent().getComponent();
                   if (componentName != null) {
                       mCurrentFullUserRecord.mCallback
                               .onMediaKeyEventDispatchedToMediaButtonReceiver(
                                       keyEvent, componentName);
                   }
               }
           } else {
               ComponentName receiver =
                       mCurrentFullUserRecord.mRestoredMediaButtonReceiver;
               int componentType = mCurrentFullUserRecord
                       .mRestoredMediaButtonReceiverComponentType;
               UserHandle userHandle = UserHandle.of(mCurrentFullUserRecord
                       .mRestoredMediaButtonReceiverUserId);
               if (DEBUG_KEY_EVENT) {
                   Log.d(TAG, "Sending " + keyEvent + " to the restored intent "
                           + receiver + ", type=" + componentType);
               }
               mediaButtonIntent.setComponent(receiver);
               try {
                   switch (componentType) {
                       case FullUserRecord.COMPONENT_TYPE_ACTIVITY:
                           mContext.startActivityAsUser(mediaButtonIntent, userHandle);
                           break;
                       case FullUserRecord.COMPONENT_TYPE_SERVICE:
                           mContext.startForegroundServiceAsUser(mediaButtonIntent,
                                   userHandle);
                           break;
                       default:
                           // Legacy behavior for other cases.
                           mContext.sendBroadcastAsUser(mediaButtonIntent, userHandle);
                   }
               } catch (Exception e) {
                   Log.w(TAG, "Error sending media button to the restored intent "
                           + receiver + ", type=" + componentType, e);
               }
               if (mCurrentFullUserRecord.mCallback != null) {
                   mCurrentFullUserRecord.mCallback
                           .onMediaKeyEventDispatchedToMediaButtonReceiver(
                                   keyEvent, receiver);
               }
           }
       } catch (CanceledException e) {
           Log.i(TAG, "Error sending key event to media button receiver "
                   + mCurrentFullUserRecord.mLastMediaButtonReceiver, e);
       } catch (RemoteException e) {
           Log.w(TAG, "Failed to send callback", e);
       }
   }
}

系统都是通过这个stack去管理所有的MediaSession

MediaSessionRecord

MediaSessionRecord是createsession的最终产物,它里面信息很庞大,其实就是一些Binder服务端,

public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName,
            ISessionCallback cb, String tag, Bundle sessionInfo,
            MediaSessionService service, Looper handlerLooper) {
	mOwnerPid = ownerPid;
	mOwnerUid = ownerUid;
	mUserId = userId;
	mPackageName = ownerPackageName;
	mTag = tag;
	mSessionInfo = sessionInfo;
	mController = new ControllerStub();
	mSessionToken = new MediaSession.Token(mController);
	mSession = new SessionStub();
	mSessionCb = new SessionCb(cb);
	mService = service;
	mContext = mService.getContext();
	mHandler = new MessageHandler(handlerLooper);
	mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
	mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
	mAudioAttrs = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build();
}

看他的初始化过程,new了很多的对象,最终MediaSession和MediaController交互都是通过它来完成的.

MediaSession.Token

这个类的关键作用就是有一个ISessionController对象,MediaController要和MediaSession交互要通过它来完成.很重要,没有这个token,MediaSesson和MediaController就没有任何关系了

TransportControls

TransportControls和MediaSession的callback是对应的

TransportControls MediaSession.Callback
play() onPlay()
stop() onStop()
pause() onPause()
seekTo(long pos) onSeekTo(long)
fastForward() onFastForward()
rewind() onRewind()
skipToNext() onSkipToNext()
skipToPrevious() onSkipToPrevious()
skipToQueueItem(long) onSkipToQueueItem(long)
playFromMediaId(String,Bundle) onPlayFromMediaId(String,Bundle)
playFromSearch(String,Bundle) onPlayFromSearch(String,Bundle)
playFromUri(Uri,Bundle) onPlayFromUri(Uri,Bundle)
sendCustomAction(String,Bundle) onCustomAction(String,Bundle)
setRating(Rating rating) onSetRating(Rating)

这里意义就不讲了,上述的方法中很容易就能看出来啥意思,根绝需求,自己做选择
MediaController很明显就是给ui用的,TransportControls里面是它的具体方法,根据需求,给界面调用,服务端,或者说MediaSession端就能收到相应回调,做具体的播放控制

服务端回调给客户端

MediaSession MediaController.Callback
setMetadata(MediaMetadata) onMetadataChanged(MediaMetadata)
setPlaybackState(PlaybackState) onPlaybackStateChanged(PlaybackState)
setQueue(List MediaSession.QueueItem>) onQueueChanged(List MediaSession.QueueItem>)
setQueueTitle(CharSequence) onQueueTitleChanged(CharSequence)

还有别的方法具体参考Aandroid源生类
以上都是根据类来分析MediaSession的下面通过示例代码来分析

MediaSessionManager使用

MediaSessionService 存了当前所有的 MediaSession,不管是active还是非active的
MediaSessionManager提供了getActiveSessions,来获取当前的active的MediaSession相关信息
你也可以通过给MediaSessionManager 添加addOnActiveSessionsChangedListener这个回调来接收,当前MediaSession active状态改变,以及获取到当前的active MediaSession
另外 通过getActiveSessions 获取到MediaSession信息是有缓存的,如果当前的MediaSession没有PlayBackState状态改变,并且在播放,则可能导致,获取到的信息是落后的

代码示例

以MediaBrowser+MediaBrowserService 使用mediasession以及MediaPlayer创建一个播放器
只传客户端以及服务端的关键代码,其他没用的就不传了
Fragment代码

@Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.music_fragment, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        mAlbumArt = getActivity().findViewById(R.id.image);
        mTitleTextView = getActivity().findViewById(R.id.name);
        mContext = getContext();
        musicService = new MusicService();
        mBrowser = new MediaBrowserCompat(
                getActivity(),
                new ComponentName(getActivity(), musicService.getClass()),//绑定浏览器服务
                BrowserConnectionCallback,//设置连接回调
                null
        );

    }
    private void handlerPlayEvent(){
        switch (mController.getPlaybackState().getState()){
            case PlaybackStateCompat.STATE_PLAYING:
                mController.getTransportControls().pause();
                break;
            case PlaybackStateCompat.STATE_PAUSED:
                mController.getTransportControls().play();
                break;
            default:
                Bundle bundle = new Bundle();
                bundle.putInt("index",1);
                //默认播放index是1的歌曲,自己设置
                mController.getTransportControls().playFromMediaId(list.get(1).getMediaId(),bundle);
                break;
        }
    }

    @Override
    public void onStart() {
        super.onStart();
        //连接Service
        mBrowser.connect();
    }


    @Override
    public void onStop() {
        super.onStop();
        if (MediaControllerCompat.getMediaController(getActivity()) != null) {
            MediaControllerCompat.getMediaController(getActivity()).unregisterCallback(ControllerCallback);
        }
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        mBrowser.disconnect();
    }
	//设置连接时的回调
    private MediaBrowserCompat.ConnectionCallback BrowserConnectionCallback =
            new MediaBrowserCompat.ConnectionCallback() {
                @Override
                public void onConnected() {
                    if (mBrowser.isConnected()) {
                        String mediaId = mBrowser.getRoot();
                        //连接成功之后,subscribe,并set回调,接收成功通知
                        mBrowser.subscribe(mediaId, BrowserSubscriptionCallback);
                    }
                    //根绝传回的browser获取Token,创建MediaController
                    mController = new MediaControllerCompat(mContext,mBrowser.getSessionToken());
                    MediaControllerCompat.setMediaController(getActivity(), mController);
                    //给Controller设置回调
                    mController.registerCallback(ControllerCallback);
                    musicService.setController(mController);
                    buildTransportControls();
                }

                @Override
                public void onConnectionFailed() {
                    Log.e(TAG, "连接失败!");
                }
    };

    public void buildTransportControls(){
        mPlayButton = (Button) getActivity().findViewById(R.id.play);
        mPlayButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                handlerPlayEvent();
                MediaMetadataCompat metadata = mController.getMetadata();
            }
        });
        mNextButton = (Button) getActivity().findViewById(R.id.next);
        mNextButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mController.getTransportControls().skipToNext();
            }
        });
        mBar = (SeekBar) getActivity().findViewById(R.id.bar);
        mBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
//                mBar.setText(turnTime(i));
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                Log.d(TAG, "onStopTrackingTouch: " + seekBar.getProgress() + " max:" + seekBar.getMax()
                );
                mController.getTransportControls().seekTo(
                        seekBar.getProgress()*100/seekBar.getMax());
            }

        });
        list = new ArrayList<>();
        layoutManager = new LinearLayoutManager(mContext);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        adapter = new RecyclerViewAdapter(mContext,list);
        adapter.setOnItemClickListener(new RecyclerViewAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {
                Bundle bundle = new Bundle();
                bundle.putString("title",list.get(position).getDescription().getTitle().toString());
                bundle.putInt("index",position);
                mController.getTransportControls().playFromMediaId(list.get(position).getMediaId(),bundle);
            }

            @Override
            public void onItemLongClick(View view, int position) {

            }
        });

        recyclerView = (RecyclerView) getActivity().findViewById(R.id.recycler);
        recyclerView.setLayoutManager(layoutManager);
        recyclerView.setAdapter(adapter);
    }

    private final MediaControllerCompat.Callback ControllerCallback =
            new MediaControllerCompat.Callback() {
                @Override
                public void onPlaybackStateChanged(PlaybackStateCompat state) {
                    switch (state.getState()){
                        case PlaybackStateCompat.STATE_NONE://无任何状态
                            mTitleTextView.setText("none");
                            break;
                        case PlaybackStateCompat.STATE_PAUSED:
                            break;
                        case PlaybackStateCompat.STATE_PLAYING:
                            break;
                    }
                }
				//播放歌曲改变,服务端调用setmetadata之后客户端能收到该回调
                @Override
                public void onMetadataChanged(MediaMetadataCompat metadata) {
                    Log.d(TAG, "onMetadataChanged");
                    if (metadata == null) {
                        return;
                    }
                    mTitleTextView.setText(metadata.getDescription().getTitle());
                }

                @Override
                public void onSessionDestroyed() {
                    super.onSessionDestroyed();
                }

                @Override
                public void onQueueChanged(List<MediaSessionCompat.QueueItem> queue) {
                    super.onQueueChanged(queue);
                }
    };



    private final MediaBrowserCompat.SubscriptionCallback BrowserSubscriptionCallback =
            new MediaBrowserCompat.SubscriptionCallback() {
                @Override
                public void onChildrenLoaded(@NonNull String parentId,
                                             @NonNull List<MediaBrowserCompat.MediaItem> children) {
                    //children 即为Service发送回来的媒体数据集合
                    for (MediaBrowserCompat.MediaItem item : children) {
                        Log.v(TAG, item.getDescription().getTitle().toString());
                        list.add(item);
                    }
                    //在onChildrenLoaded可以执行刷新列表UI的操作
                    adapter.notifyDataSetChanged();
                }
    };

服务端MusicService代码

 @Override
    public void onCreate() {
        super.onCreate();
        HandlerThread thread = new HandlerThread("MusicService");
        thread.start();
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);

        Log.d(LOG_TAG,"oncreate");

        if(mContext == null) {
            mContext = this;
        }
        // 我用的是获取MediaProvider的数据,查询出来歌曲之后,
        mCursor = new MusicAcquire(mContext).getMusic();
		//创建mediaPlayer
        mMediaPlayer = new MediaPlayer();
        mMediaPlayer.setOnPreparedListener(PreparedListener);
        mMediaPlayer.setOnCompletionListener(CompletionListener);
        mMediaPlayer.setOnErrorListener(ErrorListener);

        mPlaybackState = new PlaybackStateCompat.Builder()
                .setState(PlaybackStateCompat.STATE_NONE,0,1.0f)
                .build();
        //创建mediasession
        mediaSession = new MediaSessionCompat(this, LOG_TAG);
        // Enable callbacks from MediaButtons and TransportControls
        mediaSession.setFlags(
                MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
                        MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
        mediaSession.setPlaybackState(mPlaybackState);
        callback = new MediaSessionCallback(this,mContext,mMediaPlayer,mPlaybackState,mediaSession);
        mediaSession.setCallback(callback);
		//这一步很重要,设置当前的mediasessiontoken
        setSessionToken(mediaSession.getSessionToken());
        audioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE);
        playbackAttributes = new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_GAME)
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .build();

        focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
                .setAudioAttributes(playbackAttributes)
                .setAcceptsDelayedFocusGain(true)
                .setOnAudioFocusChangeListener(FocusChangerListener, mServiceHandler)
                .build();
        // 请求audiofocus        
        int res = audioManager.requestAudioFocus(focusRequest);
        synchronized(focusLock) {
            ......
        }
        try {
            mMediaNotificationManager = new MediaNotificationManager(this);
        } catch (RemoteException e) {
            throw new IllegalStateException("Could not create a MediaNotificationManager", e);
        }
    }
    private AudioManager.OnAudioFocusChangeListener FocusChangerListener = new AudioManager.OnAudioFocusChangeListener(){
        // 根据官方建议,根据focuse状态,设置Player的状态
        @Override
        public void onAudioFocusChange(int focusChange) {
            Log.d(LOG_TAG, "onAudioFocusChange");
            switch (focusChange) {
                case AudioManager.AUDIOFOCUS_GAIN:
                    .....
                    break;
                case AudioManager.AUDIOFOCUS_LOSS:
                    .....
                    break;
                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                    .....
                    break;
                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                    mMediaPlayer.setVolume(0.3f, 0.3f);
                    break;
            }
        }
    };
    private MediaPlayer.OnPreparedListener PreparedListener = new MediaPlayer.OnPreparedListener() {
        @Override
        public void onPrepared(MediaPlayer mediaPlayer) {
            if(playbackNowAuthorized) {
                mMediaPlayer.start();
            }
            mPlaybackState = new PlaybackStateCompat.Builder()
                    .setState(PlaybackStateCompat.STATE_PLAYING,0,1.0f)
                    .build();
            mediaSession.setPlaybackState(mPlaybackState);
            callback.setPlaybackState(mPlaybackState);
            mMediaNotificationManager.startNotification();
            Log.d(LOG_TAG, "mPlaybackState:" + mPlaybackState.getPlaybackState());
        }
    } ;

    private MediaPlayer.OnCompletionListener CompletionListener = new MediaPlayer.OnCompletionListener() {
        @Override
        public void onCompletion(MediaPlayer mediaPlayer) {
            Log.d(LOG_TAG, "onCompletion");
            mPlaybackState = new PlaybackStateCompat.Builder()
                    .setState(PlaybackStateCompat.STATE_NONE,0,1.0f)
                    .build();
            mediaSession.setPlaybackState(mPlaybackState);
            mMediaPlayer.reset();
            callback.onSkipToNext();
        }
    };

    private MediaPlayer.OnErrorListener ErrorListener = new MediaPlayer.OnErrorListener() {
        @Override
        public boolean onError(MediaPlayer mediaPlayer, int i, int i1) {
            Log.e(LOG_TAG, "Error");
            Bundle bundle = new Bundle();
            bundle.putString("title",mediaItems.get(1).getDescription().getTitle().toString());
            Uri uri = MediaStore.Audio.Media.getContentUri("external");
            Uri uri2 = Uri.parse(
                    String.valueOf(uri.buildUpon().appendPath(String.valueOf(mediaItems.get(1).getMediaId()))));
            return false;
        }
    } ;

    @Nullable
    @Override
    public BrowserRoot onGetRoot(@NonNull String clientPackageName,
                                 int clientUid, @Nullable Bundle rootHints) {
        return  new BrowserRoot(MY_MEDIA_ROOT_ID, null);
    }

    @Override
    public void onLoadChildren(@NonNull String parentId,
                               @NonNull Result<List<MediaBrowserCompat.MediaItem>> result) {
        // 给result设置信息,这地方的结果会回调到客户端
        if(mCursor != null) {
            while (mCursor.moveToNext()) {
                MediaMetadataCompat metadata = new MediaMetadataCompat.Builder()
                        .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, "" + mCursor.getLong(1))
                        .putString(MediaMetadataCompat.METADATA_KEY_TITLE, mCursor.getString(0))
                        .putLong(MediaMetadataCompat.METADATA_KEY_DURATION,mCursor.getLong(2))
                        .build();
                Log.v(LOG_TAG, "id" + mCursor.getLong(1) + " title:" + mCursor.getString(0));
                mediaItems.add(createMediaItem(metadata));
                mInfoMap.put(mediaItems.size()-1,metadata);

            }
        }
        result.sendResult(mediaItems);
    }
    private MediaBrowserCompat.MediaItem createMediaItem(MediaMetadataCompat metadata){
        return new MediaBrowserCompat.MediaItem(
                metadata.getDescription(),
                MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
        );
    }

    @Override
    public void onDestroy() {
        Log.d(LOG_TAG, "music service ondestory");
        mMediaNotificationManager.stopNotification();
        mediaSession.release();
    }

服务端callback代码

public MediaSessionCallback(MusicService service,
                                Context context,
                                MediaPlayer player,
                                PlaybackStateCompat state,
                                MediaSessionCompat sessionCompat){
        musicService = service;
        mMediaPlayer = player;
        mPlaybackState = state;
        mSessionCompat = sessionCompat;
        mContext = context;
    }

    @Override
    public void onAddQueueItem(MediaDescriptionCompat description) {

    }

    @Override
    public void onRemoveQueueItem(MediaDescriptionCompat description) {

    }

    @Override
    public boolean onMediaButtonEvent(Intent mediaButtonEvent) {
        Log.d(TAG,"onMediaButtonEvent" + mediaButtonEvent.getParcelableExtra(Intent.EXTRA_KEY_EVENT).toString());
        super.onMediaButtonEvent(mediaButtonEvent);
        return false;
    }

    public void setPlaybackState(PlaybackStateCompat state) {
        mPlaybackState = state;
    }


    @Override
    public void onPrepare() {
        Log.d(TAG,"onPrepare");
    }

    @Override
    public void onPlay() {
        Log.e(TAG,"onPlay:" + mPlaybackState.getState());
        if(mPlaybackState.getState() == PlaybackStateCompat.STATE_PAUSED){
            mMediaPlayer.start();
            mPlaybackState = new PlaybackStateCompat.Builder()
                    .setState(PlaybackStateCompat.STATE_PLAYING,0,1.0f)
                    .build();
            mSessionCompat.setPlaybackState(mPlaybackState);
        }
        mSessionCompat.setActive(true);
    }

    @Override
    public void onPause() {
        Log.e(TAG,"onPause " + mPlaybackState.getState());
        if(mPlaybackState.getState() == PlaybackStateCompat.STATE_PLAYING){
            mMediaPlayer.pause();
            Log.e(TAG,"onPause");
            mPlaybackState = new PlaybackStateCompat.Builder()
                    .setState(PlaybackStateCompat.STATE_PAUSED,0,1.0f)
                    .build();
            mSessionCompat.setPlaybackState(mPlaybackState);
        }
        mSessionCompat.setActive(false);
    }

    @Override
    public void onStop() {
        mSessionCompat.setActive(false);
    }

    @Override
    public void onPlayFromUri(Uri uri, Bundle extras) {
        Log.e(TAG,"onPlayFromUri");
        try {
            switch (mPlaybackState.getState()){
                case PlaybackStateCompat.STATE_PLAYING:
                case PlaybackStateCompat.STATE_PAUSED:
                case PlaybackStateCompat.STATE_NONE:
                    mMediaPlayer.reset();
                    mMediaPlayer.setDataSource(mContext,uri);
                    mMediaPlayer.prepare();//准备同步
                    mSessionCompat.setMetadata(musicService.mInfoMap.get(index));
                    break;
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
    @Override
    public void onPlayFromSearch(String search, Bundle extras) {

    }

    @Override
    public void onPlayFromMediaId(String mediaId, Bundle extras) {
        if(extras != null) {
            index = extras.getInt("index");
        }
        Uri uri = rawToUri(Integer.valueOf(musicService.mediaItems.get(index).getMediaId()));
        onPlayFromUri(uri,null);
    }
    private Uri rawToUri(int id){
        Uri uri = MediaStore.Audio.Media.getContentUri("external");
        Uri uri2 = Uri.parse(
                uri.buildUpon().appendPath(String.valueOf(id))
                        .toString());
        return uri2;
    }

    @Override
    public void onSkipToNext() {
        if (index < musicService.mediaItems.size() - 1){
            index++;
        } else {
            index = 0;
        }
        Uri uri = rawToUri(Integer.valueOf(musicService.mediaItems.get(index).getMediaId()));
        onPlayFromUri(uri,null);
    }

    @Override
    public void onSkipToPrevious() {
        if (index > 0){
            index--;
        } else {
            index = musicService.mediaItems.size()-1;
        }
        Uri uri = rawToUri(Integer.valueOf(musicService.mediaItems.get(index).getMediaId()));
        onPlayFromUri(uri,null);
    }

    @Override
    public void onSeekTo(long pos) {
        long pose = musicService.mInfoMap.get(index).getLong(MediaMetadataCompat.METADATA_KEY_DURATION);
        mMediaPlayer.seekTo((int) (pose*pos)/100);
    }

notification代码这地方也不放了,以上就是 创建MediaBrowserService,使用MediaSession以及MediaController创建的播放器示例代码,仅供参考

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐