引言

你有没有注意过,在 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> 播放列表信息

最佳实践:至少设置 assetIdtitleartistduration,这样系统 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 个交互点

  1. 会话生命周期管理 — 四个按钮控制完整的 create → activate → deactivate → destroy 流程,状态指示器实时反馈当前阶段
  2. 播放控制(双通道) — 应用内点击播放/暂停/上一首/下一首按钮,以及从锁屏/控制中心接收指令,两个通道的操作都被记录到事件日志中,以颜色区分来源
  3. 曲目切换与元数据同步 — 点击播放列表中的曲目切换当前歌曲,AVMetadata 自动更新到系统 UI;播放到末尾自动切到下一首
  4. 进度拖动与实时同步 — 拖动进度条滑块 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 为什么锁屏没有显示我的媒体信息?

按顺序排查:

  1. 是否已调用 activate()?(仅 createAVSession 不会展示)
  2. 是否设置了 titleartist?(系统和锁屏至少需要这两项)
  3. 是否设置了 duration?(没有总时长无法显示进度条)
  4. 是否设置了有效的 playbackState?(至少要是 PLAYPAUSE 状态)
  5. 是否有其他应用也在使用 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:

  1. 创建 → 激活 → 停用 → 销毁四步生命周期,清晰简洁
  2. AVMetadata 设置歌曲名/歌手/专辑/时长,自动展示在锁屏和控制中心
  3. AVPlaybackState 同步播放/暂停状态和进度位置,系统 UI 实时响应
  4. 事件监听让应用从锁屏/控制中心接收用户指令,无需自建锁屏 UI
  5. 与音频播放器解耦,AVSession 只管理会话状态,不管理音频流
  6. 零额外权限的基础操作——创建、激活、元数据设置、状态同步均无需声明任何权限

对于音乐播放器、播客、视频应用等任何需要媒体播放的场景,AVSession 都是通往系统级用户体验的必经之路。


Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