HarmonyOS 6实战(源码教学篇)--- AVSession Kit 新特性【仿某云音乐实现媒体会话和后台播放管理】【API20】
-
- 教程概述
- [一、AVSession 媒体会话概述](#一、AVSession 媒体会话概述)
-
- [1.1 什么是 AVSession?](#1.1 什么是 AVSession?)
- [1.2 AVSession 架构](#1.2 AVSession 架构)
- [1.3 核心概念](#1.3 核心概念)
- [二、AVSession 初始化](#二、AVSession 初始化)
-
- [2.1 AVSessionController 类设计](#2.1 AVSessionController 类设计)
- [2.2 创建和激活会话](#2.2 创建和激活会话)
- 三、媒体元数据管理
-
- [3.1 设置媒体元数据](#3.1 设置媒体元数据)
- [3.2 图片资源转换](#3.2 图片资源转换)
- 四、设置启动能力
-
- [4.1 LaunchAbility 作用](#4.1 LaunchAbility 作用)
- 五、监听控制命令
-
- [5.1 注册命令监听器](#5.1 注册命令监听器)
- [5.2 播放/暂停命令处理](#5.2 播放/暂停命令处理)
- [5.3 上一首/下一首命令处理](#5.3 上一首/下一首命令处理)
- [5.4 进度跳转命令处理](#5.4 进度跳转命令处理)
- 六、循环模式管理
-
- [6.1 循环模式映射](#6.1 循环模式映射)
- [6.2 处理循环模式切换命令](#6.2 处理循环模式切换命令)
- [6.3 同步循环模式到 AVSession](#6.3 同步循环模式到 AVSession)
- 七、播放状态同步
-
- [7.1 同步播放/暂停状态](#7.1 同步播放/暂停状态)
- [7.2 同步播放进度](#7.2 同步播放进度)
- [7.3 在 AudioRendererController 中调用](#7.3 在 AudioRendererController 中调用)
- 八、收藏功能实现
-
- [8.1 收藏状态管理](#8.1 收藏状态管理)
- [8.2 同步收藏状态](#8.2 同步收藏状态)
- [8.3 Preferences 工具类](#8.3 Preferences 工具类)
- 九、后台播放管理
-
- [9.1 后台播放原理](#9.1 后台播放原理)
- [9.2 BackgroundUtil 工具类](#9.2 BackgroundUtil 工具类)
- [9.3 在播放控制中集成](#9.3 在播放控制中集成)
- [9.4 权限配置](#9.4 权限配置)
- 十、静音模式与混音
-
- [10.1 静音模式](#10.1 静音模式)
- 十一、完整集成流程
-
- [11.1 应用启动流程](#11.1 应用启动流程)
- [11.2 页面初始化流程](#11.2 页面初始化流程)
- [11.3 AudioRendererController 初始化](#11.3 AudioRendererController 初始化)
- [11.4 AVSessionController 初始化](#11.4 AVSessionController 初始化)
- 十二、状态同步机制
-
- [12.1 状态流转图](#12.1 状态流转图)
- [12.2 关键状态同步点](#12.2 关键状态同步点)
- [12.3 状态同步代码示例](#12.3 状态同步代码示例)
- 十三、注销监听器
-
- [13.1 为什么要注销?](#13.1 为什么要注销?)
- [13.2 注销 AVSession 监听器](#13.2 注销 AVSession 监听器)
- [13.3 在释放资源时调用](#13.3 在释放资源时调用)
- 十六、常见问题与解决方案
-
- [Q1: 控制中心不显示播放信息?](#Q1: 控制中心不显示播放信息?)
- [Q2: 点击控制中心无响应?](#Q2: 点击控制中心无响应?)
- [Q3: 后台播放被暂停?](#Q3: 后台播放被暂停?)
- [Q4: 状态不同步?](#Q4: 状态不同步?)
- [Q5: 切歌后封面不更新?](#Q5: 切歌后封面不更新?)
- 总结

大家好!我是你们的老朋友木斯佳, 是华为云 HDE 认证专家和OpenTiny 开源社区的布道师,熟悉我们的小伙伴们已经实现了音频播放的小案例,今天我们继续分享HarmonyOS 6 基础能力 AVSession Kit 新特性。
AVSession Kit 提供音视频统一管控能力,音视频类应用接入AVSession后,可以发送应用的数据(比如正在播放的歌曲、歌曲的播放状态等),用户可以通过系统播控中心、语音助手等应用切换多个应用、多个设备播放音频接入AVSession后,可以进行后台音频播放。
教程概述
本教程分为上下两篇:
在上篇中,我们学习了如何使用 AudioRenderer 实现音频播放的核心功能。本篇将带你深入理解 AVSession 媒体会话机制,实现与系统媒体控制中心的交互,以及后台播放管理。
学习目标
- 理解 AVSession 媒体会话的作用和原理
- 实现与系统媒体控制中心的双向交互
- 掌握媒体元数据的设置和更新
- 实现后台播放和长时任务管理
- 同步应用内外的播放状态
- 处理收藏、循环模式等高级功能
技术栈
- 核心 API: AVSession、BackgroundTaskManager、WantAgent
- 权限: ohos.permission.KEEP_BACKGROUND_RUNNING
一、AVSession 媒体会话概述

1.1 什么是 AVSession?
AVSession(Audio Video Session)是 HarmonyOS 提供的媒体会话管理框架,用于:
- 在系统媒体控制中心显示播放信息
- 接收来自控制中心的播放控制命令
- 同步播放状态到系统
- 支持锁屏、通知栏、控制中心的统一控制
1.2 AVSession 架构
┌─────────────────────────────────────────┐
│ 系统媒体控制中心 │
│ (锁屏、通知栏、控制中心) │
└──────────────┬──────────────────────────┘
│ 命令 (play/pause/next...)
↓
┌─────────────────────────────────────────┐
│ AVSession 会话 │
│ - 接收控制命令 │
│ - 发送媒体元数据 │
│ - 同步播放状态 │
└──────────────┬──────────────────────────┘
│ 调用播放器方法
↓
┌─────────────────────────────────────────┐
│ AudioRendererController │
│ (音频播放控制器) │
└─────────────────────────────────────────┘
1.3 核心概念
| 概念 | 说明 |
|---|---|
| AVMetadata | 媒体元数据(歌曲名、歌手、封面等) |
| AVPlaybackState | 播放状态(播放/暂停、进度、循环模式等) |
| AVSessionController | 会话控制器(接收系统命令) |
| LaunchAbility | 启动能力(点击控制中心时打开应用) |
二、AVSession 初始化
2.1 AVSessionController 类设计
typescript
export class AVSessionController {
private context: common.UIAbilityContext;
private AVSession?: avSession.AVSession;
private songList: SongItem[] = [];
private musicIndex: number = 0;
private audioRendererController?: AudioRendererController;
constructor() {
let list = AppStorage.get<SongItem[]>('songList');
if (list) {
this.songList = list;
}
this.initAVSession();
}
/**
* 获取单例实例
*/
public static getInstance(): AVSessionController {
let avSessionController = AppStorage.get<AVSessionController>('AVSessionController');
if (!avSessionController) {
avSessionController = new AVSessionController();
AppStorage.setOrCreate('AVSessionController', avSessionController);
}
return avSessionController;
}
}
2.2 创建和激活会话
typescript
private async initAVSession() {
// 1. 获取上下文
this.context = AppStorage.get('context');
if (!this.context) {
Logger.error(TAG, '上下文获取失败');
return;
}
// 2. 获取音频控制器引用
this.audioRendererController = AppStorage.get('audioRendererController');
if (!this.audioRendererController) {
Logger.error(TAG, '音频控制器获取失败');
return;
}
// 3. 创建 AVSession
this.AVSession = await avSession.createAVSession(
this.context,
"PLAY_AUDIO", // 会话标签(自定义)
'audio' // 会话类型:audio(音频) / video(视频)
);
// 4. 激活会话
await this.AVSession.activate();
Logger.info(TAG, `会话创建成功,ID: ${this.AVSession.sessionId}`);
// 5. 初始化配置
await this.setAVMetadata(); // 设置媒体元数据
this.setLaunchAbility(); // 设置启动能力
this.setListenerForMesFromController(); // 监听控制命令
}
关键参数说明:
- 会话标签: 自定义字符串,用于标识会话
- 会话类型 :
audio用于音频播放,video用于视频播放 - 激活: 只有激活的会话才能在控制中心显示
三、媒体元数据管理
3.1 设置媒体元数据
媒体元数据会显示在系统媒体控制中心,包括歌曲名、歌手、封面等。
typescript
public async setAVMetadata() {
// 1. 获取当前歌曲索引
this.musicIndex = AppStorage.get('selectIndex') ?? 0;
try {
// 2. 加载封面图片
let mediaImage = await MediaTools.getPixelMapFromResource(
this.context,
this.songList[this.musicIndex].label
);
// 3. 构建元数据对象
let metadata: avSession.AVMetadata = {
assetId: `${this.musicIndex}`, // 资源ID(用于标识)
title: this.songList[this.musicIndex].title, // 歌曲名
artist: this.songList[this.musicIndex].singer, // 歌手名
mediaImage: mediaImage, // 封面图(PixelMap)
duration: this.getDuration(), // 总时长(毫秒)
};
// 4. 加载歌词(可选)
let lrc = await MediaTools.getLrcFromRawFile(
this.context,
this.songList[this.musicIndex].lyric
);
if (lrc) {
metadata.lyric = lrc; // 歌词内容
}
// 5. 设置到 AVSession
if (this.AVSession) {
await this.AVSession.setAVMetadata(metadata);
Logger.info(TAG, '元数据设置成功');
}
} catch (error) {
Logger.error(TAG, `元数据设置失败: ${error.message}`);
}
}
AVMetadata 完整字段:
| 字段 | 类型 | 说明 | 是否必填 |
|---|---|---|---|
assetId |
string | 资源唯一标识 | 是 |
title |
string | 歌曲标题 | 是 |
artist |
string | 歌手名称 | 否 |
author |
string | 作者 | 否 |
album |
string | 专辑名称 | 否 |
mediaImage |
PixelMap | 封面图片 | 否 |
duration |
number | 总时长(毫秒) | 否 |
lyric |
string | 歌词内容 | 否 |
3.2 图片资源转换
typescript
export class MediaTools {
/**
* 将 Resource 资源转换为 PixelMap
*/
static async getPixelMapFromResource(
context: common.UIAbilityContext,
resource: resourceManager.Resource
): Promise<PixelMap> {
let resourceMgr = context.resourceManager;
// 1. 读取图片资源为 ArrayBuffer
let imageBuffer = await resourceMgr.getMediaContent(resource);
// 2. 创建图片源
let imageSource = image.createImageSource(imageBuffer);
// 3. 创建 PixelMap
let pixelMap = await imageSource.createPixelMap();
return pixelMap;
}
/**
* 从 rawfile 读取歌词文件
*/
static async getLrcFromRawFile(
context: common.UIAbilityContext,
lyricPath: string
): Promise<string> {
let resourceMgr = context.resourceManager;
// 读取文本文件
let lrcBuffer = await resourceMgr.getRawFileContent(lyricPath);
// 转换为字符串
let decoder = util.TextDecoder.create('utf-8');
let lrcContent = decoder.decodeWithStream(lrcBuffer);
return lrcContent;
}
}
四、设置启动能力
4.1 LaunchAbility 作用
当用户点击系统媒体控制中心时,可以启动或切换到应用。
typescript
private setLaunchAbility() {
if (!this.context) return;
// 1. 配置 WantAgent 信息
let wantAgentInfo: wantAgent.WantAgentInfo = {
wants: [{
bundleName: this.context.abilityInfo.bundleName, // 应用包名
abilityName: this.context.abilityInfo.name // Ability 名称
}],
operationType: wantAgent.OperationType.START_ABILITIES, // 操作类型
requestCode: 0,
wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG] // 更新标志
};
// 2. 获取 WantAgent
wantAgent.getWantAgent(wantAgentInfo).then((agent) => {
if (this.AVSession) {
// 3. 设置到 AVSession
this.AVSession.setLaunchAbility(agent);
Logger.info(TAG, '启动能力设置成功');
}
});
}
WantAgent 操作类型:
START_ABILITIES: 启动 AbilitySTART_ABILITY: 启动单个 AbilitySEND_COMMON_EVENT: 发送公共事件
五、监听控制命令

5.1 注册命令监听器
系统媒体控制中心会发送各种控制命令,应用需要监听并响应。
typescript
async setListenerForMesFromController() {
if (!this.AVSession) return;
// 注册各种命令监听器
this.AVSession.on('play', this.onPlay);
this.AVSession.on('pause', this.onPause);
this.AVSession.on('playNext', this.onPlayNext);
this.AVSession.on('playPrevious', this.onPlayPrevious);
this.AVSession.on('seek', this.onSeek);
this.AVSession.on('setLoopMode', this.onSetLoopMode);
this.AVSession.on('toggleFavorite', this.onToggleFavorite);
Logger.info(TAG, '命令监听器注册成功');
}
5.2 播放/暂停命令处理
typescript
/**
* 处理播放命令
*/
private onPlay: () => void = () => {
Logger.info(TAG, '收到播放命令');
if (!this.audioRendererController) {
Logger.error(TAG, '音频控制器未初始化');
return;
}
// 判断是首次播放还是恢复播放
if (this.audioRendererController.getFirst()) {
this.audioRendererController.play(); // 首次播放
} else {
this.audioRendererController.start(); // 恢复播放
}
}
/**
* 处理暂停命令
*/
private onPause: () => void = () => {
Logger.info(TAG, '收到暂停命令');
if (!this.audioRendererController) {
Logger.error(TAG, '音频控制器未初始化');
return;
}
this.audioRendererController.pause();
}
5.3 上一首/下一首命令处理
typescript
/**
* 处理下一首命令
*/
private onPlayNext: () => void = () => {
Logger.info(TAG, '收到下一首命令');
if (!this.audioRendererController) {
Logger.error(TAG, '音频控制器未初始化');
return;
}
this.audioRendererController.playNext();
}
/**
* 处理上一首命令
*/
private onPlayPrevious: () => void = () => {
Logger.info(TAG, '收到上一首命令');
if (!this.audioRendererController) {
Logger.error(TAG, '音频控制器未初始化');
return;
}
this.audioRendererController.playPrevious();
}
5.4 进度跳转命令处理
typescript
/**
* 处理进度跳转命令
* @param curMs 目标时间(毫秒)
*/
private onSeek: (curMs: number) => void = (curMs: number) => {
Logger.info(TAG, `收到跳转命令: ${curMs}ms`);
if (!this.audioRendererController) {
Logger.error(TAG, '音频控制器未初始化');
return;
}
this.audioRendererController.seek(curMs);
}
六、循环模式管理
6.1 循环模式映射
AVSession 和应用内部使用不同的循环模式枚举,需要进行映射。
typescript
// 应用内部枚举
enum MusicPlayMode {
SINGLE_CYCLE = 0, // 单曲循环
ORDER = 1, // 顺序播放
RANDOM = 2 // 随机播放
}
// AVSession 枚举
enum avSession.LoopMode {
LOOP_MODE_SINGLE, // 单曲循环
LOOP_MODE_SEQUENCE, // 顺序播放
LOOP_MODE_SHUFFLE // 随机播放
}
6.2 处理循环模式切换命令
typescript
/**
* 处理循环模式切换命令
*/
private onSetLoopMode: (loopMode: avSession.LoopMode) => void = (loopMode) => {
Logger.info(TAG, `收到循环模式切换命令: ${loopMode}`);
if (!this.audioRendererController) {
Logger.error(TAG, '音频控制器未初始化');
return;
}
// AVSession 模式 → 应用内部模式
switch (loopMode) {
case avSession.LoopMode.LOOP_MODE_SINGLE:
this.audioRendererController.setPlayModel(MusicPlayMode.ORDER);
break;
case avSession.LoopMode.LOOP_MODE_SEQUENCE:
this.audioRendererController.setPlayModel(MusicPlayMode.RANDOM);
break;
case avSession.LoopMode.LOOP_MODE_SHUFFLE:
this.audioRendererController.setPlayModel(MusicPlayMode.SINGLE_CYCLE);
break;
}
// 同步状态
this.setPlayModeToAVSession();
this.setPlayModeToControlArea();
}
6.3 同步循环模式到 AVSession
typescript
/**
* 将应用内部的循环模式同步到 AVSession
*/
public setPlayModeToAVSession() {
if (!this.audioRendererController) return;
let currentPlayMode = this.audioRendererController.getPlayMode();
let AVSessionLoopMode: avSession.LoopMode;
// 应用内部模式 → AVSession 模式
switch (currentPlayMode) {
case MusicPlayMode.SINGLE_CYCLE:
AVSessionLoopMode = avSession.LoopMode.LOOP_MODE_SINGLE;
break;
case MusicPlayMode.ORDER:
AVSessionLoopMode = avSession.LoopMode.LOOP_MODE_SEQUENCE;
break;
case MusicPlayMode.RANDOM:
AVSessionLoopMode = avSession.LoopMode.LOOP_MODE_SHUFFLE;
break;
}
this.setLoopModeState(AVSessionLoopMode);
}
/**
* 设置循环模式状态
*/
public setLoopModeState(loopMode: avSession.LoopMode) {
if (this.AVSession) {
this.AVSession.setAVPlaybackState({ loopMode }, (err) => {
if (err) {
Logger.error(TAG, `设置循环模式失败: ${err.message}`);
} else {
Logger.info(TAG, '循环模式设置成功');
}
});
}
}
七、播放状态同步
7.1 同步播放/暂停状态
typescript
/**
* 同步播放状态到 AVSession
* @param isPlay true-播放中,false-暂停
*/
public setPlayState(isPlay: boolean) {
if (!this.AVSession) return;
this.AVSession.setAVPlaybackState({
state: isPlay ?
avSession.PlaybackState.PLAYBACK_STATE_PLAY :
avSession.PlaybackState.PLAYBACK_STATE_PAUSE
}, (err) => {
if (err) {
Logger.error(TAG, `设置播放状态失败: ${err.message}`);
} else {
Logger.info(TAG, `播放状态设置为: ${isPlay ? '播放' : '暂停'}`);
}
});
}
7.2 同步播放进度
typescript
/**
* 同步播放进度到 AVSession
* @param ms 当前播放位置(毫秒)
*/
public setProgressState(ms: number) {
if (!this.AVSession) return;
this.AVSession.setAVPlaybackState({
position: {
elapsedTime: ms, // 已播放时间
updateTime: new Date().getTime() // 更新时间戳
}
}, (err) => {
if (err) {
Logger.error(TAG, `设置进度失败: ${err.message}`);
}
});
}
注意事项:
elapsedTime: 当前播放位置(毫秒)updateTime: 状态更新的时间戳,系统用于计算实时进度- 建议每秒更新一次,避免频繁调用
7.3 在 AudioRendererController 中调用
typescript
// 在 AudioRendererController 中
private updateIsPlay(isPlay: boolean) {
AppStorage.setOrCreate<boolean>('isPlay', isPlay);
// 同步到 AVSession
if (this.avSessionController) {
this.avSessionController.setPlayState(isPlay);
}
}
public seek(ms: number) {
this.curMs = ms;
AppStorage.setOrCreate('progress', this.curMs);
AppStorage.setOrCreate('currentTime', MediaTools.msToCountdownTime(this.curMs));
// 同步到 AVSession
if (this.avSessionController) {
this.avSessionController.setProgressState(ms);
}
this.currentOffset = this.initOffset + MediaTools.getOffsetFromTime(this.curMs);
}
八、收藏功能实现
8.1 收藏状态管理
使用 Preferences 持久化存储收藏列表。
typescript
/**
* 处理收藏切换命令
*/
private onToggleFavorite: (assetId: string) => void = (assetId: string) => {
Logger.info(TAG, `收到收藏切换命令: ${assetId}`);
this.updateFavoriteState(assetId);
}
/**
* 更新收藏状态
*/
public async updateFavoriteState(assetId: string, isFavorite: boolean = false) {
if (!this.context) return;
// 1. 获取收藏列表
let favoriteIds = await PreferencesUtil.getInstance().getFormIds(this.context);
// 2. 切换收藏状态
if (favoriteIds.includes(assetId)) {
// 取消收藏
await PreferencesUtil.getInstance().removeFormId(this.context, assetId);
isFavorite = false;
} else {
// 添加收藏
await PreferencesUtil.getInstance().addFormId(this.context, assetId);
isFavorite = true;
}
// 3. 更新UI和AVSession
AppStorage.setOrCreate('isFavorite', isFavorite);
this.setFavoriteState(isFavorite);
}
8.2 同步收藏状态
typescript
/**
* 设置收藏状态到 AVSession
*/
private setFavoriteState(isFavorite: boolean) {
if (!this.AVSession) return;
this.AVSession.setAVPlaybackState({ isFavorite }, (err) => {
if (err) {
Logger.error(TAG, `设置收藏状态失败: ${err.message}`);
} else {
Logger.info(TAG, `收藏状态设置为: ${isFavorite}`);
}
});
}
/**
* 获取并更新收藏状态(切歌时调用)
*/
public async getAndUpdateFavoriteState(assetId: string) {
if (!this.context) return;
let favoriteIds = await PreferencesUtil.getInstance().getFormIds(this.context);
let isFavorite = favoriteIds.includes(assetId);
AppStorage.setOrCreate('isFavorite', isFavorite);
this.setFavoriteState(isFavorite);
}
8.3 Preferences 工具类
typescript
export class PreferencesUtil {
private static instance: PreferencesUtil;
private preferences?: dataPreferences.Preferences;
public static getInstance(): PreferencesUtil {
if (!PreferencesUtil.instance) {
PreferencesUtil.instance = new PreferencesUtil();
}
return PreferencesUtil.instance;
}
/**
* 获取收藏列表
*/
async getFormIds(context: common.UIAbilityContext): Promise<string[]> {
this.preferences = await dataPreferences.getPreferences(context, 'favoriteStore');
let ids = await this.preferences.get('favoriteIds', '[]');
return JSON.parse(ids as string);
}
/**
* 添加收藏
*/
async addFormId(context: common.UIAbilityContext, id: string): Promise<void> {
let ids = await this.getFormIds(context);
if (!ids.includes(id)) {
ids.push(id);
await this.preferences?.put('favoriteIds', JSON.stringify(ids));
await this.preferences?.flush();
}
}
/**
* 取消收藏
*/
async removeFormId(context: common.UIAbilityContext, id: string): Promise<void> {
let ids = await this.getFormIds(context);
let index = ids.indexOf(id);
if (index > -1) {
ids.splice(index, 1);
await this.preferences?.put('favoriteIds', JSON.stringify(ids));
await this.preferences?.flush();
}
}
}
九、后台播放管理

9.1 后台播放原理
HarmonyOS 默认会在应用退到后台后暂停音频播放,需要申请长时任务(Continuous Task)才能实现后台播放。
长时任务类型:
AUDIO_PLAYBACK: 音频播放AUDIO_RECORDING: 音频录制LOCATION: 定位导航BLUETOOTH_INTERACTION: 蓝牙交互MULTI_DEVICE_CONNECTION: 多设备连接WIFI_INTERACTION: WIFI 交互VOIP: 音视频通话TASK_KEEPING: 计算任务
9.2 BackgroundUtil 工具类
typescript
export class BackgroundUtil {
/**
* 启动后台任务
*/
public static startContinuousTask(context?: common.UIAbilityContext): void {
if (!context) {
Logger.error(TAG, '上下文未定义,无法启动后台任务');
return;
}
// 1. 配置 WantAgent(用于通知栏点击)
let wantAgentInfo: wantAgent.WantAgentInfo = {
wants: [{
bundleName: context.abilityInfo.bundleName,
abilityName: context.abilityInfo.name
}],
operationType: wantAgent.OperationType.START_ABILITY,
requestCode: 0,
wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
};
// 2. 获取 WantAgent
wantAgent.getWantAgent(wantAgentInfo).then((wantAgentObj) => {
try {
// 3. 启动后台任务
backgroundTaskManager.startBackgroundRunning(
context,
backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK, // 音频播放模式
wantAgentObj
).then(() => {
Logger.info(TAG, '后台任务启动成功');
}).catch((error: BusinessError) => {
Logger.error(TAG, `后台任务启动失败: ${error.code}`);
});
} catch (error) {
Logger.error(TAG, `后台任务启动异常: ${error.message}`);
}
});
}
/**
* 停止后台任务
*/
public static stopContinuousTask(context: common.UIAbilityContext): void {
try {
backgroundTaskManager.stopBackgroundRunning(context).then(() => {
Logger.info(TAG, '后台任务停止成功');
}).catch((error: BusinessError) => {
Logger.error(TAG, `后台任务停止失败: ${error.code}`);
});
} catch (error) {
Logger.error(TAG, `后台任务停止异常: ${error.message}`);
}
}
}
9.3 在播放控制中集成
typescript
// 在 AudioRendererController 中
/**
* 开始播放
*/
async play(musicIndex: number) {
// 启动后台任务
BackgroundUtil.startContinuousTask(this.context);
this.updateMusicIndex(musicIndex);
if (this.isFirst) {
await this.loadSongAssent();
await this.stop();
await this.start();
} else {
await this.stop();
await this.reset();
}
}
/**
* 开始播放
*/
async start() {
if (!this.audioRenderer) return;
await this.audioRenderer.start();
this.updateIsPlay(true);
// 确保后台任务已启动
BackgroundUtil.startContinuousTask(this.context);
}
/**
* 释放资源
*/
async release() {
if (!this.audioRenderer || !this.context) return;
await this.audioRenderer.release();
// 停止后台任务
BackgroundUtil.stopContinuousTask(this.context);
// 注销 AVSession 监听器
this.avSessionController?.unregisterSessionListener();
}
9.4 权限配置
在 module.json5 中声明后台任务权限:
json
{
"module": {
"name": "entry",
"type": "entry",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"backgroundModes": ["audioPlayback"]
}
],
"requestPermissions": [
{
"name": "ohos.permission.KEEP_BACKGROUND_RUNNING",
"reason": "$string:keep_background_running_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
}
}
在 string.json 中添加权限说明:
json
{
"string": [
{
"name": "keep_background_running_reason",
"value": "用于后台播放音乐"
}
]
}
十、静音模式与混音
10.1 静音模式
静音模式允许应用在不打断其他音频的情况下播放音频。
typescript
/**
* 设置静音模式和混音
* @param isSupportSilent true-启用静音模式,false-禁用
*/
public async setSilentModeAndMixWithOthers(isSupportSilent: boolean = false) {
if (!this.audioRenderer || !this.context) return;
let audioManger = audio.getAudioManager();
let audioSessionManager = audioManger.getSessionManager();
// 配置音频会话策略
let strategy: audio.AudioSessionStrategy = {
concurrencyMode: audio.AudioConcurrencyMode.CONCURRENCY_PAUSE_OTHERS
};
// 获取静音模式配置
let formIds = await PreferencesUtil.getInstance().getFormIds(this.context);
if (isSupportSilent) {
// 启用静音模式
if (!formIds.includes(SILENT_ID)) {
await PreferencesUtil.getInstance().addFormId(this.context, SILENT_ID);
}
this.audioRenderer.setSilentModeAndMixWithOthers(isSupportSilent);
// 停用音频会话,允许其他音频恢复
audioSessionManager.deactivateAudioSession().then(() => {
Logger.info(TAG, '音频会话停用成功');
}).catch((err: BusinessError) => {
Logger.error(TAG, `音频会话停用失败: ${err}`);
});
} else {
// 禁用静音模式
// 先激活音频会话
audioSessionManager.activateAudioSession(strategy).then(() => {
Logger.info(TAG, '音频会话激活成功');
}).catch((err: BusinessError) => {
Logger.error(TAG, `音频会话激活失败: ${err}`);
});
if (formIds.includes(SILENT_ID)) {
await PreferencesUtil.getInstance().removeFormId(this.context, SILENT_ID);
}
this.audioRenderer.setSilentModeAndMixWithOthers(isSupportSilent);
}
AppStorage.setOrCreate('isSilentMode', isSupportSilent);
}
使用场景:
- 启用静音模式:应用音频不会打断其他应用(如导航、通话)
- 禁用静音模式:应用音频会暂停其他应用的音频
十一、完整集成流程


11.1 应用启动流程
typescript
// EntryAbility.ets
export default class EntryAbility extends UIAbility {
onCreate() {
// 保存 context 到全局存储
AppStorage.setOrCreate('context', this.context);
}
onWindowStageCreate(windowStage: window.WindowStage) {
windowStage.loadContent('pages/Root', (err) => {
// 加载主页面
});
}
}
11.2 页面初始化流程
typescript
// PlayerPage.ets
@Component
struct PlayerPage {
aboutToAppear(): void {
// 1. 初始化歌曲列表
AppStorage.setOrCreate('songList', songList);
// 2. 创建音频控制器(会自动创建 AVSession)
AudioRendererController.getInstance();
}
aboutToDisappear(): void {
// 释放资源
AudioRendererController.getInstance().release();
}
build() {
NavDestination() {
PlayerInfoComponent()
}
}
}
11.3 AudioRendererController 初始化
typescript
export class AudioRendererController {
constructor() {
// 1. 初始化 AudioRenderer
this.initAudioRenderer();
}
private initAudioRenderer() {
audio.createAudioRenderer(options, (err, data) => {
this.audioRenderer = data;
// 2. 设置回调
this.setAudioRendererCallbacks();
});
}
private setAudioRendererCallbacks() {
// 3. 创建 AVSession
this.avSessionController = AVSessionController.getInstance();
// 4. 设置其他回调
this.setWriteDataCallback();
this.setInterruptCallback();
this.setOutputDeviceChangeCallback();
}
}
11.4 AVSessionController 初始化
typescript
export class AVSessionController {
constructor() {
this.initAVSession();
}
private async initAVSession() {
// 1. 创建并激活会话
this.AVSession = await avSession.createAVSession(this.context, "PLAY_AUDIO", 'audio');
await this.AVSession.activate();
// 2. 设置元数据
await this.setAVMetadata();
// 3. 设置启动能力
this.setLaunchAbility();
// 4. 监听控制命令
this.setListenerForMesFromController();
}
}
十二、状态同步机制
12.1 状态流转图
用户操作 → AudioRendererController → AppStorage → UI 更新
↓
AVSessionController
↓
系统媒体控制中心显示
系统控制中心操作 → AVSessionController → AudioRendererController
↓
AppStorage → UI 更新
12.2 关键状态同步点
| 操作 | 同步内容 | 同步目标 |
|---|---|---|
| 播放/暂停 | 播放状态 | AppStorage + AVSession |
| 切歌 | 歌曲信息、元数据 | AppStorage + AVSession |
| 进度更新 | 播放进度 | AppStorage + AVSession |
| 模式切换 | 循环模式 | AppStorage + AVSession |
| 收藏切换 | 收藏状态 | AppStorage + AVSession + Preferences |
12.3 状态同步代码示例
typescript
// 在 AudioRendererController 中更新歌曲
private updateMusicIndex(musicIndex: number) {
// 1. 更新本地状态
this.musicIndex = musicIndex;
// 2. 同步到 AppStorage(UI 会自动更新)
AppStorage.setOrCreate('selectIndex', musicIndex);
// 3. 同步到 AVSession
if (this.avSessionController) {
this.avSessionController.setAVMetadata(); // 更新元数据
this.avSessionController.getAndUpdateFavoriteState(musicIndex.toString()); // 更新收藏状态
}
}
十三、注销监听器
13.1 为什么要注销?
- 避免内存泄漏
- 防止重复监听
- 确保资源正确释放
13.2 注销 AVSession 监听器
typescript
/**
* 注销所有 AVSession 监听器
*/
async unregisterSessionListener() {
if (!this.AVSession) return;
this.AVSession.off('play');
this.AVSession.off('pause');
this.AVSession.off('playNext');
this.AVSession.off('playPrevious');
this.AVSession.off('setLoopMode');
this.AVSession.off('seek');
this.AVSession.off('toggleFavorite');
Logger.info(TAG, '监听器注销成功');
}
13.3 在释放资源时调用
typescript
// 在 AudioRendererController 中
async release() {
if (!this.audioRenderer || !this.context) return;
try {
// 1. 释放 AudioRenderer
await this.audioRenderer.release();
// 2. 注销 AVSession 监听器
this.avSessionController?.unregisterSessionListener();
// 3. 停止后台任务
BackgroundUtil.stopContinuousTask(this.context);
Logger.info(TAG, '资源释放成功');
} catch (e) {
Logger.error(TAG, '资源释放失败');
}
}
十六、常见问题与解决方案
Q1: 控制中心不显示播放信息?
原因 : AVSession 未激活或元数据未设置
解决:
typescript
await this.AVSession.activate();
await this.setAVMetadata();
Q2: 点击控制中心无响应?
原因 : 未注册命令监听器
解决:
typescript
this.setListenerForMesFromController();
Q3: 后台播放被暂停?
原因 : 未申请后台任务或权限未配置
解决:
- 配置
KEEP_BACKGROUND_RUNNING权限 - 调用
BackgroundUtil.startContinuousTask()
Q4: 状态不同步?
原因 : 未在状态变化时同步到 AVSession
解决:
typescript
this.avSessionController.setPlayState(isPlay);
this.avSessionController.setProgressState(ms);
Q5: 切歌后封面不更新?
原因 : 未重新设置元数据
解决:
typescript
this.avSessionController.setAVMetadata();
总结
核心技术回顾
-
AVSession 媒体会话
- 创建和激活会话
- 设置媒体元数据
- 监听控制命令
- 同步播放状态
-
后台播放管理
- 申请后台任务权限
- 启动/停止长时任务
- 配置 WantAgent
-
状态同步机制
- AppStorage 全局状态
- AVSession 状态同步
- Preferences 持久化
开发要点
- AVSession 必须激活才能显示
- 状态变化及时同步到 AVSession
- 后台播放需要申请权限和长时任务
- 资源使用完毕及时释放
- 监听器使用完毕及时注销
参考资源
至此,HarmonyOS 音频播放器开发教程(上下篇)全部完成。结合两篇教程,你已经掌握了从底层音频播放到系统集成的完整技术栈,可以开发功能完善的音频播放应用。