MediaSession 简介
本文紧张是先容下MediaSession,联合framework源码例子,末了看怎样利用MediaSession 来监听A2DP的播放活动
MediaSession 紧张是用来控制播放活动,如播放、停息等活动,不外这个控制活动是由别的一个进程来操纵的,举个例子,好比文件管理器进程在播放视频,此时你可以通过语音助手辨认语音停息、快进等下令,然后通过MediaSession将你的控制活动直接传输到文件管理器中来实现播放控制活动,就可以明白是跨进程通讯的一组接口。
API先容
Android reference doc MediaSession Android Developers
怎样实现一个MediaSession 服务端
Using a media session
Implement a media session
这里有一篇联合ExoPlayer 利用MediaSession的文档
Controlling media through MediaSession
AVRCP协媾和A2DP 的MediaSession 控制
AVRCP协议的全称是音视频远端控制协议,联合MediaSession框架可以或许很容易的实现音视频的播放控制,利用时我们只需要实现好客户端的代码,服务端由framework 中buletooth 实现。
接下来看Android Framework中的实例,来明白对MediaSession 相干利用和逻辑,这边是Android P源码,其相干的服务 A2dpMediaBrowserService.java
服务端实现
1、MediaBrowseService
起首可以看 A2dpMediaBrowserService 这个类的实现了MediaBrowserService 服务端
packages/apps/Bluetooth/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java
摘下其解释
/** * Implements the MediaBrowserService interface to AVRCP and A2DP * * This service provides a means for external applications to access A2DP and AVRCP. * The applications are expected to use MediaBrowser (see API) and all the music * browsing/playback/metadata can be controlled via MediaBrowser and MediaController. * * The current behavior of MediaSession exposed by this service is as follows: * 1\. MediaSession is active (i.e. SystemUI and other overview UIs can see updates) when device is * connected and first starts playing. Before it starts playing we do not active the session. * 1.1 The session is active throughout the duration of connection. * 2\. The session is de-activated when the device disconnects. It will be connected again when (1) * happens. */public class A2dpMediaBrowserService extends MediaBrowserService { // ...}可以先看下 MediaBrowserService 实现,其继承自 Service
/** * Base class for media browser services. * <p> * Media browser services enable applications to browse media content provided by an application * and ask the application to start playing it. They may also be used to control content that * is already playing by way of a {@link MediaSession}. * </p> * * To extend this class, you must declare the service in your manifest file with * an intent filter with the {@link #SERVICE_INTERFACE} action. * * For example: * </p><pre> * <service android:name=".MyMediaBrowserService" * android:label="@string/service_name" > * <intent-filter> * <action android:name="android.media.browse.MediaBrowserService" /> * </intent-filter> * </service> * </pre> * */public abstract class MediaBrowserService extends Service { // ....}MediaBrowserService 抽象了 onGetRoot 和 onLoadChildren 接口出来,以是子类要实现这两个接口。
/** * Called to get the root information for browsing by a particular client. * <p> * The implementation should verify that the client package has permission * to access browse media information before returning the root id; it * should return null if the client is not allowed to access this * information. * </p> * * @param clientPackageName The package name of the application which is * requesting access to browse media. * @param clientUid The uid of the application which is requesting access to * browse media. * @param rootHints An optional bundle of service-specific arguments to send * to the media browser service when connecting and retrieving the * root id for browsing, or null if none. The contents of this * bundle may affect the information returned when browsing. * @return The {@link BrowserRoot} for accessing this app's content or null. * @see BrowserRoot#EXTRA_RECENT * @see BrowserRoot#EXTRA_OFFLINE * @see BrowserRoot#EXTRA_SUGGESTED */ public abstract @Nullable BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints); /** * Called to get information about the children of a media item. * <p> * Implementations must call {@link Result#sendResult result.sendResult} * with the list of children. If loading the children will be an expensive * operation that should be performed on another thread, * {@link Result#detach result.detach} may be called before returning from * this function, and then {@link Result#sendResult result.sendResult} * called when the loading is complete. * </p><p> * In case the media item does not have any children, call {@link Result#sendResult} * with an empty list. When the given {@code parentId} is invalid, implementations must * call {@link Result#sendResult result.sendResult} with {@code null}, which will invoke * {@link MediaBrowser.SubscriptionCallback#onError}. * </p> * * @param parentId The id of the parent media item whose children are to be * queried. * @param result The Result to send the list of children to. */ public abstract void onLoadChildren(@NonNull String parentId, @NonNull Result<List<MediaBrowser.MediaItem>> result);onGetRoot会在客户端发起毗连时被调用,而onLoadchildren会在客户端发起订阅哀求时被调用。onGetRoot方法的参数是clientPackageName和客户端的UID,我们可以针对这两个参数做一些限定,好比答应哪些客户端毗连之类的,假如不答应就直接返回一个null就行了,否则就返回一个新的BrowserRoot对象。函数onLoadChildren则是在客户端发起订阅哀求时被调用的,在这个函数中,我们扫描音乐文件,然后将其打包到一个list中,再返回给客户端。
回到 A2dpMediaBrowserService 服务中我们看到这两个函数的实现也是比力简单的,这个可以根据现实的业务需求来做
@Override public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) { return new BrowserRoot(BrowseTree.ROOT, null); } @Override public synchronized void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result) { if (mAvrcpCtrlSrvc == null) { Log.w(TAG, "AVRCP not yet connected."); result.sendResult(Collections.emptyList()); return; } if (DBG) Log.d(TAG, "onLoadChildren parentMediaId=" + parentMediaId); if (!mAvrcpCtrlSrvc.getChildren(mA2dpDevice, parentMediaId, 0, 0xff)) { result.sendResult(Collections.emptyList()); return; } // Since we are using this thread from a binder thread we should make sure that // we synchronize against other such asynchronous calls. synchronized (this) { mParentIdToRequestMap.put(parentMediaId, result); } result.detach(); }2、MediaSession
着实上面我们看到的MediaBrowseService着实是封装了一层逻辑的,内里紧张的实现照旧 MediaSession,那么接下来有须要看看 MediaSession 是怎么被利用的
照旧在 A2dpMediaBrowserService 中
@Override public void onCreate() { if (DBG) Log.d(TAG, "onCreate"); super.onCreate(); mSession = new MediaSession(this, TAG); setSessionToken(mSession.getSessionToken()); mSession.setCallback(mSessionCallbacks); mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); mSession.setActive(true); mAvrcpCommandQueue = new AvrcpCommandQueueHandler(Looper.getMainLooper(), this); refreshInitialPlayingState(); IntentFilter filter = new IntentFilter(); filter.addAction(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED); filter.addAction(AvrcpControllerService.ACTION_BROWSE_CONNECTION_STATE_CHANGED); filter.addAction(AvrcpControllerService.ACTION_TRACK_EVENT); filter.addAction(AvrcpControllerService.ACTION_FOLDER_LIST); registerReceiver(mBtReceiver, filter); synchronized (this) { mParentIdToRequestMap.clear(); } }这里初始化之后设置了 MediaSession.Callback, 当客户端MediaController发送指令时会回调到这里
// Media Session Stuff. private MediaSession.Callback mSessionCallbacks = new MediaSession.Callback() { @Override public void onPlay() { if (DBG) Log.d(TAG, "onPlay"); mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PLAY).sendToTarget(); // TRACK_EVENT should be fired eventually and the UI should be hence updated. } @Override public void onPause() { if (DBG) Log.d(TAG, "onPause"); mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE).sendToTarget(); // TRACK_EVENT should be fired eventually and the UI should be hence updated. } // .... }关于MediaSession 内部的实现可以看这篇文章
Android MediaSession简单分析 - 简书
客户端调用
1、MediaBrowser + MediaController
MediaBrowser 媒体欣赏器,用来毗连媒体服务MediaBrowserService和订阅数据,在注册的回调接口中我们就可以获取到Service的毗连状态、获取音乐数据。一样平常在客户端中创建
MediaController 媒体控制器,在客户端中工作,通过控制器向媒体服务器发送指令,然后通过MediaControllerCompat.Callback设置回调函数来继承服务端的状态。MediaController创建时需要受控端的配对令牌,因此需要在欣赏器毗连乐成后才举行
以是要监听哪个服务端需要在MediaBrowser毗连服务的地方转达包名和类名, 这里利用了 MediaSessionCompat
mBrowser = new MediaBrowserCompat(MainActivity.this, new ComponentName(packageName,className), connectionCallback,null);// 而且注册MediaControler connect callback,假如毗连乐成则回调这个是注册mediacontroller的回调,
mController = new MediaControllerCompat(MainActivity.this, mBrowser.getSessionToken());mController.registerCallback(controllerCallback);将上面的packageName和className改成avrcp协议对应的服务就行了,但是差别的android版本对应的协议包名类名不一样 android7-9
String package = "com.android.bluetooth";String class = "com.android.bluetooth.a2dpsink.mbs.A2dpMediaBrowserService"android10以后
String package = "com.android.bluetooth";String class = "com.android.bluetooth.avrcpcontroller.BluetoothMediaBrowserService"2、MediaSessionManager + MediaController
通过SessionManager获取全部激活的session,然后编译此中获取你想要的controller
mMediaCtrlCallback = new MediaControllerCallback(); mSessionManager = (MediaSessionManager) getSystemService(MEDIA_SESSION_SERVICE); mSessionListener = new SessionChangeListener(); if (mSessionManager != null) { mSessionManager.addOnActiveSessionsChangedListener(mSessionListener, null, mHandler); List<MediaController> controllers = mSessionManager.getActiveSessions(null); for (int i = 0; i < controllers.size(); i++) { MediaController controller = (MediaController) controllers.get(i); if ((getMediaControllerTag(controller).contains(A2DP_MBS_TAG))) { setCurrentMediaController(controller); } } }需要上述代码中TAG名需要对上,这里看的源码是Android P,AVRCP服务端中注册的TAG是
A2dpMediaBrowserService
看MediaController 中的方法
/** * Get the session owner's package name. * * @return The package name of of the session owner. */ public String getPackageName() { if (mPackageName == null) { try { mPackageName = mSessionBinder.getPackageName(); } catch (RemoteException e) { Log.d(TAG, "Dead object in getPackageName.", e); } } return mPackageName; } /** * Get the session's tag for debugging purposes. * * @return The session's tag. * @hide */ public String getTag() { if (mTag == null) { try { mTag = mSessionBinder.getTag(); } catch (RemoteException e) { Log.d(TAG, "Dead object in getTag.", e); } } return mTag; }然后再向对应的controller 注册 callback来利用即可
mMediaController.registerCallback(mMediaCtrlCallback);mMediaCtrlCallback 实现抽象接口类如下,可以监听到播放状态和多媒体信息变革的修改
private class MediaControllerCallback extends MediaController.Callback { @Override public void onPlaybackStateChanged(PlaybackState state) { } @Override public void onMetadataChanged(MediaMetadata metadata) { } } |