鸿蒙新特性实战:AVSession 媒体会话 — 锁屏控制/元数据/播放状态系统级集成
引言
你有没有注意过,在 HarmonyOS 手机上播放音乐时,锁屏界面会自动显示专辑封面、歌曲名称和播放控制按钮?下拉控制中心也能看到正在播放的媒体信息并控制暂停/切歌?这背后驱动一切的,就是 AVSession(Audio Video Session)Kit。
AVSession 是 HarmonyOS 系统级的媒体会话管理框架。应用创建一个 AVSession 并设置元数据(歌名、歌手、专辑封面)和播放状态(播放中/已暂停/进度位置),系统会自动将信息展示在锁屏卡片、控制中心媒体面板等位置。更强大的是,用户在这些系统界面上的操作(点击播放/暂停/上一首/下一首/拖动进度条)会以事件回调的方式回传给应用——你不需要自己绘制锁屏 UI,系统已经替你做好了。
本文构建一个媒体会话实验室 Demo,模拟一个本地音乐播放器的 AVSession 集成:创建媒体会话、设置专辑/歌手/时长元数据、同步播放状态、监听系统控制指令(play/pause/seek/playNext/playPrevious)、切换曲目并自动更新锁屏展示。
读完本文,你将掌握:
- 创建与生命周期:
createAVSession()→activate()→deactivate()→destroy()完整流程 - 元数据管理:
setAVMetadata()设置歌名/歌手/专辑/时长 - 播放状态同步:
setAVPlaybackState()同步播放/暂停状态与进度位置 - 系统指令监听:
on('play'/'pause'/'seek'/'playNext'/'playPrevious')接收锁屏/控制中心操作 - 与音频播放器的解耦关系:AVSession 只管理会话状态,不管理音频流
一、AVSession 体系总览
1.1 是什么 vs 不是什么
AVSession 是什么:
- 一个会话管理器,在应用和系统之间建立关于"当前正在播放什么"的通信通道
- 负责将媒体元数据和播放状态同步到系统 UI(锁屏、控制中心、通知栏)
- 负责将从系统 UI 接收到的控制指令回传给应用
AVSession 不是什么:
- 它不播放音频——实际的音频播放需要
@ohos.multimedia.media(AVPlayer) 或@ohos.multimedia.sound(SoundPool) - 它不渲染 UI——锁屏和控制中心的 UI 是系统渲染的,你只提供数据
- 它不管理音频焦点——音频焦点管理是 AVPlayer / AudioManager 的职责
1.2 系统级集成架构
┌──────────────────┐ 同步元数据/状态 ┌──────────────┐
│ 应用 (你的App) │ ──────────────────→ │ AVSession │
│ │ ←────────────────── │ 系统服务 │
│ 媒体会话实验室 │ 接收控制指令 └──────┬───────┘
└──────────────────┘ │
│ 展示到系统UI
┌──────┴───────┐
│ 锁屏卡片 │
│ 控制中心面板 │
│ 通知栏播放器 │
└──────────────┘
1.3 核心 API 速查
| API | 说明 | 返回值 |
|---|---|---|
avSession.createAVSession(context, tag, type) |
创建会话实例 | Promise<AVSession> |
session.activate() |
激活会话(系统 UI 开始展示) | Promise<void> |
session.deactivate() |
停用会话(从系统 UI 移除) | Promise<void> |
session.destroy() |
销毁会话,释放资源 | Promise<void> |
session.setAVMetadata(metadata) |
设置歌曲名/歌手/专辑等 | Promise<void> |
session.setAVPlaybackState(state) |
同步播放状态(播放/暂停/进度) | Promise<void> |
session.on('play', callback) |
监听系统发来的播放指令 | void |
session.on('pause', callback) |
监听系统发来的暂停指令 | void |
session.on('stop', callback) |
监听系统发来的停止指令 | void |
session.on('playNext', callback) |
监听系统发来的下一首指令 | void |
session.on('playPrevious', callback) |
监听系统发来的上一首指令 | void |
session.on('seek', callback) |
监听系统发来的跳转指令 | void |
关键限制:
- 同一时间一个应用只能有一个激活状态的 AVSession(可以创建多个,但只能激活一个)
activate()必须在createAVSession()之后调用,否则系统 UI 不会展示- 监听回调必须在
createAVSession()成功之后、activate()之前注册,以确保不丢失任何事件
二、创建与生命周期管理
2.1 createAVSession() — 创建会话
import { avSession } from '@kit.AVSessionKit';
avSession.createAVSession(getContext(this), 'media_lab', 'audio').then((session) => {
this.session = session;
// 此时会话已创建但尚未激活
}).catch((err: Error) => {
console.error('创建失败: ' + err.message);
});
三个参数:
context:应用上下文,通过getContext(this)获取tag:会话标识字符串,用于调试和多会话管理type:会话类型,'audio'(音频)或'video'(视频)
注意:createAVSession 是异步操作,必须等待 Promise resolve 后拿到 AVSession 实例才能进行后续操作。
2.2 activate() / deactivate() — 激活与停用
// 激活 — 系统 UI 开始展示该会话
session.activate().then(() => {
// 锁屏/控制中心现在能看到当前播放的媒体信息
}).catch((err: Error) => { ... });
// 停用 — 从系统 UI 移除
session.deactivate().then(() => {
// 锁屏/控制中心不再展示该会话
}).catch((err: Error) => { ... });
关键行为:
activate()是让 AVSession 对用户可见的操作。元数据和状态必须在activate()之前或之后通过setAVMetadata()/setAVPlaybackState()设置deactivate()后可以再次调用activate()重新激活(不需要重新创建)- 应用切换到后台时,AVSession 会保持激活状态(这正是它的核心价值——锁屏可见)
2.3 destroy() — 销毁
session.destroy().then(() => {
this.session = null;
}).catch((err: Error) => { ... });
调用 destroy() 后,AVSession 实例不可再用,系统 UI 自动移除。通常在应用退出或用户停止播放时调用。
完整生命周期流程:
createAVSession() → 注册事件监听器 → setAVMetadata() → activate()
↓
用户操作 ← setAVPlaybackState() ← 系统事件回调 → setAVMetadata(新曲目)
↓
停止播放 → deactivate() → destroy()


