Media3 MediaSession
MediaLibrarySession extends MediaSession
MediaSession 是一种会话,允许媒体应用程序向其他进程(包括Android框架和其他应用程序)公开其播放器功能、播放列表信息和当前正在播放的媒体项目。
MediaLibrarySession 增加了媒体内容浏览功能。
MediaLibrarySession.Callback extends MediaSession.Callback
MediaSession.Callback 中需要关注的有
- onPlaybackResumption(),和
MediaButtonReceiver一同实现恢复播放功能。
MediaLibrarySession.Callback 中需要关注的有
- onGetLibraryRoot(),用于在客户端请求内容树的根
MediaItem时。 - onGetChildren(),用于在客户端请求内容树中
MediaItem的子项时。 - onGetSearchResult(),用于在客户端请求内容树中针对给定查询的搜索结果时。
MediaLibraryService extends MediaSessionService extends Service
MediaSessionService 用于在后台播放音频。 使用 ArrayMap 管理访问过来的 MediaSession 。
MediaLibraryService 提供媒体内容库。
MediaBrowser extends MediaController
二者都是通过 Context#bindService() 连接对应服务。
使用 MediaSessionService 实现在后台播放音频。
使用 MediaSessionService 可以让 Google 助理、系统媒体控件、外围设备上的媒体按钮或 Wear OS 等配套设备等外部客户端发现您的服务、连接到该服务并控制播放,而根本无需访问应用的界面 activity。
支持多个客户端应用同时连接到同一个 MediaSessionService。
MediaSessionService 继承自 Service。
public abstract class MediaSessionService extends Service { }
需要关注的生命周期有
- onCreate
- onDestroy
分析 MediaSessionService 源码发现。
在 onCreate 中创建了 MediaSessionServiceStub。
在 onDestroy 中释放了 MediaSessionServiceStub。
MediaSessionServiceStub 是一个 AIDL 接口。
private static final class MediaSessionServiceStub extends IMediaSessionService.Stub { }
用于 MediaController 到 MediaSessionService 的通信,即客户端和 MediaSessionService 的进程间通信。
IMediaSessionService 中提供了客户端即 IMediaController 访问到本服务的接口。
import androidx.media3.session.IMediaController;
oneway interface IMediaSessionService {
void connect(IMediaController caller, in Bundle connectionRequest) = 3000;
}
查看 connect 的具体实现,就可以知道 MediaSessionServiceStub 是如何处理不同进程下的客户端了。
- IMediaController 为空时直接返回,不做任何处理。请求信息为空时,触发 IMediaController 的断开连接。
if (caller == null || connectionRequestBundle == null) {
SessionUtil.disconnectIMediaController(caller);
return;
}
- 解析请求信息出错时,即未按格式要求传递信息时,触发 IMediaController 的断开连接。
ConnectionRequest request;
try {
request = ConnectionRequest.fromBundle(connectionRequestBundle);
} catch (RuntimeException e) {
// Malformed call from potentially malicious controller.
Log.w(TAG, "Ignoring malformed Bundle for ConnectionRequest", e);
SessionUtil.disconnectIMediaController(caller);
return;
}
- 获取弱引用中的 MediaSessionService,若获取失败即 MediaSessionService 执行 onDestroy 周期后,触发 IMediaController 的断开连接。
@Nullable MediaSessionService mediaSessionService = serviceReference.get();
if (mediaSessionService == null) {
SessionUtil.disconnectIMediaController(caller);
return;
}
- 使用调用方即客户端的 pid、uid、包名 来区分不同的 用户信息。
int callingPid = Binder.getCallingPid();
int uid = Binder.getCallingUid();
long token = Binder.clearCallingIdentity();
int pid = (callingPid != 0) ? callingPid : request.pid;
MediaSessionManager.RemoteUserInfo remoteUserInfo =
new MediaSessionManager.RemoteUserInfo(request.packageName, pid, uid);
- 判断当前用户是否值得信任,即是否具有MEDIA_CONTENT_CONTROL权限或者STATUS_BAR_SERVICE权限,或者是系统应用,或者是该应用 enabled_notification_listeners。
boolean isTrusted =
MediaSessionManager.getSessionManager(mediaSessionService.getApplicationContext())
.isTrustedForMediaControl(remoteUserInfo);
- 将跨进程调用过来的客户端统一放入线程安全的 HashSet 中。
// private final Set<IMediaController> pendingControllers = Collections.synchronizedSet(new HashSet<>());
pendingControllers.add(caller);
- 通过 Handler 交给 MediaSessionService 的主线程逐一处理。
// private final Handler handler = new Handler(context.getMainLooper());
handler.post(()->{
// 处理其他进程调用过来的 IMediaController
})
- 处理来自MediaSessionService的控制器连接请求。
// 通过 synchronized (mutex) {return c.remove(o);} 保障移除时的线程安全。
pendingControllers.remove(caller);
boolean connected = false;
try {
@Nullable MediaSessionService service = serviceReference.get();
if (service == null) {
return;
}
// 调用方进程里的 控制器信息
ControllerInfo controllerInfo =
new ControllerInfo(
remoteUserInfo,
request.libraryVersion,
request.controllerInterfaceVersion,
isTrusted,
new MediaSessionStub.Controller2Cb(
caller, request.controllerInterfaceVersion),
request.connectionHints,
request.maxCommandsForMediaItems);
// 需子类具体实现,获取一个媒体会话。
@Nullable MediaSession session = service.onGetSession(controllerInfo);
if (session == null) {
return;
}
// 将有效的媒体会话统一放入ArrayMap中管理。
service.addSession(session);
// 处理来自MediaSessionService的控制器连接请求。
session.handleControllerConnectionFromService(caller, controllerInfo);
connected = true;
} catch (Exception e) {
// Don't propagate exception in service to the controller.
Log.w(TAG, "Failed to add a session to session service", e);
} finally {
if (!connected) {
SessionUtil.disconnectIMediaController(caller);
}
}