前言
大家好,我是simple。我的理想是利用科技手段来解决生活中遇到的各种问题。
在媒体类应用(如音乐、播客、视频播放器)中,用户常常需要通过多种方式控制播放——可能是点击应用内的按钮,可能是按耳机线控,也可能是滑动系统通知栏。这些分散的控制指令如果处理不当,很容易出现状态混乱(比如“同时收到播放和暂停指令”),或者设备间状态不同步(比如手机显示“播放中”,但手表显示“已暂停”)。
这套基于AVSession的实现方案,核心就是解决这些问题:通过统一的媒体会话管理,让所有控制指令走同一个“入口”,确保播放状态在任何设备上都一致,同时高效管理资源,避免内存浪费。
核心功能与实现架构
整个方案由两个核心类协作完成:
-
AVSessionPlayerManager:负责创建和管理全局唯一的AVSession实例(单例模式)。媒体会话就像一个“指挥中心”,全局只能有一个,否则会出现“多头指挥”的混乱,这个类就是保证这一点的。
-
AVSessionPlayerController:处理具体的“指挥”工作——注册控制指令的监听(比如“播放”“暂停”)、设置歌曲信息(如歌名、歌手)、更新播放状态(如“正在播放”“已暂停”),以及最后释放资源。
完整实现流程
1. 初始化会话:创建“指挥中心”
使用AVSessionPlayerManager的createSession方法初始化会话。因为用了单例模式,第一次调用会创建会话并激活,后续调用直接返回已有的实例,避免重复创建导致冲突。
// 首次调用创建会话,后续调用直接返回已有实例
const session = await AVSessionPlayerManager.createSession(context);
2. 注册事件监听:搭建“指令接收线”
会话创建后,通过AVSessionPlayerController的registerEventListeners方法注册所有支持的控制指令(播放、暂停、下一首等)。这里用了一个事件映射表,把指令名和对应的处理函数关联起来,既清晰又方便管理。
注册后,不管是来自耳机的“暂停”,还是通知栏的“下一首”,都会通过这个“接收线”进入统一处理流程。
3. 设置媒体信息:告诉“指挥中心”播放内容
通过setMediaInfo方法,把当前播放的媒体信息(如歌名、封面)、播放队列(比如“播放列表”)传给会话。同时会初始化播放器,从队列中取第一首开始播放,并同步更新播放状态(比如“正在播放”“已收藏”)。
这样一来,系统或其他设备就能通过AVSession获取到完整的媒体信息(比如通知栏显示当前歌曲名)。
4. 处理控制指令:统一响应操作
当收到控制指令(比如“播放”)时,Controller会调用对应的处理方法(handlePlay),操作播放器执行实际动作(如avplayer.play()),并同步更新会话状态。
比如用户按了耳机的“播放键”,指令会触发handlePlay,播放器开始播放,同时会话状态更新为“播放中”——手机、手表、通知栏都会同步显示这个状态。
5. 销毁资源:退出时“清场”
当应用退出或切换场景时,调用destroy方法释放资源:先移除所有事件监听(避免“已退出但还在接收指令”),再释放播放器,最后销毁会话,确保没有内存泄漏。
关键代码解析
单例模式:保证会话唯一
AVSessionPlayerManager的核心设计是单例模式,通过静态属性session存储唯一实例,避免重复创建:
// AVSessionPlayerManager中的单例实现
static async createSession(context: Context): Promise<avSession.AVSession> {
// 已有会话直接返回,不重复创建
if (AVSessionPlayerManager.session) {
return AVSessionPlayerManager.session;
}
// 首次创建并激活会话
AVSessionPlayerManager.session = await avSession.createAVSession(context, 'audio_test', 'audio');
await AVSessionPlayerManager.session.activate();
return AVSessionPlayerManager.session;
}
这种设计从根源上避免了“多个会话同时存在”导致的冲突。
事件注册:统一管理指令监听
Controller的registerEventListeners方法用“事件-处理函数”映射表管理所有指令,既减少重复代码,又方便后续维护:
// 事件与处理函数的映射表
const eventHandlers: Record<SupportedEvents, (...args: any[]) => void> = {
play: () => this.handlePlay(),
pause: () => this.handlePause(),
stop: () => this.handleStop(),
// 其他指令...
};
// 批量注册事件并记录,方便销毁时清理
Object.entries(eventHandlers).forEach(([event, handler]) => {
this.session!.on(event as SupportedEvents, handler);
this.registeredEvents.push(event as SupportedEvents);
});
后续如果要新增指令(比如“调整音量”),只需在映射表中加一行,无需修改注册逻辑。
资源销毁:避免内存泄漏
destroy方法系统性清理资源,是保证应用轻量运行的关键:
destroy(): void {
// 移除所有事件监听
this.registeredEvents.forEach(event => this.session?.off(event));
// 释放播放器
this.avplayer?.release();
// 销毁会话
this.session?.destroy((err) => {
if (err) console.error('销毁会话失败:', err);
});
}
这里的核心是“有注册就有注销”——通过registeredEvents记录所有注册过的事件,确保退出时一个不落全移除。
总结
这套方案的核心思路很简单:用单例会话做“唯一入口”,用控制器统一处理指令和状态,最后记得“用完就清”。无论是音乐APP还是视频播放器,只要涉及多渠道控制或跨设备同步,这套逻辑都能直接复用,既减少了状态混乱的风险,又降低了后期维护成本。