HarmonyOS 后台音乐播放实战:AVSession 播控中心与音频焦点管理全解析
文章目录

每日一句正能量
“比别人的目光更可怕的是你那颗在意他人目光的心。”
你以为是别人的眼光困住了你,其实是你心里那个“在意”在作祟。外界的目光本身没有力量,是你赋予了它们审判权。当你不再把别人的看法当成镜子,那些目光就会像风吹过树叶,有声音,但无重量。
摘要
在移动应用开发中,后台音乐播放是音频类应用的核心能力。本文系统讲解 HarmonyOS 提供的后台音频播放方案,从 AVSession 播控中心接入、长时任务申请、音频焦点管理三个维度出发,结合 ArkTS 代码实战,帮助开发者构建稳定可靠的后台音乐播放系统。
一、引言:后台音乐播放的工程挑战
音乐播放器、有声书、播客等音频类应用,后台播放是刚需。然而,后台音频播放涉及多个系统能力的协同,开发者往往面临以下挑战:
- 退后台即静音:应用切到后台或锁屏后,音频被系统强制暂停,用户无法继续收听。
- 播控中心失联:锁屏界面和通知栏无法显示播放控制按钮,用户体验割裂。
- 多应用音频冲突:前台打开短视频或接听电话时,后台音乐没有正确暂停或恢复。
- 长时任务管理混乱:播放时申请了长时任务,暂停后忘记释放,导致系统资源浪费。
HarmonyOS 提供了 AVSession(音视频播控服务)、BackgroundTaskManager(后台任务管理) 和 AudioSession(音频焦点管理) 三大核心能力,三者协同才能构建完整的后台音乐播放体验。本文将系统梳理这三个能力的接入方法、交互逻辑和生产级最佳实践。
二、能力架构:三位一体的后台播放方案
HarmonyOS 后台音乐播放需要三个核心能力协同工作:

| 能力 | 职责 | 不接入的后果 |
|---|---|---|
| AVSession | 向系统播控中心注册媒体会话,同步元数据和播放状态 | 锁屏/通知栏不显示播控卡片,无法接收系统播控指令 |
| 长时任务 | 申请 AUDIO_PLAYBACK 类型后台任务,防止进程被回收 |
退后台后音频被系统静音并冻结,进程可能被回收 |
| 音频焦点 | 管理多应用音频共存策略,处理打断和恢复事件 | 前台应用播放音频时,后台音乐继续播放造成混音 |
核心原则:当应用需要后台播放
STREAM_USAGE_MUSIC、STREAM_USAGE_MOVIE、STREAM_USAGE_AUDIOBOOK或STREAM_USAGE_GAME类型的音频时,必须同时接入 AVSession 和申请长时任务。不满足此规范的应用,退至后台时会被系统静音并冻结,直到应用重新切回前台才会恢复。
三、AVSession 播控中心接入
3.1 AVSession 核心概念
AVSession(Audio/Video Session)是 HarmonyOS 提供的音视频播控服务,在应用与系统控制器(播控中心、语音助手)之间建立标准化的"会话"。应用只需向系统注册播放状态和控制命令,无需单独适配各种外设,即可实现"一次开发、处处可控"。
AVSession 的两种角色:
- 提供方(Provider):实际播放音视频的应用,如音乐播放器、视频 App。
- 控制方(Controller):向提供方发送播控指令的组件,如系统播控中心、语音助手、穿戴设备。
本文聚焦提供方开发,即音乐播放器如何接入 AVSession 并向系统注册自身。
3.2 接入流程

