ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

HarmonyOS 7 + AVSession Kit + Audio Kit 技术干货:媒体会话、后台播放与系统播控联动机制【鸿蒙心迹】

HarmonyOS 7 + AVSession Kit + Audio Kit 技术干货:媒体会话、后台播放与系统播控联动机制【鸿蒙心迹】 音频能播放只能说明播放器本身能工作真正做成系统级媒体体验还要让系统媒体中心、耳机、车载、手表等控制入口理解“现在播的是什么、播放到哪里、点击暂停以后该通知谁”。这篇文章从播放器内部状态出发把 Audio Kit 与 AVSession Kit 的分工和联动链路完整拆开。一、为什么播放器已经能播了还需要 AVSession第一次做音频播放时很容易觉得播放器逻辑已经完整创建播放实例、加载资源、播放、暂停、上一首、下一首再做一个进度条功能就差不多了。这种实现放在应用内部确实能用。但只要用户把应用退到后台或者希望在系统控制中心、锁屏界面、蓝牙耳机、车载设备上控制播放问题就出现了。系统并不知道你的页面上哪一个按钮代表“播放”也不知道当前歌曲叫什么、播放到哪一秒、下一首是谁。你的播放器状态只存在于自己的业务代码里系统世界并没有“看见”它。AVSession Kit 就是把这层信息从应用内部带到系统媒体会话层。它的核心价值可以理解成两部分应用把媒体元数据和播放状态同步给系统系统或外设产生的播放控制指令再通过会话回传给应用。所以 Audio Kit / AVPlayer 解决“声音怎么真正播出来”AVSession 解决“系统怎样理解和控制这次播放”。两者不是替代关系而是上下两层。二、先把播放器状态和会话状态分开这类功能最容易犯的错误就是直接把播放器对象当成系统会话。播放器内部通常有自己的状态比如当前资源是否加载是否正在播放当前 position音量播放列表输出设备。而 AVSession 维护的是系统播控需要看到的一组信息当前媒体元数据播放 / 暂停 / 停止状态播放位置可响应的控制事件当前输出设备和会话是否激活。如果把这两层混在一起后面最容易出现状态不同步页面显示播放中系统控制中心显示暂停耳机按了暂停但应用 UI 没变切下一首以后声音变了系统封面还停留在上一首。所以我更推荐把播放层和会话层拆成两个 Service再用统一的状态更新方法连接。三、创建 AVSession 时第一件事不是 setMetadata而是把生命周期想清楚AVSession 的典型入口是创建会话并激活。官方能力里应用可以创建 AVSession设置媒体元数据和播放状态并响应播放、暂停、上一首、下一首等控制事件。工程上真正要注意的是会话应该和媒体播放生命周期一起管理而不是页面一进来就永远创建一个。我会把会话初始化单独封装import { avSession } from kit.AVSessionKit export class MediaSessionService { private session: avSession.AVSession | null null async create(context: Context) { if (this.session) return this.session await avSession.createAVSession( context, music_session, audio ) await this.session.activate() this.registerControllerEvents() } async destroy() { if (!this.session) return await this.session.deactivate() await this.session.destroy() this.session null } }这段代码最关键的是activate / deactivate / destroy这条生命周期链。很多联动异常并不是 setMetadata 写错了而是会话已经失效、重复创建或者播放结束以后没有释放。尤其当页面多次进入退出时如果会话生命周期没有收口很容易出现控制事件重复响应。四、系统媒体中心到底靠什么知道“现在播的是哪首歌”答案就是媒体元数据。只让系统知道“现在正在播放”是不够的还要同步标题、作者、专辑、封面等内容。否则控制中心最多只能出现一个没有上下文的播放状态。切歌时我通常会把“播放器切换资源”和“会话元数据更新”放到同一个业务动作里async syncMetadata(track: Track) { if (!this.session) return await this.session.setAVMetadata({ assetId: track.id, title: track.title, artist: track.artist, album: track.album, mediaImage: track.coverUri }) } async syncPlaybackState(isPlaying: boolean, position: number) { if (!this.session) return await this.session.setAVPlaybackState({ state: isPlaying ? avSession.PlaybackState.PLAYBACK_STATE_PLAY : avSession.PlaybackState.PLAYBACK_STATE_PAUSE, position: { elapsedTime: position, updateTime: Date.now() } }) }不同 API 版本的字段和枚举要以当前 SDK 为准但工程原则很固定播放器状态发生变化时会话状态要跟着变。页面上的 UI 不应该是第三套独立状态而应该和播放器、AVSession 共用同一份业务状态源。五、系统控制事件不是“通知”而是业务指令AVSession 真正有价值的地方在于它不是单向上报。系统控制中心、耳机、手表、车载等控制端发出“播放、暂停、下一首”时应用会收到会话事件。此时不能只记一条日志而要真正驱动播放器。我会在会话初始化时统一注册这些事件private registerControllerEvents() { if (!this.session) return this.session.on(play, async () { await playerService.play() await this.syncPlaybackState(true, playerService.position) }) this.session.on(pause, async () { await playerService.pause() await this.syncPlaybackState(false, playerService.position) }) this.session.on(playNext, async () { const track await playerService.next() await this.syncMetadata(track) await this.syncPlaybackState(true, 0) }) this.session.on(playPrevious, async () { const track await playerService.previous() await this.syncMetadata(track) await this.syncPlaybackState(true, 0) }) }这就是系统播控真正的闭环外部控制事件 → AVSession → 播放器业务 → 更新播放结果 → 再同步 AVSession 状态。中间任何一步断掉用户都会感觉“系统按钮按了没反应”或者“按了以后 UI 不对”。六、开发时我重点看的是“页面、会话、日志能不能对上”媒体类问题很多时候很难只靠界面判断。比如页面暂停按钮已经切成播放图标但系统媒体中心还显示正在播放这时视觉上很难确定究竟是哪一层没同步。所以开发阶段我更习惯让日志明确打出AVSession 创建成功activate 完成metadata 已更新playbackState 已更新收到 play / pause / next 等外部指令播放器真正执行成功。这样调试时右侧模拟器负责看播放器结果中间代码负责看状态收口底部日志负责判断系统会话链路有没有走完整。对于系统联动能力这种“证据链”比只看按钮状态靠谱得多。七、后台播放不是“把页面关掉声音还在”这么简单用户对后台播放的理解很直接退出页面甚至切到别的应用音频继续播放系统仍然能控制。但工程实现不能只是“播放器对象没被销毁”。真正完整的后台播放需要同时考虑播放能力是否符合系统后台运行规范媒体会话是否仍处于有效状态当前媒体元数据是否继续可见播放位置是否持续更新系统播控事件是否还能回到应用应用恢复前台以后 UI 是否能重新对齐当前真实播放状态。所以应用进入后台时我不会主动把 AVSession 清掉只要播放还在继续会话也应该继续保持有效。只有播放真正结束、用户明确停止或业务不再需要时才进入 deactivate / destroy。这和普通页面生命周期不一样。八、播放页最好把 AVSession 的“系统状态”露出来一点为了调试我会在播放器页面加一张会话状态卡片会话是否激活、当前 playbackState、输出设备、媒体信息是否已同步。这类信息正式产品可以隐藏但开发版本非常有用。因为一旦系统媒体中心没有出现你马上就能判断会话没创建会话没 activatemetadata 没设置playbackState 没同步还是系统控制事件没有注册。相比“重新运行一下看看”定位效率高很多。九、耳机、车载、手表的控制本质上都应该回到同一套状态机不同控制来源可能给人一种错觉好像要分别写很多套处理逻辑。实际上业务上更稳的设计是不管事件来自系统媒体中心、蓝牙耳机、车载还是手表都最终归一成同一组播放命令playpausestopnextpreviousseek。外部设备只是“事件入口”不同真正执行的播放器逻辑应该一致。我在调试页里会专门把“控制来源 收到的事件 处理结果”记录出来。这样当用户反馈“耳机能暂停但车机下一首没反应”时就能马上判断事件到底有没有传进来而不是盲目怀疑播放器。十、几个最常见的状态不同步问题这类媒体会话项目最后最容易卡住的是状态边界。第一种是播放已经开始但setAVPlaybackState()没跟上系统仍然显示暂停。第二种是切歌后只换了播放器资源没有更新 metadata导致系统封面和标题滞后。第三种是系统控制事件触发了播放器但业务状态没有更新页面重新回到前台以后 UI 显示错误。还有一种更隐蔽重复注册会话事件。页面多次进入后同一个pause事件被触发两三次表面上看像播放器状态异常实际是监听没有跟会话生命周期一起清理。所以我会坚持三个原则播放状态只有一份业务真源所有外部控制统一进入播放器状态机会话创建、监听注册和销毁严格成对。十一、AudioRenderer 还是 AVPlayer要看你真正处理的音频是什么Audio Kit 里不同播放能力适合不同场景。如果是普通媒体文件播放使用更高层的媒体播放器通常更省事。如果你需要直接处理 PCM、做实时预处理、效果链或更底层的音频渲染AudioRenderer 会更灵活。但不管底层播放最终用哪种能力只要你希望系统控制中心和外部控制设备理解当前媒体状态AVSession 这一层的思路基本一致。这也是为什么我更愿意把 AVSession 看成“播控协议层”而不是“另一个播放器”。十二、本文小记这次把 Audio Kit 和 AVSession Kit 串起来以后我对 HarmonyOS 媒体能力最大的认识变化是不再把播放器理解成一个独立页面。真正完整的媒体体验其实有两层底层播放负责声音媒体会话负责系统级状态和控制。完整链路应该是音频资源 → 播放器 → AVSession 元数据 / 播放状态 → 系统媒体中心与外设 → 控制事件回传 → 播放器状态更新。这条链跑通以后后台播放、系统控制中心、蓝牙耳机、车载、手表等体验才真正被串成一个整体。如果后面继续往下做我会把播放列表队列、seek 同步、输出设备切换和投播能力再接进来。做到那一步以后媒体应用才不只是“能播放”而是开始真正融入系统级播控体系。
返回列表