三、元数据管理 — AVMetadata
3.1 设置元数据
let metadata: avSession.AVMetadata = {
assetId: 'track_1', // 媒体资源 ID(唯一标识)
title: '月光曲', // 歌曲名(锁屏主标题)
artist: '贝多芬', // 歌手名(锁屏副标题)
album: '古典精选集', // 专辑名
duration: 240000 // 总时长(毫秒,用于进度条展示)
};
session.setAVMetadata(metadata).then(() => { ... });
3.2 AVMetadata 主要字段
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
assetId |
string |
是 | 唯一标识当前媒体资源 |
title |
string |
推荐 | 歌名/视频标题(锁屏第一行) |
artist |
string |
推荐 | 歌手/作者(锁屏第二行) |
album |
string |
否 | 专辑名 |
duration |
number |
推荐 | 总时长(毫秒),影响进度条范围 |
author |
string |
否 | 作者(与 artist 可不同) |
description |
string |
否 | 媒体描述 |
subtitle |
string |
否 | 副标题 |
avQueue |
Array<AVQueueItem> |
否 | 播放列表信息 |
最佳实践:至少设置 assetId、title、artist、duration,这样系统 UI 才能呈现完整的播放信息。
3.3 更新元数据——切歌时调用
当用户切换到下一首时,应立即调用 setAVMetadata() 更新新歌曲的信息:
nextTrack(): void {
this.currentTrackIndex = (this.currentTrackIndex + 1) % this.tracks.length;
this.currentPosition = 0; // 新歌曲从头开始
this.updateMetadata(); // 调用 setAVMetadata() 更新锁屏展示
this.syncState(); // 同步播放状态(进度归零)
}
四、播放状态同步 — AVPlaybackState
4.1 设置播放状态
let state: avSession.AVPlaybackState = {
state: avSession.PlaybackState.PLAYBACK_STATE_PLAY, // 播放中
position: {
elapsedTime: 45000, // 当前已播放 45 秒
updateTime: Date.now() // 更新时间戳(用于系统推算进度条)
},
speed: 1.0 // 播放速度(1.0 = 正常)
};
session.setAVPlaybackState(state);
4.2 AVPlaybackState 核心字段
| 字段 | 类型 | 说明 |
|---|---|---|
state |
avSession.PlaybackState |
播放状态枚举 |
position |
{ elapsedTime, updateTime } |
播放进度信息 |
speed |
number |
播放速度(1.0 正常,2.0 两倍速) |
4.3 PlaybackState 枚举值
| 枚举值 | 含义 | 系统 UI 表现 |
|---|---|---|
PLAYBACK_STATE_INITIAL |
初始状态(刚创建) | 不显示 |
PLAYBACK_STATE_PREPARE |
准备中(加载数据) | 显示缓冲指示 |
PLAYBACK_STATE_PLAY |
播放中 | 显示暂停按钮 |
PLAYBACK_STATE_PAUSE |
已暂停 | 显示播放按钮 |
PLAYBACK_STATE_STOP |
已停止 | 从系统 UI 移除播放控制 |
PLAYBACK_STATE_FAST_FORWARD |
快进中 | 显示快进状态 |
PLAYBACK_STATE_REWIND |
快退中 | 显示快退状态 |
4.4 position 字段详解
position 决定了系统 UI(尤其是锁屏)中进度条的显示位置:
position: {
elapsedTime: this.currentPosition, // 当前播放到的位置(毫秒)
updateTime: Date.now() // 此位置的记录时间戳
}
为什么需要 updateTime:系统用 elapsedTime + (当前时间 - updateTime) × speed 来计算实时的进度条位置。这样即使应用不频繁更新,进度条也能平滑前进。但在本 Demo 中为了简化,我们直接用秒级定时器更新 elapsedTime。
五、系统控制指令监听
这是 AVSession 最强大的特性——用户无需打开你的应用,直接在锁屏界面或控制中心操作,系统会将指令转发给你的会话:
// 在 createAVSession() 成功后注册
session.on('play', () => {
this.isPlaying = true;
this.startTimer();
this.syncState();
});
session.on('pause', () => {
this.isPlaying = false;
this.stopTimer();
this.syncState();
});
session.on('stop', () => {
this.isPlaying = false;
this.stopTimer();
this.currentPosition = 0;
this.syncState();
});
session.on('playNext', () => {
this.nextTrack();
});
session.on('playPrevious', () => {
this.prevTrack();
});
session.on('seek', (seekTime: number) => {
this.currentPosition = seekTime; // seekTime 是系统传来的目标位置(毫秒)
this.syncState();
});
关键说明:
- 所有事件回调都在主线程执行,不需要手动切换线程
seek事件的回调参数是目标时间(毫秒),而非偏移量- 收到
playNext/playPrevious后,必须更新元数据(setAVMetadata()),否则锁屏仍显示旧歌曲信息 - 如果应用不支持某些指令(如不支持快进),可以不注册对应事件——系统会隐藏相关按钮
六、实战 Demo:媒体会话实验室
页面结构
媒体会话实验室
├── 标题栏 — "媒体会话实验室" + "@ohos.multimedia.avsession"
├── 会话状态卡片
│ ├── 状态指示器:未创建 / 已创建 / 已激活 / 已停用 / 已销毁
│ └── 四个生命周期按钮:创建 / 激活 / 停用 / 销毁
├── 正在播放卡片(模拟音乐播放器)
│ ├── 专辑图占位块(彩色方块,颜色随曲目变化)
│ ├── 歌曲名 + 歌手 + 专辑名
│ ├── 进度条(Slider,可拖动 seek)
│ ├── 进度时间显示(当前 / 总时长)
│ └── 三按钮控制:上一首 / 播放暂停 / 下一首
├── 播放列表(5 首模拟曲目)
│ ├── 每行显示封面色块 + 歌名 + 歌手 + 时长
│ ├── 当前播放项高亮(紫色加粗)
│ └── 点击可切换曲目
├── 事件日志(时间 + 来源 + 详情)
│ ├── 蓝色 = 系统(锁屏/控制中心指令)
│ ├── 绿色 = App(应用内操作)
│ └── 红色 = 错误
└── 核心 API 参考
4 个交互点
- 会话生命周期管理 — 四个按钮控制完整的 create → activate → deactivate → destroy 流程,状态指示器实时反馈当前阶段
- 播放控制(双通道) — 应用内点击播放/暂停/上一首/下一首按钮,以及从锁屏/控制中心接收指令,两个通道的操作都被记录到事件日志中,以颜色区分来源
- 曲目切换与元数据同步 — 点击播放列表中的曲目切换当前歌曲,AVMetadata 自动更新到系统 UI;播放到末尾自动切到下一首
- 进度拖动与实时同步 — 拖动进度条滑块 seek 到指定位置,播放状态同步到系统 UI;秒级定时器自动推进播放进度并每 10 秒全量同步
核心代码实现
创建会话并注册事件
createSession(): void {
avSession.createAVSession(getContext(this), 'media_lab', 'audio').then((session) => {
this.session = session;
this.addLog('系统', 'AVSession 创建成功');
session.on('play', () => {
this.isPlaying = true;
this.startTimer();
this.syncState();
this.addLog('系统', '收到 Play 指令 (锁屏/控制中心)');
});
session.on('pause', () => {
this.isPlaying = false;
this.stopTimer();
this.syncState();
this.addLog('系统', '收到 Pause 指令 (锁屏/控制中心)');
});
session.on('playNext', () => {
this.nextTrack();
this.addLog('系统', '收到 PlayNext 指令 (锁屏/控制中心)');
});
session.on('playPrevious', () => {
this.prevTrack();
this.addLog('系统', '收到 PlayPrevious 指令 (锁屏/控制中心)');
});
session.on('seek', (seekTime: number) => {
this.currentPosition = seekTime;
this.syncState();
this.addLog('系统', '收到 Seek 指令 → ' + this.formatTime(seekTime));
});
this.updateMetadata();
this.syncState();
}).catch((err: Error) => {
this.addLog('错误', '创建失败: ' + err.message);
});
}
事件注册必须在 createAVSession() 成功后、activate() 之前完成。
播放状态同步
syncState(): void {
if (this.session === null) return;
let state: avSession.AVPlaybackState = {
state: this.isPlaying ?
avSession.PlaybackState.PLAYBACK_STATE_PLAY :
avSession.PlaybackState.PLAYBACK_STATE_PAUSE
};
if (this.currentPosition > 0 || this.isPlaying) {
state.position = {
elapsedTime: this.currentPosition,
updateTime: Date.now()
};
state.speed = 1.0;
}
this.session.setAVPlaybackState(state);
}
根据 isPlaying 决定状态是 PLAY 还是 PAUSE。只有在有播放进度时才设置 position 字段,避免误导系统。
曲目切换
nextTrack(): void {
this.stopTimer();
this.isPlaying = false;
this.currentTrackIndex = (this.currentTrackIndex + 1) % this.tracks.length;
this.currentPosition = 0;
this.updateMetadata(); // 更新锁屏展示的歌曲信息
this.syncState(); // 状态改为暂停、进度归零
}
updateMetadata(): void {
let track = this.tracks[this.currentTrackIndex];
let metadata: avSession.AVMetadata = {
assetId: 'track_' + this.currentTrackIndex,
title: track.title,
artist: track.artist,
album: track.album,
duration: track.duration
};
this.session!.setAVMetadata(metadata);
}
模拟播放进度
startTimer(): void {
this.stopTimer();
this.timer = setInterval(() => {
let track = this.tracks[this.currentTrackIndex];
this.currentPosition = this.currentPosition + 1000; // 每秒+1秒
if (this.currentPosition >= track.duration) {
// 播放完毕,自动切到下一首
this.currentPosition = 0;
this.currentTrackIndex = (this.currentTrackIndex + 1) % this.tracks.length;
this.updateMetadata();
this.syncState();
}
// 每 10 秒全量同步一次(减少不必要的系统调用)
if (this.currentPosition % 10000 === 0) {
this.syncState();
}
}, 1000);
}
预览效果预期
- 创建会话 → 状态灯变黄(“已创建”),事件日志显示"AVSession 创建成功"
- 激活会话 → 状态灯变绿(“已激活”),此时下拉控制中心可看到媒体播放面板显示当前歌曲信息
- 点击播放 → 状态变为"播放中",控制中心出现暂停按钮,应用内进度条开始每秒推进
- 锁屏操作 → 锁屏界面显示歌曲名和播放控制,点击"下一首"后应用自动切歌并更新锁屏信息,事件日志以蓝色记录"收到 PlayNext 指令"
- 拖动进度 → 拖动应用内进度条,调用
seekTo(),播放位置跳转到对应时间点并同步到系统 UI
七、实战技巧与常见问题
7.1 为什么锁屏没有显示我的媒体信息?
按顺序排查:
- 是否已调用
activate()?(仅createAVSession不会展示) - 是否设置了
title和artist?(系统和锁屏至少需要这两项) - 是否设置了
duration?(没有总时长无法显示进度条) - 是否设置了有效的
playbackState?(至少要是PLAY或PAUSE状态) - 是否有其他应用也在使用 AVSession?(系统一次只展示一个会话)
7.2 AVSession 与音频播放器的关系
AVSession不播放音频,它只管理"播放状态信息"。典型的架构是:
应用层:UI 按钮 → 播放状态管理(AVSession + @State)
中间层:AVSession → 同步状态到系统 UI
AVSession ← 接收系统 UI 操作
底层层:AVPlayer / SoundPool → 实际解码和输出音频
在这种架构下,AVSession 和 AVPlayer 是解耦的——你可以在不改变音频流的情况下更新锁屏展示,也可以在不改变锁屏的情况下调整音频参数。
7.3 销毁时机的选择
- deactivate():用户停止播放但应用仍在运行(如退出播放页面)。方便用户下次打开时快速恢复
- destroy():用户彻底退出应用或退出登录时调用。释放资源
八、与 Android MediaSession 的对比
| 维度 | HarmonyOS AVSession | Android MediaSession |
|---|---|---|
| 创建方式 | createAVSession(ctx, tag, type) |
MediaSession(context, tag) |
| 播放状态 | avSession.PlaybackState 枚举 |
PlaybackStateCompat.Builder |
| 元数据 | AVMetadata 对象(字段直接赋值) |
MediaMetadataCompat.Builder |
| 控制回调 | session.on('play'/'pause'/...) |
MediaSessionCompat.Callback |
| 激活 | session.activate() |
session.setActive(true) |
| 事件监听 | 字符串事件名 + 回调函数 | 重写 Callback 抽象方法 |
| 锁屏集成 | 自动(系统 UI 层) | 需要 MediaStyle 通知(自己构造) |
HarmonyOS 的 AVSession API 设计更简洁——事件回调使用字符串事件名(类似 Node.js EventEmitter),而非重写抽象类。元数据和播放状态的设置也是直接赋值对象,无需 Builder 模式。
九、总结
@ohos.multimedia.avsession 是 HarmonyOS 应用中实现系统级媒体集成的核心 API:
- 创建 → 激活 → 停用 → 销毁四步生命周期,清晰简洁
- AVMetadata 设置歌曲名/歌手/专辑/时长,自动展示在锁屏和控制中心
- AVPlaybackState 同步播放/暂停状态和进度位置,系统 UI 实时响应
- 事件监听让应用从锁屏/控制中心接收用户指令,无需自建锁屏 UI
- 与音频播放器解耦,AVSession 只管理会话状态,不管理音频流
- 零额外权限的基础操作——创建、激活、元数据设置、状态同步均无需声明任何权限
对于音乐播放器、播客、视频应用等任何需要媒体播放的场景,AVSession 都是通往系统级用户体验的必经之路。
更多推荐



所有评论(0)