阅读 97

MediaSession框架的源码分析

一:MediaSession的框架图

1:整体框架

image.png

2:音频应用的框架图

image.png

音频应用框架在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才是真正干活的,完成媒体服务的功能。

image.png

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的创建和连接,还绑定服务,设置连接回调

image.png

MediaBrowser.connect 媒体浏览器连接

正在干活的是MediaBrowser.class, 可以看到其中启动和绑定了服务MediaBrowserService

image.png

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

image.png

然后创建MediaControllerCompat,可以看到参数中,传入了mMediaBrowserCompat.sessionToken

image.png

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就可以了)。

image.png

另外设置了MediaSession的回调接口,它触发是由MediaController的请求触发的。

MediaSessionCompat

进入MediaSessionCompat -》MediaSessionImplApi28-》MediaSessionImplApi21,发现实现的是 MediaSeesion和Token的对象。

image.png

MediaSession

MediaSessionManager 管理,创建Session的对象, 参数mCbSub是CallBack

image.png

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的控制

image.png

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。

image.png

ISession

mBinder为ISession的对象,可以看到源码,实现MediaSessionRecord#SessionStub

image.png

上面的接口,可以看到熟悉的,用于回调给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

image.png

而这个token真是给客户端的MediaControllerCompat来操作的,下面看MediaControllerCompat。

4:MediaControllerCompat 创建客户端控制器

MediaControllerCompat创建

image.png

回调MediaControllerCompat.Callback()

image.png

其中的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);
        }
    }

    ...
    ...
}复制代码

到这里音频应用的框架源码基本走了一遍,由于时间和知识有限,欢迎大家指正和留言。


作者:大力水手666
链接:https://juejin.cn/post/7018837198455717919


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