HarmonyOS APP中的媒体控制
核心要点:AVSessionController 是媒体控制的「遥控器」,它让控制端(锁屏、通知栏、蓝牙设备、其他应用)能够远程操控媒体应用的播放行为。掌握 Controller 的创建、命令发送、进度同步和音量控制,是实现跨组件媒体控制的关键。
| 项目 | 说明 |
|---|---|
| 核心模块 | @ohos.multimedia.avsession |
一、背景与动机
上一篇我们讲了 AVSession——媒体应用的「身份证」。但有了身份证还不够,系统还需要一个「遥控器」来操控这个会话。
你想想看,当你坐在沙发上,手机连着蓝牙音箱,你想切歌,你是按音箱上的按钮,还是跑过去划手机?当然是按音箱。那音箱怎么告诉手机「我要切歌」?手机又怎么知道当前播放到哪一分哪一秒?
这就是 AVSessionController 的用武之地。
Controller 是 AVSession 的「另一半」。Session 负责发布信息,Controller 负责消费信息和发送命令。它们就像对讲机的两端——一个说,一个听,但反过来也行。
典型的使用场景:
- 锁屏媒体控件——系统锁屏上的播放/暂停/切歌按钮
- 通知栏媒体控件——下拉通知栏里的迷你播放器
- 蓝牙设备控制——耳机/音箱上的物理按键
- 跨应用控制——一个 App 控制另一个 App 的播放
- 智能家居联动——语音助手控制播放
二、核心原理
2.1 Session 与 Controller 的关系
flowchart LR
classDef primary fill:#1890ff,stroke:#096dd9,color:#fff
classDef warning fill:#fa8c16,stroke:#d48806,color:#fff
classDef error fill:#f5222d,stroke:#cf1322,color:#fff
classDef info fill:#13c2c2,stroke:#006d75,color:#fff
classDef purple fill:#722ed1,stroke:#531dab,color:#fff
subgraph 媒体应用
A[AVSession]:::primary
end
subgraph AVSessionService
B[会话管理中心]:::purple
end
subgraph 控制端
C[AVSessionController 1]:::warning
D[AVSessionController 2]:::warning
E[AVSessionController N]:::warning
end
A -->|注册会话| B
B -->|分发会话信息| C
B -->|分发会话信息| D
B -->|分发会话信息| E
C -->|发送控制命令| B
D -->|发送控制命令| B
E -->|发送控制命令| B
B -->|转发命令| A
核心关系:
- 一个 AVSession 可以有多个 AVSessionController
- Controller 通过 sessionId 与 Session 绑定
- Controller 发送的命令,最终由 Session 侧的
on('play')等回调接收
2.2 控制命令体系
flowchart TB
classDef primary fill:#1890ff,stroke:#096dd9,color:#fff
classDef warning fill:#fa8c16,stroke:#d48806,color:#fff
classDef error fill:#f5222d,stroke:#cf1322,color:#fff
classDef info fill:#13c2c2,stroke:#006d75,color:#fff
classDef purple fill:#722ed1,stroke:#531dab,color:#fff
Root[AVSessionController 控制命令]:::primary
Root --> A[播放控制]:::warning
Root --> B[进度控制]:::warning
Root --> C[音量控制]:::info
Root --> D[模式控制]:::info
A --> A1[sendPlayCommand]
A --> A2[sendPauseCommand]
A --> A3[sendStopCommand]
A --> A4[sendPlayNextCommand]
A --> A5[sendPlayPreviousCommand]
A --> A6[sendFastForwardCommand]
A --> A7[sendRewindCommand]
B --> B1[sendSeekCommand]
C --> C1[sendVolumeKeyEvent]
D --> D1[sendSetSpeedCommand]
D --> D2[sendSetLoopModeCommand]
D --> D3[sendToggleFavoriteCommand]
2.3 Controller 生命周期
| 阶段 | 方法 | 说明 |
|---|---|---|
| 创建 | avsession.createController(sessionId) |
通过 sessionId 创建控制器 |
| 活跃 | 监听会话变化、发送命令 | 正常工作状态 |
| 销毁 | controller.destroy() |
释放资源 |
三、代码实战
3.1 发现并创建控制器
要控制一个媒体会话,首先得找到它。系统提供了 avsession.getAllSessionDescriptors() 来获取当前所有活跃会话。
import { avsession } from '@kit.MultimediaKit';
import { BusinessError } from '@kit.BasicServicesKit';
/**
* 发现并创建媒体会话控制器
* 典型场景:系统媒体控制面板需要发现所有正在播放的媒体应用
*/
class MediaControllerManager {
private controllers: Map<string, avsession.AVSessionController> = new Map();
/**
* 发现所有活跃的媒体会话,并为每个会话创建控制器
*/
async discoverAndCreateControllers(): Promise<void> {
try {
// 第一步:获取所有活跃会话描述符
const descriptors = await avsession.getAllSessionDescriptors();
console.info(`[Controller] 发现 ${descriptors.length} 个活跃会话`);
for (const descriptor of descriptors) {
const sessionId = descriptor.sessionId;
if (!sessionId) continue;
// 跳过已创建的控制器
if (this.controllers.has(sessionId)) continue;
// 第二步:根据 sessionId 创建控制器
const controller = await avsession.createController(sessionId);
this.controllers.set(sessionId, controller);
console.info(`[Controller] 创建控制器成功: sessionId=${sessionId}, tag=${descriptor.sessionTag}`);
// 第三步:注册控制器监听
this.setupControllerListeners(controller);
}
} catch (err) {
const error = err as BusinessError;
console.error(`[Controller] 发现会话失败: ${error.message}`);
}
}
/**
* 注册控制器的各种监听
*/
private setupControllerListeners(controller: avsession.AVSessionController): void {
// 监听会话元数据变化(歌曲切换时触发)
controller.on('metadataChange', (metadata: avsession.AVMetadata) => {
console.info(`[Controller] 元数据变化: title=${metadata.title}, artist=${metadata.artist}`);
// 更新 UI 显示
});
// 监听播放状态变化(播放/暂停/进度变化时触发)
controller.on('playbackStateChange', (state: avsession.AVPlaybackState) => {
console.info(`[Controller] 播放状态变化: state=${state.state}, position=${state.position?.elapsedTime}`);
// 更新播放按钮状态、进度条
});
// 监听会话销毁事件
controller.on('sessionDestroy', () => {
const sessionId = controller.sessionId;
console.info(`[Controller] 会话已销毁: ${sessionId}`);
this.controllers.delete(sessionId);
controller.destroy();
});
// 监听活跃状态变化
controller.on('activeChange', (isActive: boolean) => {
console.info(`[Controller] 活跃状态变化: ${isActive}`);
});
}
/**
* 获取指定会话的控制器
*/
getController(sessionId: string): avsession.AVSessionController | undefined {
return this.controllers.get(sessionId);
}
/**
* 销毁所有控制器
*/
async destroyAll(): Promise<void> {
for (const [sessionId, controller] of this.controllers) {
try {
controller.off('metadataChange');
controller.off('playbackStateChange');
controller.off('sessionDestroy');
controller.off('activeChange');
await controller.destroy();
} catch (err) {
// 忽略销毁错误
}
}
this.controllers.clear();
console.info('[Controller] 所有控制器已销毁');
}
}
3.2 发送播放控制命令与进度同步
控制器最核心的功能就是发送控制命令。下面展示一个完整的远程控制面板实现。
import { avsession } from '@kit.MultimediaKit';
import { BusinessError } from '@kit.BasicServicesKit';
/**
* 远程媒体控制面板
* 模拟锁屏/通知栏的媒体控制逻辑
*/
class RemoteMediaPanel {
private controller: avsession.AVSessionController | null = null;
constructor(controller: avsession.AVSessionController) {
this.controller = controller;
}
// ========== 播放控制命令 ==========
/** 发送播放命令 */
async play(): Promise<void> {
if (!this.controller) return;
try {
await this.controller.sendControlCommand({ command: 'play' });
console.info('[RemotePanel] 发送播放命令');
} catch (err) {
const error = err as BusinessError;
console.error(`[RemotePanel] 播放命令失败: ${error.message}`);
}
}
/** 发送暂停命令 */
async pause(): Promise<void> {
if (!this.controller) return;
try {
await this.controller.sendControlCommand({ command: 'pause' });
console.info('[RemotePanel] 发送暂停命令');
} catch (err) {
const error = err as BusinessError;
console.error(`[RemotePanel] 暂停命令失败: ${error.message}`);
}
}
/** 发送停止命令 */
async stop(): Promise<void> {
if (!this.controller) return;
try {
await this.controller.sendControlCommand({ command: 'stop' });
console.info('[RemotePanel] 发送停止命令');
} catch (err) {
const error = err as BusinessError;
console.error(`[RemotePanel] 停止命令失败: ${error.message}`);
}
}
/** 发送下一曲命令 */
async playNext(): Promise<void> {
if (!this.controller) return;
try {
await this.controller.sendControlCommand({ command: 'playNext' });
console.info('[RemotePanel] 发送下一曲命令');
} catch (err) {
const error = err as BusinessError;
console.error(`[RemotePanel] 下一曲命令失败: ${error.message}`);
}
}
/** 发送上一曲命令 */
async playPrevious(): Promise<void> {
if (!this.controller) return;
try {
await this.controller.sendControlCommand({ command: 'playPrevious' });
console.info('[RemotePanel] 发送上一曲命令');
} catch (err) {
const error = err as BusinessError;
console.error(`[RemotePanel] 上一曲命令失败: ${error.message}`);
}
}
// ========== 进度同步 ==========
/** 发送进度跳转命令 */
async seekTo(positionMs: number): Promise<void> {
if (!this.controller) return;
try {
await this.controller.sendControlCommand({
command: 'seek',
parameter: positionMs, // 目标位置(毫秒)
});
console.info(`[RemotePanel] 发送跳转命令: ${positionMs}ms`);
} catch (err) {
const error = err as BusinessError;
console.error(`[RemotePanel] 跳转命令失败: ${error.message}`);
}
}
/** 获取当前播放进度(从缓存的播放状态中读取) */
async getCurrentProgress(): Promise<{ position: number; duration: number } | null> {
if (!this.controller) return null;
try {
const playbackState = await this.controller.getAVPlaybackState();
const position = playbackState.position?.elapsedTime ?? 0;
// 时长需要从元数据获取
const metadata = await this.controller.getAVMetadata();
const duration = metadata.duration ?? 0;
return { position, duration };
} catch (err) {
console.error('[RemotePanel] 获取进度失败');
return null;
}
}
// ========== 模式控制 ==========
/** 设置播放速度 */
async setSpeed(speed: number): Promise<void> {
if (!this.controller) return;
try {
await this.controller.sendControlCommand({
command: 'setSpeed',
parameter: speed,
});
console.info(`[RemotePanel] 设置播放速度: ${speed}x`);
} catch (err) {
const error = err as BusinessError;
console.error(`[RemotePanel] 设置速度失败: ${error.message}`);
}
}
/** 设置循环模式 */
async setLoopMode(mode: avsession.LoopMode): Promise<void> {
if (!this.controller) return;
try {
await this.controller.sendControlCommand({
command: 'setLoopMode',
parameter: mode,
});
console.info(`[RemotePanel] 设置循环模式: ${mode}`);
} catch (err) {
const error = err as BusinessError;
console.error(`[RemotePanel] 设置循环模式失败: ${error.message}`);
}
}
/** 切换收藏状态 */
async toggleFavorite(assetId: string): Promise<void> {
if (!this.controller) return;
try {
await this.controller.sendControlCommand({
command: 'toggleFavorite',
parameter: assetId,
});
console.info(`[RemotePanel] 切换收藏: ${assetId}`);
} catch (err) {
const error = err as BusinessError;
console.error(`[RemotePanel] 切换收藏失败: ${error.message}`);
}
}
}
3.3 音量控制与锁屏/通知栏集成
音量控制是媒体控制中特殊的一环——它不经过 AVSession 的命令通道,而是直接与系统音频服务交互。同时,我们来看看如何监听系统的会话变化,实现类似锁屏媒体控件的完整功能。
import { avsession } from '@kit.MultimediaKit';
import { audio } from '@kit.AudioKit';
import { BusinessError } from '@kit.BasicServicesKit';
/**
* 完整的媒体控制面板
* 集成音量控制、锁屏/通知栏控制、会话变化监听
*/
@Entry
@Component
struct MediaControlPanel {
// 当前活跃的控制器
@State currentController: avsession.AVSessionController | null = null;
// 当前歌曲信息
@State songTitle: string = '未知歌曲';
@State songArtist: string = '未知艺术家';
@State songAlbum: string = '';
// 播放状态
@State isPlaying: boolean = false;
@State currentPosition: number = 0; // 当前位置(毫秒)
@State totalDuration: number = 0; // 总时长(毫秒)
// 音量
@State currentVolume: number = 50;
@State maxVolume: number = 100;
// 音频管理器
private audioManager: audio.AudioVolumeManager | null = null;
// 会话变化监听器
private sessionListener: avsession.SessionListener | null = null;
aboutToAppear(): void {
this.initMediaControl();
}
aboutToDisappear(): void {
this.cleanup();
}
/**
* 初始化媒体控制
*/
async initMediaControl(): Promise<void> {
// 第一步:初始化音量管理
this.audioManager = audio.getAudioManager().getVolumeManager();
this.initVolumeControl();
// 第二步:监听系统会话变化
this.setupSessionListener();
// 第三步:发现并连接当前活跃会话
await this.connectToActiveSession();
}
/**
* 初始化音量控制
*/
private initVolumeControl(): void {
if (!this.audioManager) return;
// 获取媒体流音量
this.audioManager.getVolume(audio.AudioVolumeType.MEDIA, (err: BusinessError, volume: number) => {
if (err) {
console.error(`[Volume] 获取音量失败: ${err.message}`);
return;
}
this.currentVolume = volume;
});
// 监听音量变化
this.audioManager.on('volumeChange', (volumeEvent: audio.VolumeEvent) => {
if (volumeEvent.volumeType === audio.AudioVolumeType.MEDIA) {
this.currentVolume = volumeEvent.volume;
this.maxVolume = volumeEvent.maxVolume;
console.info(`[Volume] 音量变化: ${volumeEvent.volume}/${volumeEvent.maxVolume}`);
}
});
}
/**
* 设置系统会话变化监听
* 当有新的媒体会话创建或销毁时,系统会通知我们
*/
private setupSessionListener(): void {
this.sessionListener = {
// 新会话创建时回调
onCreate: (session: avsession.AVSessionDescriptor) => {
console.info(`[SessionListener] 新会话创建: ${session.sessionId}`);
this.connectToActiveSession();
},
// 会话销毁时回调
onDestroy: (session: avsession.AVSessionDescriptor) => {
console.info(`[SessionListener] 会话销毁: ${session.sessionId}`);
// 如果销毁的是当前控制的会话,清空 UI
if (this.currentController && this.currentController.sessionId === session.sessionId) {
this.currentController = null;
this.resetUI();
}
},
// 顶级会话变化时回调(系统焦点切换)
onTopSessionChange: (session: avsession.AVSessionDescriptor) => {
console.info(`[SessionListener] 顶级会话变化: ${session.sessionId}`);
this.connectToActiveSession();
},
};
avsession.on('sessionCreate', this.sessionListener.onCreate);
avsession.on('sessionDestroy', this.sessionListener.onDestroy);
avsession.on('topSessionChange', this.sessionListener.onTopSessionChange);
}
/**
* 连接到当前活跃会话
*/
private async connectToActiveSession(): Promise<void> {
try {
const descriptors = await avsession.getAllSessionDescriptors();
if (descriptors.length === 0) {
console.info('[Panel] 没有活跃的媒体会话');
this.resetUI();
return;
}
// 选择第一个活跃会话(实际应用中可以根据类型、优先级等筛选)
const targetSession = descriptors[0];
const sessionId = targetSession.sessionId;
if (!sessionId) return;
// 如果已有控制器且 sessionId 相同,不需要重新创建
if (this.currentController && this.currentController.sessionId === sessionId) {
return;
}
// 销毁旧控制器
if (this.currentController) {
this.currentController.off('metadataChange');
this.currentController.off('playbackStateChange');
await this.currentController.destroy();
}
// 创建新控制器
this.currentController = await avsession.createController(sessionId);
this.setupControllerListeners(this.currentController);
// 读取当前元数据和播放状态
const metadata = await this.currentController.getAVMetadata();
this.updateMetadataUI(metadata);
const playbackState = await this.currentController.getAVPlaybackState();
this.updatePlaybackStateUI(playbackState);
console.info(`[Panel] 已连接到会话: ${sessionId}`);
} catch (err) {
const error = err as BusinessError;
console.error(`[Panel] 连接会话失败: ${error.message}`);
}
}
/**
* 注册控制器监听(实时同步 UI)
*/
private setupControllerListeners(controller: avsession.AVSessionController): void {
// 元数据变化 → 更新歌曲信息
controller.on('metadataChange', (metadata: avsession.AVMetadata) => {
this.updateMetadataUI(metadata);
});
// 播放状态变化 → 更新播放按钮和进度条
controller.on('playbackStateChange', (state: avsession.AVPlaybackState) => {
this.updatePlaybackStateUI(state);
});
}
// ========== UI 更新方法 ==========
private updateMetadataUI(metadata: avsession.AVMetadata): void {
this.songTitle = metadata.title ?? '未知歌曲';
this.songArtist = metadata.artist ?? '未知艺术家';
this.songAlbum = metadata.album ?? '';
this.totalDuration = metadata.duration ?? 0;
}
private updatePlaybackStateUI(state: avsession.AVPlaybackState): void {
this.isPlaying = state.state === avsession.PlaybackState.PLAYBACK_STATE_PLAY;
this.currentPosition = state.position?.elapsedTime ?? 0;
}
private resetUI(): void {
this.songTitle = '未知歌曲';
this.songArtist = '未知艺术家';
this.isPlaying = false;
this.currentPosition = 0;
this.totalDuration = 0;
}
// ========== 控制操作 ==========
/** 播放/暂停切换 */
async togglePlayPause(): Promise<void> {
if (!this.currentController) return;
const command = this.isPlaying ? 'pause' : 'play';
await this.currentController.sendControlCommand({ command });
}
/** 下一曲 */
async nextTrack(): Promise<void> {
if (!this.currentController) return;
await this.currentController.sendControlCommand({ command: 'playNext' });
}
/** 上一曲 */
async prevTrack(): Promise<void> {
if (!this.currentController) return;
await this.currentController.sendControlCommand({ command: 'playPrevious' });
}
/** 进度跳转 */
async seekTo(positionMs: number): Promise<void> {
if (!this.currentController) return;
await this.currentController.sendControlCommand({ command: 'seek', parameter: positionMs });
}
/** 调节音量 */
async adjustVolume(delta: number): Promise<void> {
if (!this.audioManager) return;
const newVolume = Math.max(0, Math.min(this.maxVolume, this.currentVolume + delta));
try {
await this.audioManager.setVolume(audio.AudioVolumeType.MEDIA, newVolume);
} catch (err) {
console.error('[Volume] 调节音量失败');
}
}
/**
* 清理资源
*/
private async cleanup(): Promise<void> {
// 移除会话监听
avsession.off('sessionCreate');
avsession.off('sessionDestroy');
avsession.off('topSessionChange');
// 销毁控制器
if (this.currentController) {
this.currentController.off('metadataChange');
this.currentController.off('playbackStateChange');
await this.currentController.destroy();
}
// 移除音量监听
if (this.audioManager) {
this.audioManager.off('volumeChange');
}
}
build() {
Column() {
// 歌曲信息
Text(this.songTitle).fontSize(20).fontWeight(FontWeight.Bold)
Text(this.songArtist).fontSize(14).fontColor('#999999')
// 进度条
Slider({
value: this.currentPosition,
min: 0,
max: this.totalDuration || 1,
})
.onChange((value: number) => {
this.seekTo(value);
})
// 控制按钮
Row() {
Button('上一曲').onClick(() => this.prevTrack())
Button(this.isPlaying ? '暂停' : '播放').onClick(() => this.togglePlayPause())
Button('下一曲').onClick(() => this.nextTrack())
}
// 音量控制
Row() {
Button('音量-').onClick(() => this.adjustVolume(-1))
Text(`${this.currentVolume}/${this.maxVolume}`)
Button('音量+').onClick(() => this.adjustVolume(1))
}
}
.width('100%')
.height('100%')
.padding(20)
}
}
四、踩坑与注意事项
4.1 Controller 创建时机
Controller 只能控制已激活的会话。如果会话还没激活就创建 Controller,getAVMetadata() 等方法可能返回空数据。
// ❌ 错误:会话还没激活就创建控制器
const descriptors = await avsession.getAllSessionDescriptors();
// 此时拿到的可能是未激活的会话
// ✅ 正确:检查会话是否活跃
const descriptors = await avsession.getAllSessionDescriptors();
for (const desc of descriptors) {
// 只有活跃会话才创建控制器
if (desc.isActive) {
const controller = await avsession.createController(desc.sessionId!);
}
}
4.2 命令发送失败的常见原因
sendControlCommand 可能失败,常见原因:
| 错误码 | 原因 | 解决方案 |
|---|---|---|
| 6600103 | 会话未激活 | 先激活会话再发命令 |
| 6600105 | 命令无效 | 检查命令名称是否正确 |
| 6600101 | 会话不存在 | 会话可能已被销毁,重新获取 |
| 401 | 参数错误 | 检查 parameter 类型是否匹配 |
// 健壮的命令发送
async function sendCommandSafely(
controller: avsession.AVSessionController,
command: avsession.AVControlCommand
): Promise<boolean> {
try {
await controller.sendControlCommand(command);
return true;
} catch (err) {
const error = err as BusinessError;
switch (error.code) {
case 6600103:
console.warn('[Command] 会话未激活,尝试重新连接');
break;
case 6600101:
console.warn('[Command] 会话不存在,需要重新发现');
break;
default:
console.error(`[Command] 命令发送失败: ${error.message}`);
}
return false;
}
}
4.3 进度同步的精度问题
playbackStateChange 回调的触发频率取决于 Session 侧的更新频率。如果 Session 侧每秒更新一次播放位置,Controller 侧的进度条就会有 1 秒的延迟。
解决方案:在 Controller 侧做插值估算。
// 进度插值估算器
class ProgressEstimator {
private lastPosition: number = 0;
private lastUpdateTime: number = 0;
private isPlaying: boolean = false;
/** 接收来自 Controller 的播放状态更新 */
update(position: number, updateTime: number, isPlaying: boolean): void {
this.lastPosition = position;
this.lastUpdateTime = updateTime;
this.isPlaying = isPlaying;
}
/** 获取估算的当前进度 */
getEstimatedPosition(): number {
if (!this.isPlaying) return this.lastPosition;
const elapsed = Date.now() - this.lastUpdateTime;
return this.lastPosition + elapsed;
}
}
4.4 音量控制的特殊性
音量控制不经过 AVSession 的命令通道,而是直接与系统音频服务交互。这意味着:
sendControlCommand中没有音量命令- 音量调节需要使用
audio.AudioVolumeManager - 音量变化需要单独监听
// ❌ 错误:AVSession 没有音量命令
await controller.sendControlCommand({ command: 'setVolume' }); // 不存在!
// ✅ 正确:使用 AudioVolumeManager
const volumeManager = audio.getAudioManager().getVolumeManager();
await volumeManager.setVolume(audio.AudioVolumeType.MEDIA, 10);
4.5 Controller 的内存泄漏
每个 Controller 都是系统资源,用完必须销毁。特别是在会话频繁创建/销毁的场景(如用户频繁切换 App),不及时销毁 Controller 会导致内存泄漏。
// 监听会话销毁事件,自动清理对应的 Controller
controller.on('sessionDestroy', () => {
controller.off('metadataChange');
controller.off('playbackStateChange');
controller.destroy();
this.controllers.delete(sessionId);
});
五、HarmonyOS 6 适配
5.1 API 变更
| 变更项 | HarmonyOS 5 | HarmonyOS 6 |
|---|---|---|
| 命令发送 | sendControlCommand() |
新增 sendControlCommandAsync() 支持异步回调结果 |
| 进度同步 | 被动监听 playbackStateChange |
新增 subscribePlaybackPosition() 主动订阅高频进度 |
| 音量控制 | 需要单独使用 AudioVolumeManager | 新增 setVolume() 集成到 Controller |
| 分布式控制 | 仅本地控制 | 新增远程设备发现与控制 |
5.2 高频进度订阅
HarmonyOS 6 新增了主动进度订阅能力,可以获取更精确的播放位置:
// HarmonyOS 6 新增:高频进度订阅
controller.subscribePlaybackPosition(100, (position: avsession.AVPlaybackPosition) => {
// 每 100ms 回调一次,精度大幅提升
console.info(`[Progress] position=${position.elapsedTime}`);
});
// 取消订阅
controller.unsubscribePlaybackPosition();
5.3 迁移要点
- 将
sendControlCommand替换为sendControlCommandAsync以获取命令执行结果 - 使用
subscribePlaybackPosition替代手动插值估算 - 分布式控制场景需要声明
ohos.permission.DISTRIBUTED_DATASYNC权限
六、总结
| 知识点 | 核心内容 |
|---|---|
| AVSessionController 本质 | AVSession 的「遥控器」,一个 Session 可以有多个 Controller |
| 创建方式 | avsession.createController(sessionId) 绑定到指定会话 |
| 发现会话 | getAllSessionDescriptors() 获取所有活跃会话 |
| 播放控制 | sendControlCommand({ command }) 发送 play/pause/stop 等命令 |
| 进度同步 | 监听 playbackStateChange 获取播放位置,或使用插值估算 |
| 音量控制 | 不经过 AVSession,使用 audio.AudioVolumeManager 直接控制 |
| 元数据监听 | metadataChange 回调实时获取歌曲信息变化 |
| 会话变化 | sessionCreate / sessionDestroy / topSessionChange 三大系统回调 |
| 资源释放 | Controller 必须在会话销毁时同步 destroy(),避免内存泄漏 |
| HarmonyOS 6 | 新增高频进度订阅、异步命令回调、分布式控制等能力 |
💡 一句话总结:AVSessionController 是媒体控制的「遥控器端」,通过发现会话→创建控制器→监听状态变化→发送控制命令的完整链路,实现跨组件、跨应用的媒体控制。记住:命令走 AVSession 通道,音量走 Audio 服务,两者不可混淆。
- 点赞
- 收藏
- 关注作者
评论(0)