接入 AVSession 的完整流程如下:
- 创建 AVSession:在应用启动或开始播放业务前创建会话。
- 设置媒体元数据:同步歌曲标题、歌手、专辑、封面图等信息。
- 注册播控命令监听:响应系统下发的播放、暂停、切歌、 seek 等指令。
- 激活会话:调用
activate()使会话生效。 - 同步播放状态:播放状态变化时及时上报给系统。
- 接收并执行播控指令:在监听器中执行实际播放逻辑,并同步状态。
3.3 代码实战:AVSession 接入
// src/manager/AVSessionManager.ets
import { avSession } from '@kit.AVSessionKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';
/**
* 歌曲信息模型
*/
export interface SongInfo {
assetId: string; // 唯一标识
title: string; // 歌曲标题
artist: string; // 歌手
album: string; // 专辑
duration: number; // 总时长(ms)
coverUrl?: string; // 封面图URL
mediaUri: string; // 音频资源地址
}
/**
* AVSession 管理器
* 负责与系统播控中心交互,注册媒体会话并同步状态
*/
export class AVSessionManager {
private static instance: AVSessionManager | null = null;
private avSession: avSession.AVSession | undefined = undefined;
private currentSong: SongInfo | null = null;
// 播放状态回调(供外部订阅)
private onPlayCallback: (() => void) | null = null;
private onPauseCallback: (() => void) | null = null;
private onPlayNextCallback: (() => void) | null = null;
private onPlayPreviousCallback: (() => void) | null = null;
private onSeekCallback: ((time: number) => void) | null = null;
public static getInstance(): AVSessionManager {
if (!AVSessionManager.instance) {
AVSessionManager.instance = new AVSessionManager();
}
return AVSessionManager.instance;
}
/**
* 创建并激活 AVSession
*/
public async createSession(context: common.UIAbilityContext): Promise<void> {
if (this.avSession) {
console.info('[AVSession] 会话已存在,跳过创建');
return;
}
try {
this.avSession = await avSession.createAVSession(context, 'MusicPlayer', 'audio');
console.info('[AVSession] 会话创建成功');
// 注册播控命令监听
this.registerCommandListeners();
// 激活会话
await this.avSession.activate();
console.info('[AVSession] 会话已激活');
// 设置后台播放模式
await this.avSession.setBackgroundPlayMode(avSession.BackgroundPlayMode.ENABLE_BACKGROUND_PLAY);
console.info('[AVSession] 后台播放模式已设置');
} catch (error) {
const err = error as BusinessError;
console.error(`[AVSession] 创建会话失败: ${err.code}, ${err.message}`);
throw error;
}
}
/**
* 注册播控命令监听器
*/
private registerCommandListeners(): void {
if (!this.avSession) return;
// 播放指令
this.avSession.on('play', () => {
console.info('[AVSession] 收到播控中心: play');
this.onPlayCallback?.();
});
// 暂停指令
this.avSession.on('pause', () => {
console.info('[AVSession] 收到播控中心: pause');
this.onPauseCallback?.();
});
// 下一首
this.avSession.on('playNext', () => {
console.info('[AVSession] 收到播控中心: playNext');
this.onPlayNextCallback?.();
});
// 上一首
this.avSession.on('playPrevious', () => {
console.info('[AVSession] 收到播控中心: playPrevious');
this.onPlayPreviousCallback?.();
});
// 进度跳转
this.avSession.on('seek', (time: number) => {
console.info(`[AVSession] 收到播控中心: seek to ${time}ms`);
this.onSeekCallback?.(time);
});
// 快进
this.avSession.on('fastForward', () => {
console.info('[AVSession] 收到播控中心: fastForward');
this.onSeekCallback?.((this.currentSong?.duration || 0) * 0.1);
});
// 快退
this.avSession.on('rewind', () => {
console.info('[AVSession] 收到播控中心: rewind');
this.onSeekCallback?.(-(this.currentSong?.duration || 0) * 0.1);
});
// 设置播放模式(循环/随机)
this.avSession.on('setLoopMode', (mode: avSession.LoopMode) => {
console.info(`[AVSession] 收到播控中心: setLoopMode=${mode}`);
});
// 收藏
this.avSession.on('toggleFavorite', (assetId: string) => {
console.info(`[AVSession] 收到播控中心: toggleFavorite assetId=${assetId}`);
});
}
/**
* 设置当前播放歌曲的元数据
*/
public async setMediaMetadata(song: SongInfo): Promise<void> {
if (!this.avSession) return;
this.currentSong = song;
const metadata: avSession.AVMetadata = {
assetId: song.assetId,
title: song.title,
artist: song.artist,
album: song.album,
duration: song.duration,
mediaType: avSession.AVMediaType.AUDIO,
displayTags: avSession.DisplayTag.TAG_AUDIO_VIVID
};
try {
await this.avSession.setAVMetadata(metadata);
console.info(`[AVSession] 元数据已同步: ${song.title} - ${song.artist}`);
} catch (error) {
console.error('[AVSession] 设置元数据失败:', error);
}
}
/**
* 同步播放状态到播控中心
*/
public async setPlaybackState(state: avSession.AVPlaybackState): Promise<void> {
if (!this.avSession) return;
try {
await this.avSession.setAVPlaybackState(state);
console.info(`[AVSession] 播放状态已同步: state=${state.state}`);
} catch (error) {
console.error('[AVSession] 同步播放状态失败:', error);
}
}
/**
* 设置播放状态为播放中
*/
public async setPlayingState(position: number, speed: number = 1.0): Promise<void> {
const state: avSession.AVPlaybackState = {
state: avSession.PlaybackState.PLAYBACK_STATE_PLAY,
position: { elapsedTime: position, updateTime: Date.now() },
speed: speed
};
await this.setPlaybackState(state);
}
/**
* 设置播放状态为暂停
*/
public async setPausedState(position: number): Promise<void> {
const state: avSession.AVPlaybackState = {
state: avSession.PlaybackState.PLAYBACK_STATE_PAUSE,
position: { elapsedTime: position, updateTime: Date.now() }
};
await this.setPlaybackState(state);
}
/**
* 设置播放状态为停止
*/
public async setStoppedState(): Promise<void> {
const state: avSession.AVPlaybackState = {
state: avSession.PlaybackState.PLAYBACK_STATE_STOP
};
await this.setPlaybackState(state);
}
/**
* 注册播放控制回调
*/
public registerCallbacks(callbacks: {
onPlay?: () => void;
onPause?: () => void;
onPlayNext?: () => void;
onPlayPrevious?: () => void;
onSeek?: (time: number) => void;
}): void {
this.onPlayCallback = callbacks.onPlay || null;
this.onPauseCallback = callbacks.onPause || null;
this.onPlayNextCallback = callbacks.onPlayNext || null;
this.onPlayPreviousCallback = callbacks.onPlayPrevious || null;
this.onSeekCallback = callbacks.onSeek || null;
}
/**
* 释放 AVSession
*/
public async release(): Promise<void> {
if (!this.avSession) return;
try {
await this.avSession.deactivate();
await this.avSession.destroy();
this.avSession = undefined;
console.info('[AVSession] 会话已释放');
} catch (error) {
console.error('[AVSession] 释放会话失败:', error);
}
}
}
3.4 关键注意事项
- 创建时机:建议在应用启动或开始播放业务前创建 AVSession,避免频繁创建和释放影响播放连续性。
- 对象保持:后台播放期间需确保 AVSession 对象实例一直存在,使用类成员变量而非局部变量保存。
- 状态同步:应用必须及时响应播控指令并同步播放状态,否则播控中心会显示"假播放"(进度走但实际已暂停)。
- 元数据完整性:必须设置标题、副标题/歌手、封面图等元数据,否则播控中心展示不完整。
四、长时任务申请与管理
4.1 为什么需要长时任务
即使正确接入了 AVSession,如果未申请长时任务,应用退至后台后仍会被系统静音并冻结。长时任务是应用在后台继续执行播放逻辑的"权限通行证"。
4.2 长时任务生命周期管理
长时任务的管理需要遵循以下规范:
- 播放时申请:开始播放时申请
AUDIO_PLAYBACK类型长时任务。 - 暂停时释放:用户主动点击暂停时,及时取消长时任务。
- 恢复时重新申请:用户再次点击播放时,重新申请长时任务。
- 被打断时处理:音频在后台播放时被打断(如焦点打断),系统会自行检测并冻结或取消长时任务。当应用重启音频播放时,需要再次申请。
- 投播时管理:通过 AVSession 投播组件后台投播时,开始投播申请长时任务,断开投播时取消。
4.3 代码实战:长时任务管理
// src/manager/BackgroundTaskManager.ets
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
import { wantAgent } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';
/**
* 后台长时任务管理器
*/
export class AudioBackgroundTaskManager {
private static instance: AudioBackgroundTaskManager | null = null;
private isRunning: boolean = false;
public static getInstance(): AudioBackgroundTaskManager {
if (!AudioBackgroundTaskManager.instance) {
AudioBackgroundTaskManager.instance = new AudioBackgroundTaskManager();
}
return AudioBackgroundTaskManager.instance;
}
/**
* 启动 AUDIO_PLAYBACK 长时任务
*/
public async startAudioPlayback(context: common.UIAbilityContext): Promise<void> {
if (this.isRunning) {
console.info('[BackgroundTask] 长时任务已在运行');
return;
}
try {
const wantAgentObj = await wantAgent.getWantAgent(context, {
wants: [{
bundleName: context.abilityInfo.bundleName,
abilityName: context.abilityInfo.name
}],
operationType: wantAgent.OperationType.START_ABILITIES,
requestCode: 0,
wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
});
await backgroundTaskManager.startBackgroundRunning(
context,
backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK,
wantAgentObj
);
this.isRunning = true;
console.info('[BackgroundTask] AUDIO_PLAYBACK 长时任务已启动');
} catch (error) {
const err = error as BusinessError;
console.error(`[BackgroundTask] 启动长时任务失败: ${err.code}, ${err.message}`);
}
}
/**
* 停止 AUDIO_PLAYBACK 长时任务
*/
public async stopAudioPlayback(context: common.UIAbilityContext): Promise<void> {
if (!this.isRunning) return;
try {
await backgroundTaskManager.stopBackgroundRunning(context);
this.isRunning = false;
console.info('[BackgroundTask] AUDIO_PLAYBACK 长时任务已停止');
} catch (error) {
const err = error as BusinessError;
console.error(`[BackgroundTask] 停止长时任务失败: ${err.code}, ${err.message}`);
}
}
public getIsRunning(): boolean {
return this.isRunning;
}
}
五、音频焦点管理
5.1 为什么需要音频焦点
在多个音频流同时播放的场景下,如果系统不加管控,会造成多个音频混音播放,用户体验极差。HarmonyOS 预设了音频打断(InterruptEvent)策略,对所有播放音频流进行统一管理。
当其他音频流申请焦点时,系统会根据焦点策略进行仲裁,判定本音频流的焦点是否有变化,并通过音频焦点事件通知应用执行相应操作(如暂停、继续、降低音量、恢复音量等)。

