引言
在音频类应用开发中,锁屏状态下的音频播放与播控能力是一项核心用户体验需求。当用户锁屏后,系统播控中心需要展示当前播放的音频信息,并支持用户进行播放/暂停、切歌、快进快退等操作。本文将详细介绍如何在 HarmonyOS 上实现完整的锁屏音频播放功能。
一、技术栈与核心概念
1.1 AVSession Kit 简介
AVSession Kit(音频媒体会话套件)是 HarmonyOS 官方提供的一套用于管理媒体播放会话的 API。通过它,应用可以将当前播放的媒体信息(标题、作者、封面、播放进度等)同步到系统播控中心,并接收用户从锁屏、控制中心下发的播控指令。
1.2 核心类与接口
| 类/接口 | 用途 |
|---|---|
createAVSession() |
创建媒体会话实例 |
AVSession.activate() |
激活会话,开始与系统播控中心交互 |
AVSession.setAVMetadata() |
设置媒体元数据(标题、作者、封面等) |
AVSession.setAVPlaybackState() |
设置播放状态(播放/暂停、进度、倍速等) |
AVSession.on(event, callback) |
注册播控命令监听器 |
1.3 会话类型(AVSessionType)
系统播控中心展示的按钮样式由会话类型决定:
audio类型:显示「上一首、播放/暂停、下一首」等音频类按钮video类型:显示「快退、上一首、播放/暂停、下一首、快进」等视频类按钮
选择正确的会话类型对播控中心展示效果至关重要。
二、权限与后台能力配置
2.1 后台播放模式
要实现锁屏后继续播放,需要在应用的能力配置中声明后台播放模式:
json
{
"backgroundModes": ["audioPlayback"]
}
这告诉系统:本应用在后台时需要保持音频播放能力。
2.2 权限声明
同时需要在模块配置中声明媒体播放权限:
json
{
"requestPermissions": [
{
"name": "ohos.permission.MEDIA_PLAYBACK"
}
]
}
三、完整实现步骤
步骤1:创建并激活 AVSession
在应用启动或音频播放初始化时,创建媒体会话:
typescript
import { avSession as AVSessionKit } from '@kit.AVSessionKit';
import { common } from '@kit.AbilityKit';
// 创建 AVSession
const session = await AVSessionKit.createAVSession(
context, // 应用上下文
'AudioSession', // 会话标识
'audio' // 会话类型:'audio' 或 'video'
);
// 激活会话
await session.activate();
注意:创建会话后应立即激活(activate),这样系统播控中心才能感知到你的应用在播放音频。
步骤2:注册播控命令监听器
监听用户从锁屏/控制中心发出的播控指令,并在回调中执行对应的播放逻辑:
typescript
// 播放
session.on('play', () => {
// 调用播放器的播放方法
});
// 暂停
session.on('pause', () => {
// 调用播放器的暂停方法
});
// 上一首
session.on('playPrevious', () => {
// 切换到上一首音频
});
// 下一首
session.on('playNext', () => {
// 切换到下一首音频
});
// 进度跳转(seek)
session.on('seek', (seekTime: number) => {
// seekTime 单位为毫秒,调用播放器的 seek 方法
});
// 快退
session.on('rewind', () => {
// 向后跳转指定时长(如15秒)
});
// 快进
session.on('fastForward', () => {
// 向前跳转指定时长(如15秒)
});
步骤3:同步媒体元数据
当切换音频时,将新音频的元数据同步到系统播控中心:
typescript
import { avSession as AVSessionKit } from '@kit.AVSessionKit';
const metadata: AVSessionKit.AVMetadata = {
assetId: '1001', // 媒体唯一ID
title: '音频标题', // 标题
artist: '作者名称', // 作者
duration: 180000, // 总时长(毫秒)
mediaImage: pixelMapOrUrl // 封面图片(PixelMap 对象或图片URL)
};
session.setAVMetadata(metadata).then(() => {
// 元数据设置成功
}).catch((err) => {
// 处理错误
});
步骤4:同步播放状态与进度
播放状态变化(播放/暂停)或进度更新时,同步给系统:
typescript
const playbackState: AVSessionKit.AVPlaybackState = {
state: AVSessionKit.PlaybackState.PLAYBACK_STATE_PLAY, // 或 PLAYBACK_STATE_PAUSE
speed: 1.0,
position: {
elapsedTime: 30000, // 当前播放位置(毫秒)
updateTime: new Date().getTime() // 更新时间戳
},
duration: 180000 // 总时长(毫秒)
};
session.setAVPlaybackState(playbackState);
关键提示 :
position.updateTime字段很重要,系统播控中心会根据这个时间戳和elapsedTime来计算当前进度显示。
步骤5:封面图片处理
锁屏播控中心的封面图片可以是以下两种形式:
方式一:直接使用图片 URL
typescript
const metadata: AVSessionKit.AVMetadata = {
// ... 其他字段
mediaImage: 'https://example.com/cover.jpg'
};
方式二:下载图片并转换为 PixelMap
如果需要更高质量的展示效果,可以先下载图片并转换为 PixelMap 对象:
typescript
import { image } from '@kit.ImageKit';
import { http } from '@kit.NetworkKit';
async function downloadImageAsPixelMap(imageUrl: string): Promise<image.PixelMap | null> {
try {
// 1. 通过 HTTP 下载图片数据
const request = http.createHttp();
const response = await request.request(imageUrl, {
method: http.RequestMethod.GET,
expectDataType: http.HttpDataType.ARRAY_BUFFER
});
const arrayBuffer = response.result as ArrayBuffer;
request.destroy();
// 2. 创建图片源
const imageSource = image.createImageSource(arrayBuffer);
// 3. 创建 PixelMap
const decodingOptions: image.DecodingOptions = {
editable: false,
desiredPixelFormat: 3, // RGBA_8888
desiredDynamicRange: image.DecodingDynamicRange.AUTO
};
const pixelMap = await imageSource.createPixelMap(decodingOptions);
imageSource.release();
return pixelMap;
} catch (err) {
return null;
}
}
四、快进与快退的实现
4.1 选择正确的会话类型
要在锁屏播控中心显示快进快退按钮,需要使用 video 类型的会话:
typescript
// 使用 video 类型,播控中心将显示快退和快进按钮
const session = await AVSessionKit.createAVSession(context, 'AudioSession', 'video');
4.2 实现快进快退逻辑
typescript
// 快退 15 秒
function rewind(currentPosition: number): number {
const REWIND_TIME = 15 * 1000; // 15秒
const newPosition = Math.max(0, currentPosition - REWIND_TIME);
// 调用播放器 seek 到 newPosition
// 同步新的播放状态到 AVSession
return newPosition;
}
// 快进 15 秒
function fastForward(currentPosition: number, duration: number): number {
const FORWARD_TIME = 15 * 1000; // 15秒
const newPosition = Math.min(duration, currentPosition + FORWARD_TIME);
// 调用播放器 seek 到 newPosition
// 同步新的播放状态到 AVSession
return newPosition;
}
// 注册快退快进监听器
session.on('rewind', () => {
const newPos = rewind(currentPositionRef.current);
currentPositionRef.current = newPos;
syncPlaybackStateToSession(true, newPos, durationRef.current);
});
session.on('fastForward', () => {
const newPos = fastForward(currentPositionRef.current, durationRef.current);
currentPositionRef.current = newPos;
syncPlaybackStateToSession(true, newPos, durationRef.current);
});
五、前后台切换与状态管理
5.1 从后台恢复时的状态处理
当应用从后台(锁屏状态)切换回前台时,需要注意:
- 不要中断正在播放的音频:如果播放器正在播放,保持播放状态
- 同步最新的播放进度:确保切换时的进度是最新的
typescript
function onAppForeground(): void {
// 检查播放器状态
if (playerState === 'playing' || playerState === 'paused' || playerState === 'prepared') {
// 播放器正在工作中,不中断播放
return;
}
// 其他恢复逻辑...
}
5.2 切换歌曲时的状态重置
每次切换到新的音频时,必须重置以下状态:
currentTime(当前播放时间):重置为 0durationTime(总时长):重置为新音频的时长- AVSession 播放状态:同步新的进度为 0
typescript
// 切换歌曲时
function switchToNewAudio(): void {
// 1. 重置本地状态
currentTime = 0;
durationTime = 0;
// 2. 设置新的音频 URL 并开始播放
// 3. 同步元数据到 AVSession
session.setAVMetadata({
assetId: newAudio.id,
title: newAudio.title,
artist: newAudio.artist,
duration: newAudio.duration,
mediaImage: newAudio.coverImage
});
// 4. 同步播放状态,进度从 0 开始
session.setAVPlaybackState({
state: AVSessionKit.PlaybackState.PLAYBACK_STATE_PLAY,
speed: 1.0,
position: {
elapsedTime: 0,
updateTime: new Date().getTime()
},
duration: newAudio.duration
});
}
六、常见问题与最佳实践
❌ 问题1:锁屏播控中心没有立即显示
原因:可能是元数据设置时机不对,或者会话未正确激活。
解决:
- 确保
createAVSession()后立即调用activate() - 在播放器开始播放后立即调用
setAVMetadata()和setAVPlaybackState() - 添加日志确认 API 调用成功
❌ 问题2:封面图片不显示
原因:封面图片的格式或大小不符合要求,或者下载失败。
解决:
- 优先使用 PixelMap 格式(比 URL 更可靠)
- 控制图片大小(建议 400x400 像素以内)
- 确保图片下载成功(添加错误处理日志)
❌ 问题3:切换歌曲后进度条显示旧的进度
原因:切换歌曲时没有重置播放进度和时长,导致 AVSession 保存了旧的进度信息。
解决:
- 每次切换歌曲时重置
currentTime = 0和durationTime = 新时长 - 同步
setAVPlaybackState()时将elapsedTime设为 0 - 同步
setAVMetadata()时将duration设为新音频的时长
❌ 问题4:解锁后播放被中断
原因 :应用从后台切回前台时,onForeground 逻辑意外地重启了播放器。
解决:
- 在
onForeground回调中先检查播放器当前状态 - 如果播放器正在
playing/paused/prepared,直接返回不做任何操作 - 只在播放器状态异常时才执行恢复逻辑
七、完整流程时序图
下面是一次完整的锁屏播放交互流程:
scss
用户操作 应用侧 系统播控中心
│ │ │
├─ 点击播放 │ │
│ ─────────────────────────>│ │
│ │ 1. 创建并激活 AVSession │
│ │ 2. setAVMetadata() │
│ │ 3. setAVPlaybackState() │
│ │ ─────────────────────────> │
│ │ │ 显示音频信息
│ │ │ 和播放控制按钮
│ │ │
├─ 锁屏 │ │
│ ──────────────────────────┤───────────────────────────▶│
│ │ │ 锁屏播控中心可见
│ │ │
│ │ │ 用户点击快进
│ │ 4. on('fastForward') │ <───────────────────
│ │ <───────────────────────── │
│ │ │
│ │ 5. 播放器 seek +15秒 │
│ │ 6. setAVPlaybackState() │
│ │ ─────────────────────────▶ │ 更新进度条显示
│ │ │
│ │ │ 用户点击下一首
│ │ 7. on('playNext') │ <───────────────────
│ │ <───────────────────────── │
│ │ │
│ │ 8. 切换音频 + 重置进度 │
│ │ 9. setAVMetadata() │
│ │ 10. setAVPlaybackState() │
│ │ ─────────────────────────▶ │ 显示新音频信息
│ │ │
八、总结
实现 HarmonyOS 锁屏音频播放的核心要点:
- 正确配置权限和后台模式:确保应用在后台可以继续播放
- 创建和管理 AVSession 生命周期:与播放状态保持一致
- 及时同步元数据和播放状态:让锁屏播控中心展示正确的信息
- 监听并响应系统播控命令:实现完整的双向交互
- 合理管理前后台切换和歌曲切换:避免状态错乱和播放中断
通过本文介绍的方法,你可以实现一个体验流畅、与系统播控中心完美集成的音频播放功能。
参考文档: