HarmonyOS律愈实战06:AVSession后台音频保活实现
HarmonyOS 后台音频实战:AVSession 与后台播放保活



1. 为什么需要后台音频
疗愈类 App 的播放场景很特殊:用户可能打开音频后锁屏,或者切到其他应用。如果一切到后台就停止,体验会非常割裂。
“律愈”项目通过三层能力支撑后台播放:
module.json5声明后台音频模式和权限。backgroundTaskManager开启后台任务。AVSession把当前播放信息同步到系统媒体中心。
2. 配置后台能力
在 module.json5 中,项目声明:
{
"requestPermissions": [
{
"name": "ohos.permission.KEEP_BACKGROUND_RUNNING"
}
],
"abilities": [
{
"name": "EntryAbility",
"backgroundModes": [
"audioPlayback"
]
}
]
}
这一步是基础。如果没有声明后台模式,后续服务代码写得再完整,也无法得到预期效果。
3. 后台音频服务类
项目将后台能力封装在 YulvBackgroundAudioService:
export class YulvBackgroundAudioService {
private readonly ctx: common.Context;
private session: avSession.AVSession | null = null;
private backgroundRunning: boolean = false;
constructor(ctx: common.Context) {
this.ctx = ctx;
}
}
这个类不直接播放音频。真正播放由 ToneAudioPlayer 完成,它只负责系统层面的“播放状态声明”和“后台任务管理”。
4. 创建 AVSession
private async ensureSession(): Promise<avSession.AVSession | null> {
if (this.session !== null) {
return this.session;
}
try {
this.session = await avSession.createAVSession(this.ctx, 'YulvToneAudio', 'audio');
await this.session.activate();
return this.session;
} catch (_e) {
this.session = null;
return null;
}
}
AVSession 的作用是告诉系统:当前应用正在进行媒体播放。它也方便系统媒体中心展示标题、艺术家、播放状态等信息。
5. 构建 WantAgent
后台任务需要一个 WantAgent,用于用户从系统入口重新回到应用:
private async buildWantAgent(): Promise<WantAgent> {
const launchWant: Want = {
bundleName: this.ctx.applicationInfo.name,
abilityName: 'EntryAbility'
};
const info: YulvWantAgentInfo = {
wants: [launchWant],
actionType: abilityWantAgent.OperationType.START_ABILITY,
requestCode: 20260611,
actionFlags: [abilityWantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
};
return abilityWantAgent.getWantAgent(info);
}
这里的 requestCode 可以理解为这个后台任务入口的标识。
6. 启动后台运行
private async startBackgroundTask(): Promise<void> {
if (this.backgroundRunning) {
return;
}
try {
const agent: WantAgent = await this.buildWantAgent();
await backgroundTaskManager.startBackgroundRunning(this.ctx, ['audioPlayback'], agent);
this.backgroundRunning = true;
} catch (_e) {
this.backgroundRunning = false;
}
}
这里做了一个状态保护:如果已经在后台运行,就不重复开启。
7. 播放时更新媒体信息
当音频开始播放时,项目调用 markPlaying():
async markPlaying(tone: ToneKey): Promise<void> {
const session: avSession.AVSession | null = await this.ensureSession();
if (session !== null) {
const meta = wuYinMetaOf(tone);
const metadata: YulvAVMetadata = {
assetId: `yulv_${tone}`,
title: `${meta.name}疗愈`,
artist: '律愈',
album: `${meta.element} · ${meta.organ}`,
subtitle: meta.description,
duration: 6 * 1000
};
const state: YulvAVPlaybackState = {
state: avSession.PlaybackState.PLAYBACK_STATE_PLAY,
speed: 1
};
await session.setAVMetadata(metadata);
await session.setAVPlaybackState(state);
await session.activate();
}
await this.startBackgroundTask();
}
这里的设计很清晰:先更新系统媒体信息,再启动后台任务。
8. 暂停和释放
暂停时更新播放状态,并停止后台任务:
async markPaused(): Promise<void> {
if (this.session !== null) {
const state: YulvAVPlaybackState = {
state: avSession.PlaybackState.PLAYBACK_STATE_PAUSE,
speed: 0
};
await this.session.setAVPlaybackState(state);
}
await this.stopBackgroundTask();
}
释放时销毁 AVSession:
async release(): Promise<void> {
await this.stopBackgroundTask();
if (this.session !== null) {
await this.session.deactivate();
await this.session.destroy();
this.session = null;
}
}
9. 与播放器的关系
ToneAudioPlayer 在真正播放前后调用后台服务:
if (this.loadedTone !== null) {
await this.backgroundAudio.markPlaying(this.loadedTone);
}
await pl.play();
暂停时:
async pause(): Promise<void> {
this.playAfterPrepared = false;
if (this.avPlayer !== null && this.avPlayer.state === 'playing') {
await this.avPlayer.pause();
}
await this.backgroundAudio.markPaused();
}
10. 流程图
11. 小结
后台音频不是单一 API 能解决的问题。它需要配置、媒体会话、后台任务和播放器状态一起配合。
这个项目把后台播放封装为独立服务,是比较推荐的写法。页面只关心“播放/暂停”,播放器只关心“音频源和状态机”,后台服务只关心“系统媒体状态和后台运行”。边界清楚,后续维护会轻松很多。
9. 后台音频的三个层次
后台播放不是“加一个权限”就完成了。律愈项目里有三个层次:
ext 配置层:module.json5 声明权限和 backgroundModes 播放层:ToneAudioPlayer 真正播放音频 系统层:AVSession + backgroundTaskManager 告诉系统当前是音频任务
10. AVSession 元数据的意义
setAVMetadata() 不只是装饰信息,它让系统媒体中心知道当前播放的是什么:
s const metadata: YulvAVMetadata = { assetId: yulv_, title: ${meta.name}疗愈, artist: '律愈', album: ${meta.element} · , subtitle: meta.description, duration: 6 * 1000 };
对于音乐、冥想、课程类应用,媒体中心展示质量会直接影响用户体验。
11. WantAgent 为什么存在
后台任务需要一个可返回应用的入口:
s const launchWant: Want = { bundleName: this.ctx.applicationInfo.name, abilityName: 'EntryAbility' };
这说明后台音频不是孤立运行,它仍然和主 Ability 保持关联。用户从系统媒体入口回到应用时,需要知道启动哪个 Ability。
12. 播放和后台状态要同步
播放器播放成功后调用:
s await this.backgroundAudio.markPlaying(this.loadedTone); await pl.play();
暂停时调用:
s await this.backgroundAudio.markPaused();
这个同步很重要。如果音频暂停了但后台任务还在跑,系统层状态就不准确;如果音频播放了但 AVSession 没更新,系统媒体中心也无法正确展示。
13. 异常处理策略
项目中后台服务的异常大多被捕获并忽略,这是合理的:
s try { await session.setAVMetadata(metadata); await session.setAVPlaybackState(state); await session.activate(); } catch (_e) { // 不阻断实际播放 }
原因是 AVSession 失败不应该影响基本播放能力。文章中可以解释这种降级策略:后台能力是增强体验,主播放链路才是核心。
更多推荐



所有评论(0)