一:MediaSession的框架图
1:整体框架
2:音频应用的框架图
音频应用框架在MediaSession通用框架中加了MediaBroswer和MediaBrowserService,这篇主要根据音频应用的逻辑简单了解一下MediaSession的源码。
二:源码分析
1:创建媒体服务MediaBrowserService
MyMusicService,它继承MediaBrowserServiceCompat,
class MyMusicService : MediaBrowserServiceCompat() {
...
}
MediaBrowserServiceCompat
MediaBrowserServiceCompat继承service,它是个后台服务,
看onCreate中,根据不同的版本做了兼容,发现他们都是实现了MediaBrowserServiceImpl接口。 以及onBind方法返回的也是mImpl的onbind()。
public abstract class MediaBrowserServiceCompat extends Service {
private MediaBrowserServiceImpl mImpl;
@Override
public void onCreate() {
super.onCreate();
if (Build.VERSION.SDK_INT >= 28) {
mImpl = new MediaBrowserServiceImplApi28();
} else if (Build.VERSION.SDK_INT >= 26) {
mImpl = new MediaBrowserServiceImplApi26();
} else if (Build.VERSION.SDK_INT >= 23) {
mImpl = new MediaBrowserServiceImplApi23();
} else if (Build.VERSION.SDK_INT >= 21) {
mImpl = new MediaBrowserServiceImplApi21();
} else {
mImpl = new MediaBrowserServiceImplBase();
}
mImpl.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
return mImpl.onBind(intent);
}
}
MediaBrowserServiceImpl接口的内容
interface MediaBrowserServiceImpl {
void onCreate();
IBinder onBind(Intent intent);
void setSessionToken(MediaSessionCompat.Token token);
void notifyChildrenChanged(String parentId, Bundle options);
void notifyChildrenChanged(RemoteUserInfo remoteUserInfo, String parentId, Bundle options);
Bundle getBrowserRootHints();
RemoteUserInfo getCurrentBrowserInfo();
}
再看MediaBrowserServiceImplApi28类的实现,发现一步步到21才是最终的,
看onCreate中MediaBrowserServiceApi21类创建了MediaBrowserService,并调用它的onCreate,onbind一样,以及setSessionToken。
@RequiresApi(21)
class MediaBrowserServiceImplApi21 implements MediaBrowserServiceImpl {
final List<Bundle> mRootExtrasList = new ArrayList<>();
MediaBrowserService mServiceFwk;
Messenger mMessenger;
@Override
public void onCreate() {
mServiceFwk = new MediaBrowserServiceApi21(MediaBrowserServiceCompat.this);
mServiceFwk.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
return mServiceFwk.onBind(intent);
}
@Override
public void setSessionToken(final MediaSessionCompat.Token token) {
mHandler.postOrRun(new Runnable() {
@Override
public void run() {
if (!mRootExtrasList.isEmpty()) {
IMediaSession extraBinder = token.getExtraBinder();
if (extraBinder != null) {
for (Bundle rootExtras : mRootExtrasList) {
BundleCompat.putBinder(rootExtras, EXTRA_SESSION_BINDER,
extraBinder.asBinder());
}
}
mRootExtrasList.clear();
}
mServiceFwk.setSessionToken((MediaSession.Token) token.getToken());
}
});
}
MediaBrowserServiceApi21
MediaBrowserServiceApi21继承了MediaBrowserService,MediaBrowserService才是真正干活的,完成媒体服务的功能。
MediaBrowserService
看onCreate方法,它主要有ServiceBinder来工作,
@Override
public void onCreate() {
super.onCreate();
mBinder = new ServiceBinder();
}
@Override
public IBinder onBind(Intent intent) {
if (SERVICE_INTERFACE.equals(intent.getAction())) {
return mBinder;
}
return null;
}
看onBind,判断了aciton,也就是manifest注册service时需要加入的action
<service
android:name="com.ttjjttjj.mymediasession.shared.MyMusicService"
android:exported="true">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
ServiceBinder实现了IMediaBrowserService的aidl接口
private class ServiceBinder extends IMediaBrowserService.Stub {
}
IMediaBrowserService接口
16oneway interface IMediaBrowserService {
17 void connect(String pkg, in Bundle rootHints, IMediaBrowserServiceCallbacks callbacks);
18 void disconnect(IMediaBrowserServiceCallbacks callbacks);
19
20 void addSubscriptionDeprecated(String uri, IMediaBrowserServiceCallbacks callbacks);
21 void removeSubscriptionDeprecated(String uri, IMediaBrowserServiceCallbacks callbacks);
22
23 void getMediaItem(String uri, in ResultReceiver cb, IMediaBrowserServiceCallbacks callbacks);
24 void addSubscription(String uri, in IBinder token, in Bundle options,
25 IMediaBrowserServiceCallbacks callbacks);
26 void removeSubscription(String uri, in IBinder token, IMediaBrowserServiceCallbacks callbacks);
27}
其中主要方法是由MediaBrowserCompat来请求通信的,service的具体内容先不看,来看一下客户端浏览器的连接。
2:MediaBrowserCompat 客户端浏览器
看下图就是MediaBrowserCompat的创建和连接,还绑定服务,设置连接回调
MediaBrowser.connect 媒体浏览器连接
正在干活的是MediaBrowser.class, 可以看到其中启动和绑定了服务MediaBrowserService
MediaBrowserService.connect() 媒体服务触发连接
connect()调用后,就到了MediaBrowserService中的connect方法
private class ServiceBinder extends IMediaBrowserService.Stub {
@Override
public void connect(final String pkg, final Bundle rootHints,
final IMediaBrowserServiceCallbacks callbacks) {
mHandler.post(new Runnable() {
@Override
public void run() {
final IBinder b = callbacks.asBinder();
// Clear out the old subscriptions. We are getting new ones.
mConnections.remove(b);
final ConnectionRecord connection = new ConnectionRecord();
connection.pkg = pkg;
connection.pid = pid;
connection.uid = uid;
connection.rootHints = rootHints;
connection.callbacks = callbacks;
mCurConnection = connection;
connection.root = MediaBrowserService.this.onGetRoot(pkg, uid, rootHints);
mCurConnection = null;
// If they didn't return something, don't allow this client.
if (connection.root == null) {
Log.i(TAG, "No root for client " + pkg + " from service "
+ getClass().getName());
try {
callbacks.onConnectFailed();
} catch (RemoteException ex) {
Log.w(TAG, "Calling onConnectFailed() failed. Ignoring. "
+ "pkg=" + pkg);
}
} else {
try {
mConnections.put(b, connection);
b.linkToDeath(connection, 0);
if (mSession != null) {
callbacks.onConnect(connection.root.getRootId(),
mSession, connection.root.getExtras());
}
} catch (RemoteException ex) {
Log.w(TAG, "Calling onConnect() failed. Dropping client. "
+ "pkg=" + pkg);
mConnections.remove(b);
}
}
}
});
}
}
方法中主要是创建记录ConnectionRecord,记录MediaBrowserCompat的信息,调用了onGetRoot生成root,,然后回调给 MediaBrowserCompat,root是MediaBrowserCompat和MediaBrowserSrevice连接成功的信物,如果root为null就表示连接失败。
回调MediaBrowserCompat.ConnectionCallback()
这样就到了MediaBrowserCompat的回调接口connect中,
看到返回了服务端的root,然后进行订阅,以及订阅回调,其中可以返回初始的数据给controller
然后创建MediaControllerCompat,可以看到参数中,传入了mMediaBrowserCompat.sessionToken
MediaBrowserCompat返回的seesionToken,是连接上MediaBrowserServiceCompat,然后MediaBrowserServiceCompat中创建了MediaSession,返回给了客户端的控制器MediaControllerCompat,sessionToken是MediaController和MediaSession关联在一起的。 然后看看MediaSession。
3:MediaSessionCompat 媒体会话
下图可以看到,MediaSession是在MediaBrowserServiceCompat中创建,然后传出token给service,(上面MediaBrowserCompat的token就是service返回的,然后MediaController通过token和MediaSeesion连接,如果是视频应用token就不需要service返回了,直接传给MediaController就可以了)。
另外设置了MediaSession的回调接口,它触发是由MediaController的请求触发的。
MediaSessionCompat
进入MediaSessionCompat -》MediaSessionImplApi28-》MediaSessionImplApi21,发现实现的是 MediaSeesion和Token的对象。
MediaSession
MediaSessionManager 管理,创建Session的对象, 参数mCbSub是CallBack
MediaSessionManager#MediaSessionService
MediaSessionManager 的实现是 MediaSessionService 的系统服务
/**
* System implementation of MediaSessionManager
*/
public class MediaSessionService extends SystemService implements Monitor {
看onCreate, SessionManagerImpl具体
public MediaSessionService(Context context) {
super(context);
mContext = context;
mSessionManagerImpl = new SessionManagerImpl();
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
mNotificationManager = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
}
SessionManagerImpl
#createSession#createSessionInternal#MediaSessionRecord
SessionManagerImpl中创建Session,以及创建了MediaSessionRecord
MediaSessionRecord
MediaSessionRecord中创建了ControllerStub,它是ISessionController的具体实现,完成MediaController的控制
MediaessionRecord中创建了SessionStub,它胡处理控制传来的操作,callback给之前的参数mCbSub(CallbackStub)
CallbackStub
CallbackStub中通过MediaSession分发之前的操作,
@Override
public void onPlay(String packageName, int pid, int uid) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchPlay(createRemoteUserInfo(packageName, pid, uid));
}
}
MediaSession#dispatchPlay#postToCallback#postToCallbackDelayed#mCallback
void dispatchPlay(RemoteUserInfo caller) {
postToCallback(caller, CallbackMessageHandler.MSG_PLAY, null, null);
}
mCallback#CallbackMessageHandler.post 这里就是handler发送消息
void postToCallbackDelayed(RemoteUserInfo caller, int what, Object obj, Bundle data,
long delay) {
synchronized (mLock) {
if (mCallback != null) {
mCallback.post(caller, what, obj, data, delay);
}
}
}
#CallbackMessageHandler.post处理消息,通过mCallback来回调,就是一开始MediaSessio设置的回调MediaSession.Callback。
ISession
mBinder为ISession的对象,可以看到源码,实现MediaSessionRecord#SessionStub
上面的接口,可以看到熟悉的,用于回调给mediaController。
void setMetadata(in MediaMetadata metadata);
void setPlaybackState(in PlaybackState state);
Token
创建的Token中传入了ISessionController,对应的是ISessionController
public static final class Token implements Parcelable {
private final int mUid;
private final ISessionController mBinder;
/**
* @hide
*/
public Token(int uid, ISessionController binder) {
mUid = uid;
mBinder = binder;
}
...
...
}
ISessionController
其中包含了熟悉的控制器的paly,stop等操作, 实现是MediaSessionRecord#ControllerStub
而这个token真是给客户端的MediaControllerCompat来操作的,下面看MediaControllerCompat。
4:MediaControllerCompat 创建客户端控制器
MediaControllerCompat创建
回调MediaControllerCompat.Callback()
其中的onPlaybackStateChanged,和onMetadataChanged, 会在上面的ISession调用setPlaybackState和setMetadata会触发他们。
mSession.setPlaybackState(mPlaybackStateCompat)
mSession.setMetadata(LocalDataHelper.transformPlayBeanByDuration(getPlayBean(),
mMediaPlayer.duration.toLong()))
transportControls执行操作
当UI上点击播放,暂停,下一个等操作是,直接通过MediaControllerCompat.transportControls的接口,看一下怎麽实现的。
/**
* MediaControllerCompat 操作UI按钮,执行pause, play等
*/
private fun handlerPlayEvent() {
when (viewModel.mMediaControllerCompat.playbackState.state) {
PlaybackStateCompat.STATE_PLAYING -> {
"onClick pause".logd()
viewModel.mMediaControllerCompat.transportControls.pause()
}
PlaybackStateCompat.STATE_PAUSED -> {
"onClick play".logd()
viewModel.mMediaControllerCompat.transportControls.play()
}
else -> {
"onClick other".logd()
viewModel.mMediaControllerCompat.transportControls.playFromSearch("", null)
}
}
}
TransportControls
看到其中的实现是mSessionBinder对象,对应的是ISessionController,就是Token中持有的,所以MediaController操作就调用了ISessionController的接口,然后看上面MediaSession创建了过程中一步一步到回调中MediaSession.Callback(), 这样就完成从UI到MediaController到MediaSession到CallBack的过程。
private final ISessionController mSessionBinder;
public final class TransportControls {
private static final String TAG = "TransportController";
private TransportControls() {
}
...
...
/**
* Request that the player start its playback at its current position.
*/
public void play() {
try {
mSessionBinder.play(mContext.getPackageName());
} catch (RemoteException e) {
Log.wtf(TAG, "Error calling play.", e);
}
}
public void playFromUri(Uri uri, Bundle extras) {
if (uri == null || Uri.EMPTY.equals(uri)) {
throw new IllegalArgumentException(
"You must specify a non-empty Uri for playFromUri.");
}
try {
mSessionBinder.playFromUri(mContext.getPackageName(), uri, extras);
} catch (RemoteException e) {
Log.wtf(TAG, "Error calling play(" + uri + ").", e);
}
}
/**
* Request that the player pause its playback and stay at its current
* position.
*/
public void pause() {
try {
mSessionBinder.pause(mContext.getPackageName());
} catch (RemoteException e) {
Log.wtf(TAG, "Error calling pause.", e);
}
}
public void stop() {
try {
mSessionBinder.stop(mContext.getPackageName());
} catch (RemoteException e) {
Log.wtf(TAG, "Error calling stop.", e);
}
}
public void seekTo(long pos) {
try {
mSessionBinder.seekTo(mContext.getPackageName(), pos);
} catch (RemoteException e) {
Log.wtf(TAG, "Error calling seekTo.", e);
}
}
/**
* Start fast forwarding. If playback is already fast forwarding this
* may increase the rate.
*/
public void fastForward() {
try {
mSessionBinder.fastForward(mContext.getPackageName());
} catch (RemoteException e) {
Log.wtf(TAG, "Error calling fastForward.", e);
}
}
/**
* Skip to the next item.
*/
public void skipToNext() {
try {
mSessionBinder.next(mContext.getPackageName());
} catch (RemoteException e) {
Log.wtf(TAG, "Error calling next.", e);
}
}
...
...
}
到这里音频应用的框架源码基本走了一遍,由于时间和知识有限,欢迎大家指正和留言。