5.2 焦点模式选择
HarmonyOS 提供了四种并发模式,应用应根据业务场景选择合适的策略:
| 并发模式 | 行为 | 适用场景 |
|---|---|---|
CONCURRENCY_DEFAULT |
使用系统默认策略 | 不关心并发的场景 |
CONCURRENCY_MIX_WITH_OTHERS |
与其他音频同时播放 | 导航播报、背景音效 |
CONCURRENCY_DUCK_OTHERS |
自己播放时压低其他音量 | 语音助手、语音消息 |
CONCURRENCY_PAUSE_OTHERS |
暂停其他音频流 | 音乐播放器、有声书 |
优先级规则:
STOP > PAUSE > DUCK > PLAYBOTH。如果指定的策略优先级高于系统默认值,策略不会生效。
5.3 代码实战:音频焦点管理
// src/manager/AudioFocusManager.ets
import { audio } from '@kit.AudioKit';
import { BusinessError } from '@kit.BasicServicesKit';
/**
* 音频焦点管理器
* 负责管理应用音频焦点,处理打断和恢复事件
*/
export class AudioFocusManager {
private static instance: AudioFocusManager | null = null;
private sessionManager: audio.AudioSessionManager | null = null;
private isActive: boolean = false;
private onFocusChanged?: (event: audio.AudioSessionStateChangedEvent) => void;
public static getInstance(): AudioFocusManager {
if (!AudioFocusManager.instance) {
AudioFocusManager.instance = new AudioFocusManager();
}
return AudioFocusManager.instance;
}
private constructor() {
const audioManager = audio.getAudioManager();
this.sessionManager = audioManager.getSessionManager();
}
/**
* 激活音频会话(申请焦点)
* 适用于音乐播放器、有声书等需要连续播放的场景
*/
public async activateWithScene(
mode: audio.AudioConcurrencyMode = audio.AudioConcurrencyMode.CONCURRENCY_PAUSE_OTHERS
): Promise<void> {
if (!this.sessionManager) {
throw new Error('AudioSessionManager 未初始化');
}
if (this.isActive) {
console.info('[AudioFocus] 会话已激活');
return;
}
try {
const scene: audio.AudioSessionScene = { concurrencyMode: mode };
this.sessionManager.setAudioSessionScene(scene);
this.sessionManager.on('audioSessionStateChanged', this.handleStateChanged);
await this.sessionManager.activateAudioSession();
this.isActive = true;
console.info(`[AudioFocus] 音频会话已激活,模式: ${mode}`);
} catch (error) {
const err = error as BusinessError;
console.error(`[AudioFocus] 激活失败: ${err.code}, ${err.message}`);
throw error;
}
}
/**
* 停用音频会话
*/
public async deactivate(): Promise<void> {
if (!this.sessionManager || !this.isActive) return;
try {
this.sessionManager.off('audioSessionStateChanged', this.handleStateChanged);
await this.sessionManager.deactivateAudioSession();
this.isActive = false;
console.info('[AudioFocus] 音频会话已停用');
} catch (error) {
const err = error as BusinessError;
console.error(`[AudioFocus] 停用失败: ${err.code}, ${err.message}`);
}
}
private handleStateChanged = (event: audio.AudioSessionStateChangedEvent): void => {
console.info(`[AudioFocus] 状态变化: ${event.stateChangeHint}`);
this.onFocusChanged?.(event);
};
public onFocusChange(callback: (event: audio.AudioSessionStateChangedEvent) => void): void {
this.onFocusChanged = callback;
}
public getIsActive(): boolean {
return this.isActive;
}
}
5.4 打断恢复的关键流程
当应用收到 RESUME 事件时,系统只通知 session 不通知 AudioRenderer,需要手动执行三步恢复:
/**
* 恢复播放三步走
*/
private async handleResume(): Promise<void> {
if (!this.sessionManager) return;
// 第一步:重设场景
const scene: audio.AudioSessionScene = {
concurrencyMode: audio.AudioConcurrencyMode.CONCURRENCY_PAUSE_OTHERS
};
this.sessionManager.setAudioSessionScene(scene);
// 第二步:重新激活
await this.sessionManager.activateAudioSession();
// 第三步:启动渲染器(或 AVPlayer)
// this.avPlayer.play();
console.info('[AudioFocus] 播放已恢复');
}
六、完整播放器集成
6.1 播放器状态机
// src/model/PlayerTypes.ets
export enum PlayerState {
IDLE = 'idle',
INITIALIZED = 'initialized',
PREPARED = 'prepared',
PLAYING = 'playing',
PAUSED = 'paused',
STOPPED = 'stopped',
COMPLETED = 'completed',
ERROR = 'error'
}
export interface PlayQueue {
songs: SongInfo[];
currentIndex: number;
loopMode: 'loop_all' | 'loop_one' | 'shuffle';
}
6.2 音乐播放器核心实现
// src/manager/MusicPlayer.ets
import { media } from '@kit.MediaKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';
import { AVSessionManager } from './AVSessionManager';
import { AudioBackgroundTaskManager } from './BackgroundTaskManager';
import { AudioFocusManager } from './AudioFocusManager';
import { SongInfo, PlayerState, PlayQueue } from '../model/PlayerTypes';
/**
* 音乐播放器核心控制器
* 集成 AVPlayer + AVSession + 长时任务 + 音频焦点
*/
export class MusicPlayer {
private static instance: MusicPlayer | null = null;
private avPlayer: media.AVPlayer | null = null;
private avSessionMgr: AVSessionManager = AVSessionManager.getInstance();
private bgTaskMgr: AudioBackgroundTaskManager = AudioBackgroundTaskManager.getInstance();
private focusMgr: AudioFocusManager = AudioFocusManager.getInstance();
private playQueue: PlayQueue = { songs: [], currentIndex: 0, loopMode: 'loop_all' };
private playerState: PlayerState = PlayerState.IDLE;
private currentPosition: number = 0;
private playbackSpeed: number = 1.0;
private stateListeners: Array<(state: PlayerState) => void> = [];
private progressListeners: Array<(position: number, duration: number) => void> = [];
public static getInstance(): MusicPlayer {
if (!MusicPlayer.instance) {
MusicPlayer.instance = new MusicPlayer();
}
return MusicPlayer.instance;
}
private constructor() {
this.initAVSessionCallbacks();
}
/**
* 初始化播放器
*/
public async initialize(context: common.UIAbilityContext): Promise<void> {
await this.avSessionMgr.createSession(context);
this.avPlayer = await media.createAVPlayer();
this.setupAVPlayerCallbacks();
console.info('[MusicPlayer] 播放器初始化完成');
}
/**
* 设置播放队列
*/
public setPlaylist(songs: SongInfo[], startIndex: number = 0): void {
this.playQueue = { songs, currentIndex: startIndex, loopMode: 'loop_all' };
}
/**
* 播放指定歌曲
*/
public async play(song?: SongInfo): Promise<void> {
const targetSong = song || this.playQueue.songs[this.playQueue.currentIndex];
if (!targetSong) {
console.error('[MusicPlayer] 播放列表为空');
return;
}
try {
// 1. 申请音频焦点
await this.focusMgr.activateWithScene();
// 2. 申请后台长时任务
const context = getContext(this) as common.UIAbilityContext;
await this.bgTaskMgr.startAudioPlayback(context);
// 3. 设置 AVSession 元数据
await this.avSessionMgr.setMediaMetadata(targetSong);
// 4. 准备并播放
if (this.avPlayer) {
if (this.playerState === PlayerState.PLAYING || this.playerState === PlayerState.PAUSED) {
this.avPlayer.stop();
}
this.avPlayer.url = targetSong.mediaUri;
await this.avPlayer.prepare();
await this.avPlayer.play();
this.playerState = PlayerState.PLAYING;
this.currentPosition = 0;
// 5. 同步播放状态到播控中心
await this.avSessionMgr.setPlayingState(0, this.playbackSpeed);
console.info(`[MusicPlayer] 开始播放: ${targetSong.title}`);
this.notifyStateChange(PlayerState.PLAYING);
}
} catch (error) {
this.playerState = PlayerState.ERROR;
console.error('[MusicPlayer] 播放失败:', error);
this.notifyStateChange(PlayerState.ERROR);
}
}
/**
* 暂停播放
*/
public async pause(): Promise<void> {
if (!this.avPlayer || this.playerState !== PlayerState.PLAYING) return;
try {
await this.avPlayer.pause();
this.playerState = PlayerState.PAUSED;
this.currentPosition = this.avPlayer.currentTime;
await this.avSessionMgr.setPausedState(this.currentPosition);
const context = getContext(this) as common.UIAbilityContext;
await this.bgTaskMgr.stopAudioPlayback(context);
console.info('[MusicPlayer] 播放已暂停');
this.notifyStateChange(PlayerState.PAUSED);
} catch (error) {
console.error('[MusicPlayer] 暂停失败:', error);
}
}
/**
* 恢复播放
*/
public async resume(): Promise<void> {
if (!this.avPlayer || this.playerState !== PlayerState.PAUSED) return;
try {
await this.focusMgr.activateWithScene();
const context = getContext(this) as common.UIAbilityContext;
await this.bgTaskMgr.startAudioPlayback(context);
await this.avPlayer.play();
this.playerState = PlayerState.PLAYING;
await this.avSessionMgr.setPlayingState(this.currentPosition, this.playbackSpeed);
console.info('[MusicPlayer] 播放已恢复');
this.notifyStateChange(PlayerState.PLAYING);
} catch (error) {
console.error('[MusicPlayer] 恢复播放失败:', error);
}
}
/**
* 停止播放
*/
public async stop(): Promise<void> {
if (!this.avPlayer) return;
try {
await this.avPlayer.stop();
this.playerState = PlayerState.STOPPED;
this.currentPosition = 0;
await this.avSessionMgr.setStoppedState();
const context = getContext(this) as common.UIAbilityContext;
await this.bgTaskMgr.stopAudioPlayback(context);
await this.focusMgr.deactivate();
console.info('[MusicPlayer] 播放已停止');
this.notifyStateChange(PlayerState.STOPPED);
} catch (error) {
console.error('[MusicPlayer] 停止失败:', error);
}
}
/**
* 播放下一首
*/
public async playNext(): Promise<void> {
if (this.playQueue.songs.length === 0) return;
this.playQueue.currentIndex = (this.playQueue.currentIndex + 1) % this.playQueue.songs.length;
await this.play();
}
/**
* 播放上一首
*/
public async playPrevious(): Promise<void> {
if (this.playQueue.songs.length === 0) return;
this.playQueue.currentIndex = (this.playQueue.currentIndex - 1 + this.playQueue.songs.length) % this.playQueue.songs.length;
await this.play();
}
/**
* 跳转到指定位置
*/
public async seekTo(position: number): Promise<void> {
if (!this.avPlayer) return;
try {
await this.avPlayer.seek(position);
this.currentPosition = position;
console.info(`[MusicPlayer] 跳转到: ${position}ms`);
} catch (error) {
console.error('[MusicPlayer] 跳转失败:', error);
}
}
/**
* 设置 AVPlayer 回调
*/
private setupAVPlayerCallbacks(): void {
if (!this.avPlayer) return;
this.avPlayer.on('stateChange', async (state: media.AVPlayerState, reason: media.StateChangeReason) => {
console.info(`[MusicPlayer] AVPlayer 状态变化: ${state}, reason: ${reason}`);
if (state === 'completed') {
this.playerState = PlayerState.COMPLETED;
if (this.playQueue.loopMode === 'loop_one') {
await this.play();
} else {
await this.playNext();
}
}
});
this.avPlayer.on('error', (err: BusinessError) => {
console.error(`[MusicPlayer] AVPlayer 错误: ${err.code}, ${err.message}`);
this.playerState = PlayerState.ERROR;
this.notifyStateChange(PlayerState.ERROR);
});
this.avPlayer.on('timeUpdate', (time: number) => {
this.currentPosition = time;
const duration = this.avPlayer?.duration || 0;
this.notifyProgressChange(time, duration);
});
}
private initAVSessionCallbacks(): void {
this.avSessionMgr.registerCallbacks({
onPlay: () => this.resume(),
onPause: () => this.pause(),
onPlayNext: () => this.playNext(),
onPlayPrevious: () => this.playPrevious(),
onSeek: (time: number) => this.seekTo(time)
});
}
public onStateChange(listener: (state: PlayerState) => void): void {
this.stateListeners.push(listener);
}
public onProgressUpdate(listener: (position: number, duration: number) => void): void {
this.progressListeners.push(listener);
}
private notifyStateChange(state: PlayerState): void {
this.stateListeners.forEach(l => l(state));
}
private notifyProgressChange(position: number, duration: number): void {
this.progressListeners.forEach(l => l(position, duration));
}
/**
* 释放播放器资源
*/
public async release(): Promise<void> {
await this.stop();
if (this.avPlayer) {
this.avPlayer.release();
this.avPlayer = null;
}
await this.avSessionMgr.release();
console.info('[MusicPlayer] 播放器资源已释放');
}
}
七、权限配置与模块声明
7.1 module.json5 配置
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
},
{
"name": "ohos.permission.KEEP_BACKGROUND_RUNNING"
},
{
"name": "ohos.permission.MEDIA_CONTROL"
}
],
"abilities": [
{
"name": "EntryAbility",
"backgroundModes": [
"audioPlayback"
]
}
]
}
}
7.2 关键配置说明
ohos.permission.KEEP_BACKGROUND_RUNNING:申请后台长时任务权限,必须声明。ohos.permission.MEDIA_CONTROL:媒体控制权限,用于 AVSession 与系统播控中心交互。backgroundModes: ["audioPlayback"]:声明应用支持音频播放后台模式。
八、测试验收清单
8.1 生命周期测试
- 前台播放时切到桌面,音频是否继续播放
- 息屏锁屏后,音频是否正常播放
- 锁屏界面是否显示播控卡片(封面/标题/控制按钮)
- 通知栏是否显示播放通知
- 应用被系统回收后重新启动,播放状态是否正确恢复
8.2 播控中心交互测试
- 点击锁屏播控卡片的播放/暂停,应用是否正确响应
- 点击下一首/上一首,是否正确切换歌曲
- 拖动进度条,是否正确跳转到对应位置
- 播控中心显示的歌曲信息是否与当前播放一致
- 播放状态变化时,播控中心是否及时更新
8.3 音频焦点测试
- 后台播放时接听电话,音乐是否正确暂停
- 电话挂断后,音乐是否自动恢复播放
- 后台播放时打开短视频 App,音乐是否正确暂停
- 关闭短视频 App 后,音乐是否自动恢复
- 导航播报时,音乐是否正确压低音量(DUCK)
- 导航播报结束后,音乐音量是否恢复正常
8.4 长时任务管理测试
- 播放时是否正确申请了 AUDIO_PLAYBACK 长时任务
- 暂停时是否正确释放了长时任务
- 恢复播放时是否重新申请了长时任务
- 长时间暂停后,进程是否被系统正常回收
九、常见问题与最佳实践
Q1:为什么退后台后音频被静音了?
A:检查三点:(1) 是否正确接入了 AVSession;(2) 是否申请了 AUDIO_PLAYBACK 长时任务;(3) module.json5 中是否声明了 audioPlayback 后台模式。三者缺一不可。
Q2:播控中心显示"假播放"(进度在走但实际已暂停)怎么办?
A:这是因为应用被系统挂起后,来不及调用 AVSession 同步暂停状态。确保在收到暂停指令或检测到播放中断时,立即调用 setAVPlaybackState 上报状态。同时检查长时任务是否有效。
Q3:如何支持蓝牙耳机控制?
A:只要正确接入 AVSession 并注册了播放/暂停/切歌指令监听,蓝牙耳机控制会自动生效。系统会将蓝牙指令转发到 AVSession 的对应回调中。
Q4:播放网络音频时如何优化起播速度?
A:使用 AVPlayer 的 setBufferingStrategy 设置预加载策略,或采用 HLS/DASH 协议实现自适应码率。同时确保网络请求使用合适的缓存策略。
Q5:多个页面如何共享同一个播放器实例?
A:使用单例模式(如本文的 MusicPlayer.getInstance()),在应用启动时初始化,所有页面通过同一个实例控制播放。避免每个页面独立创建 AVPlayer 造成资源冲突。
十、总结
HarmonyOS 的后台音乐播放需要 AVSession、长时任务和音频焦点三大能力协同工作。本文从架构设计到代码实现,系统梳理了完整的后台音频播放方案。核心设计要点总结如下:
- AVSession 是播控中心与应用的桥梁:负责向系统注册媒体信息、接收播控指令、同步播放状态。
- 长时任务是后台保活的通行证:
AUDIO_PLAYBACK类型长时任务确保应用退后台后不被系统回收。 - 音频焦点是多应用共存的基础:通过合适的并发模式,确保音乐播放器与电话、导航、短视频等应用和平共处。
- 状态同步是用户体验的关键:应用必须及时响应播控指令并同步状态,避免"假播放"现象。
- 生命周期管理决定资源消耗:播放时申请、暂停时释放、恢复时重新申请,避免长时任务空转。
- 恢复播放需要手动三步重连:收到 RESUME 事件后,重设场景、重新激活、启动渲染器,缺一不可。
希望本文能帮助开发者在 HarmonyOS 应用中构建稳定、流畅、用户友好的后台音乐播放系统。
转载自:https://blog.csdn.net/u014727709/article/details/163730136
欢迎 👍点赞✍评论⭐收藏,欢迎指正
更多推荐



所有评论(0)