什么是HarmonyOS APP媒体框架的中枢神经

举报
Jack20 发表于 2026/06/21 11:40:09 2026/06/21
【摘要】 核心要点:AVSession 是 HarmonyOS 媒体框架的「中枢神经」,它让音频/视频应用与系统媒体控制中心(锁屏、通知栏、蓝牙设备)无缝对接。掌握会话的创建、激活、元数据设置和生命周期管理,是构建专业级媒体应用的第一步。项目说明核心模块@ohos.multimedia.avsession 一、背景与动机想象一下这个场景:你正在用手机听音乐,突然来了个电话,挂断电话后音乐自动恢复播放—...

核心要点:AVSession 是 HarmonyOS 媒体框架的「中枢神经」,它让音频/视频应用与系统媒体控制中心(锁屏、通知栏、蓝牙设备)无缝对接。掌握会话的创建、激活、元数据设置和生命周期管理,是构建专业级媒体应用的第一步。

项目 说明
核心模块 @ohos.multimedia.avsession

一、背景与动机

想象一下这个场景:你正在用手机听音乐,突然来了个电话,挂断电话后音乐自动恢复播放——这一切丝滑的背后,就是「媒体会话」在默默工作。

再想想,你戴着蓝牙耳机,按了一下耳机上的播放/暂停键,手机里的音乐就停了。你有没有好奇过,耳机怎么知道手机上在播放什么?手机又怎么响应耳机的按键?

答案就是 AVSession——HarmonyOS 的媒体会话框架。

如果没有 AVSession,每个音乐 App 都得自己处理蓝牙按键、自己对接锁屏控件、自己管理音频焦点……这简直是灾难。AVSession 的出现,就是把这些「脏活累活」统一收口,让开发者专注于业务逻辑,让系统来协调一切。

简单来说,AVSession 解决了三个核心问题:

  1. 媒体控制标准化——统一的播放/暂停/上下曲命令体系
  2. 系统级媒体展示——锁屏、通知栏、控制面板自动显示当前播放信息
  3. 跨设备媒体协同——蓝牙、投屏等场景下的无缝联动

二、核心原理

2.1 AVSession 架构总览

AVSession 框架的核心思想是「发布-订阅」模式。媒体应用作为「提供者」创建会话并发布媒体信息,系统媒体控制中心作为「消费者」订阅会话并展示控制界面。

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

    subgraph 应用层
        A[音乐App]:::primary
        B[视频App]:::primary
        C[播客App]:::primary
    end

    subgraph 会话层
        D[AVSession 1]:::warning
        E[AVSession 2]:::warning
        F[AVSession N]:::warning
    end

    subgraph 系统服务层
        G[AVSessionService]:::purple
    end

    subgraph 控制端
        H[锁屏控件]:::info
        I[通知栏]:::info
        J[控制面板]:::info
        K[蓝牙设备]:::info
    end

    A --> D
    B --> E
    C --> F
    D --> G
    E --> G
    F --> G
    G --> H
    G --> I
    G --> J
    G --> K

2.2 会话生命周期

一个 AVSession 从创建到销毁,经历以下关键阶段:

stateDiagram-v2
    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

    [*] --> 已创建:createSession
    已创建 --> 已激活:activate
    已激活 --> 已失活:deactivate
    已失活 --> 已激活:activate
    已激活 --> 已销毁:destroy
    已失活 --> 已销毁:destroy
    已创建 --> 已销毁:destroy
    已销毁 --> [*]

关键规则

  • 只有激活状态的会话才会被系统媒体控制中心发现
  • 同一时刻,系统中只有一个顶级会话(Top Session)获得焦点
  • 会话销毁后不可恢复,需要重新创建

2.3 会话类型

AVSession 支持三种媒体类型:

类型 常量 说明
音频 avsession.SessionType.AUDIO 音乐、播客、有声书等
视频 avsession.SessionType.VIDEO 电影、短视频、直播等
语音通话 avsession.SessionType.VOICE_CALL 电话、VoIP 等

三、代码实战

3.1 创建并激活媒体会话

这是最基础的用法——创建一个音频类型的媒体会话,设置元数据,然后激活它。

import { avsession } from '@kit.MultimediaKit';
import { BusinessError } from '@kit.BasicServicesKit';

// 媒体会话管理器
let session: avsession.AVSession | null = null;

/**
 * 创建并激活媒体会话
 * tag: 会话标签,用于标识会话来源
 * type: 会话类型(AUDIO / VIDEO / VOICE_CALL)
 */
async function createAndActivateSession(): Promise<void> {
  try {
    // 第一步:创建会话
    // tag 是自定义标签,方便调试时识别
    session = await avsession.createAVSession(
      getContext(this),
      'MyMusicPlayer',       // 会话标签
      avsession.SessionType.AUDIO  // 会话类型
    );

    console.info(`[AVSession] 会话创建成功,sessionId: ${session.sessionId}`);

    // 第二步:设置会话元数据(让系统知道你在播放什么)
    const metadata: avsession.AVMetadata = {
      assetId: 'song_001',           // 媒体资源唯一标识
      title: '夜曲',                  // 歌曲标题
      artist: '周杰伦',               // 艺术家
      album: '十一月的萧邦',           // 专辑名
      duration: 235000,               // 时长(毫秒)
      mediaType: 'AUDIO',             // 媒体类型
      // 封面图 URI(支持本地路径和网络路径)
      mediaImage: avsession.createAVMediaImage('https://example.com/cover.jpg'),
    };
    await session.setAVMetadata(metadata);

    // 第三步:设置播放状态(让系统知道当前播放进度)
    const playbackState: avsession.AVPlaybackState = {
      state: avsession.PlaybackState.PLAYBACK_STATE_PLAY,  // 正在播放
      speed: 1.0,                    // 播放速度
      position: {                    // 播放位置
        elapsedTime: 30000,          // 已播放时长(毫秒)
        updateTime: Date.now(),      // 更新时间戳
      },
      bufferedTime: 180000,          // 缓冲时长(毫秒)
      loopMode: avsession.LoopMode.LOOP_MODE_SINGLE,  // 单曲循环
      isFavorite: false,             // 是否收藏
    };
    await session.setAVPlaybackState(playbackState);

    // 第四步:激活会话(只有激活后系统才能发现它)
    await session.activate();
    console.info('[AVSession] 会话已激活,现在可以在锁屏/通知栏看到播放信息了');

  } catch (err) {
    const error = err as BusinessError;
    console.error(`[AVSession] 创建会话失败: code=${error.code}, msg=${error.message}`);
  }
}

3.2 监听媒体控制命令

当用户在锁屏、通知栏或蓝牙设备上操作时,系统会通过 AVSession 下发控制命令。我们需要监听这些命令并做出响应。

import { avsession } from '@kit.MultimediaKit';
import { BusinessError } from '@kit.BasicServicesKit';

/**
 * 注册媒体控制命令监听
 * 这是应用响应系统媒体控制的关键入口
 */
function setupCommandListeners(session: avsession.AVSession): void {
  // ========== 播放控制命令 ==========

  // 播放命令
  session.on('play', () => {
    console.info('[AVSession] 收到播放命令');
    // 调用你的播放逻辑
    playerPlay();
  });

  // 暂停命令
  session.on('pause', () => {
    console.info('[AVSession] 收到暂停命令');
    playerPause();
  });

  // 停止命令
  session.on('stop', () => {
    console.info('[AVSession] 收到停止命令');
    playerStop();
  });

  // 下一曲命令
  session.on('playNext', () => {
    console.info('[AVSession] 收到下一曲命令');
    playNextTrack();
  });

  // 上一曲命令
  session.on('playPrevious', () => {
    console.info('[AVSession] 收到上一曲命令');
    playPreviousTrack();
  });

  // 快进命令
  session.on('fastForward', () => {
    console.info('[AVSession] 收到快进命令');
    seekToPosition(getCurrentPosition() + 10000); // 快进10秒
  });

  // 快退命令
  session.on('rewind', () => {
    console.info('[AVSession] 收到快退命令');
    seekToPosition(Math.max(0, getCurrentPosition() - 10000)); // 快退10秒
  });

  // ========== 进度跳转命令 ==========
  session.on('seek', (time: number) => {
    console.info(`[AVSession] 收到跳转命令,目标位置: ${time}ms`);
    seekToPosition(time);
  });

  // ========== 速度设置命令 ==========
  session.on('setSpeed', (speed: number) => {
    console.info(`[AVSession] 收到速度设置命令: ${speed}x`);
    setPlaybackSpeed(speed);
  });

  // ========== 循环模式命令 ==========
  session.on('setLoopMode', (mode: avsession.LoopMode) => {
    console.info(`[AVSession] 收到循环模式命令: ${mode}`);
    setLoopMode(mode);
  });

  // ========== 收藏命令 ==========
  session.on('toggleFavorite', (assetId: string) => {
    console.info(`[AVSession] 收到收藏切换命令,资源ID: ${assetId}`);
    toggleFavorite(assetId);
  });
}

// ========== 模拟播放器方法 ==========
function playerPlay(): void { /* 实际播放逻辑 */ }
function playerPause(): void { /* 实际暂停逻辑 */ }
function playerStop(): void { /* 实际停止逻辑 */ }
function playNextTrack(): void { /* 切换下一曲 */ }
function playPreviousTrack(): void { /* 切换上一曲 */ }
function seekToPosition(time: number): void { /* 跳转到指定位置 */ }
function getCurrentPosition(): number { return 0; /* 获取当前播放位置 */ }
function setPlaybackSpeed(speed: number): void { /* 设置播放速度 */ }
function setLoopMode(mode: avsession.LoopMode): void { /* 设置循环模式 */ }
function toggleFavorite(assetId: string): void { /* 切换收藏状态 */ }

3.3 多会话管理与会话切换

在复杂应用中(比如既有音乐播放又有语音通话),可能需要同时管理多个会话。下面展示如何优雅地管理多会话。

import { avsession } from '@kit.MultimediaKit';
import { BusinessError } from '@kit.BasicServicesKit';

/**
 * 多会话管理器
 * 管理应用内的多个媒体会话,确保同一时刻只有一个活跃会话
 */
class MultiSessionManager {
  // 会话池:用 Map 存储不同类型的会话
  private sessionMap: Map<string, avsession.AVSession> = new Map();
  // 当前活跃会话的 key
  private activeSessionKey: string = '';

  /**
   * 创建指定类型的会话
   * @param key 会话标识(如 'music', 'voice_call')
   * @param type 会话类型
   */
  async createSession(key: string, type: avsession.SessionType): Promise<avsession.AVSession | null> {
    try {
      // 如果已存在同名会话,先销毁旧的
      if (this.sessionMap.has(key)) {
        await this.destroySession(key);
      }

      const session = await avsession.createAVSession(
        getContext(this),
        `Session_${key}`,
        type
      );

      this.sessionMap.set(key, session);
      console.info(`[MultiSession] 会话创建成功: ${key}, sessionId: ${session.sessionId}`);

      // 注册通用命令监听
      this.registerCommonListeners(key, session);

      return session;
    } catch (err) {
      const error = err as BusinessError;
      console.error(`[MultiSession] 创建会话失败: ${error.message}`);
      return null;
    }
  }

  /**
   * 激活指定会话(自动失活其他会话)
   * 系统中同一时刻只有一个顶级会话获得焦点
   */
  async activateSession(key: string): Promise<boolean> {
    const targetSession = this.sessionMap.get(key);
    if (!targetSession) {
      console.error(`[MultiSession] 会话不存在: ${key}`);
      return false;
    }

    try {
      // 先失活当前活跃会话
      if (this.activeSessionKey && this.activeSessionKey !== key) {
        const currentSession = this.sessionMap.get(this.activeSessionKey);
        if (currentSession) {
          await currentSession.deactivate();
          console.info(`[MultiSession] 已失活会话: ${this.activeSessionKey}`);
        }
      }

      // 激活目标会话
      await targetSession.activate();
      this.activeSessionKey = key;
      console.info(`[MultiSession] 已激活会话: ${key}`);
      return true;
    } catch (err) {
      const error = err as BusinessError;
      console.error(`[MultiSession] 激活会话失败: ${error.message}`);
      return false;
    }
  }

  /**
   * 销毁指定会话
   */
  async destroySession(key: string): Promise<void> {
    const session = this.sessionMap.get(key);
    if (!session) return;

    try {
      // 先移除所有监听
      session.off('play');
      session.off('pause');
      session.off('stop');
      session.off('playNext');
      session.off('playPrevious');

      // 销毁会话
      await session.destroy();
      this.sessionMap.delete(key);

      // 如果销毁的是当前活跃会话,清空标记
      if (this.activeSessionKey === key) {
        this.activeSessionKey = '';
      }

      console.info(`[MultiSession] 会话已销毁: ${key}`);
    } catch (err) {
      const error = err as BusinessError;
      console.error(`[MultiSession] 销毁会话失败: ${error.message}`);
    }
  }

  /**
   * 获取指定会话
   */
  getSession(key: string): avsession.AVSession | undefined {
    return this.sessionMap.get(key);
  }

  /**
   * 销毁所有会话(应用退出时调用)
   */
  async destroyAll(): Promise<void> {
    const keys = Array.from(this.sessionMap.keys());
    for (const key of keys) {
      await this.destroySession(key);
    }
    console.info('[MultiSession] 所有会话已销毁');
  }

  /**
   * 注册通用命令监听
   */
  private registerCommonListeners(key: string, session: avsession.AVSession): void {
    session.on('play', () => {
      console.info(`[MultiSession] ${key} 收到播放命令`);
    });
    session.on('pause', () => {
      console.info(`[MultiSession] ${key} 收到暂停命令`);
    });
    session.on('stop', () => {
      console.info(`[MultiSession] ${key} 收到停止命令`);
    });
  }
}

// ========== 使用示例 ==========
const manager = new MultiSessionManager();

async function initSessions(): Promise<void> {
  // 创建音乐会话
  const musicSession = await manager.createSession('music', avsession.SessionType.AUDIO);
  // 创建语音通话会话
  const callSession = await manager.createSession('voice_call', avsession.SessionType.VOICE_CALL);

  // 激活音乐会话
  await manager.activateSession('music');

  // 来电时切换到语音通话会话
  // await manager.activateSession('voice_call');

  // 挂断后切回音乐会话
  // await manager.activateSession('music');
}

四、踩坑与注意事项

4.1 会话必须激活才能被发现

这是新手最容易踩的坑。创建会话后如果不调用 activate(),系统媒体控制中心完全看不到你的会话,锁屏上也不会显示播放信息。

// ❌ 错误:只创建不激活
const session = await avsession.createAVSession(context, 'tag', type);
await session.setAVMetadata(metadata);
// 忘记 activate(),锁屏上看不到任何信息

// ✅ 正确:创建后立即激活
const session = await avsession.createAVSession(context, 'tag', type);
await session.setAVMetadata(metadata);
await session.activate();  // 关键一步!

4.2 元数据中的封面图处理

mediaImage 字段支持两种类型:string(URI)和 image.PixelMap(像素图)。使用 URI 时要注意:

  • 网络图片需要应用有网络权限
  • 本地图片需要使用 file:// 协议前缀
  • 图片过大可能导致系统渲染卡顿,建议压缩到 200KB 以内
import { image } from '@kit.ImageKit';

// 方式一:使用 URI(简单,推荐)
const metadataWithUri: avsession.AVMetadata = {
  assetId: '001',
  title: '测试歌曲',
  mediaImage: avsession.createAVMediaImage('file:///data/cover.jpg'),
};

// 方式二:使用 PixelMap(适合动态生成的封面)
async function createMetadataWithPixelMap(pixelMap: image.PixelMap): Promise<avsession.AVMetadata> {
  return {
    assetId: '001',
    title: '测试歌曲',
    mediaImage: avsession.createAVMediaImage(pixelMap),
  };
}

4.3 播放状态必须实时更新

很多开发者设置了元数据后忘了更新播放状态,导致锁屏上的进度条不动、播放/暂停按钮状态不对。播放状态需要随播放进度持续更新

// ❌ 错误:只在初始化时设置一次播放状态
await session.setAVPlaybackState(playbackState);
// 之后再也不更新了

// ✅ 正确:播放过程中持续更新
function onPlayerPositionUpdate(currentTime: number): void {
  if (!session) return;
  session.setAVPlaybackState({
    state: avsession.PlaybackState.PLAYBACK_STATE_PLAY,
    position: {
      elapsedTime: currentTime,
      updateTime: Date.now(),
    },
  });
}

4.4 会话销毁时机

会话是系统级资源,用完必须销毁。否则会占用系统资源,甚至导致其他应用的会话无法正常显示。

// 在 Ability 的 onDestroy 中销毁会话
@Entry
@Component
struct Index {
  aboutToAppear(): void {
    // 创建会话...
  }

  aboutToDisappear(): void {
    // 页面销毁时清理会话
    if (session) {
      session.destroy().then(() => {
        console.info('[AVSession] 会话已销毁');
        session = null;
      });
    }
  }
}

4.5 assetId 的唯一性

assetId 是媒体资源的唯一标识,系统用它来判断是否是同一首歌。如果两首歌的 assetId 相同,系统可能不会更新锁屏信息。

// ❌ 错误:所有歌曲用同一个 assetId
const metadata: avsession.AVMetadata = {
  assetId: 'music',  // 所有歌曲都用 'music',系统无法区分
  title: '歌曲A',
};

// ✅ 正确:每首歌使用唯一 ID
const metadata: avsession.AVMetadata = {
  assetId: `track_${songId}`,  // 如 'track_001', 'track_002'
  title: songTitle,
};

五、HarmonyOS 6 适配

5.1 API 变更

变更项 HarmonyOS 5 HarmonyOS 6
会话创建 avsession.createAVSession() 新增 avsession.createAVSessionConfig() 支持更多配置项
元数据 AVMetadata 新增 lyric 字段支持歌词展示
播放状态 AVPlaybackState 新增 playbackError 字段支持错误状态上报
多设备 单设备会话 新增分布式会话,支持跨设备媒体流转

5.2 迁移指南

// HarmonyOS 5 写法
const session = await avsession.createAVSession(context, 'tag', type);

// HarmonyOS 6 写法(新增配置项)
const config: avsession.AVSessionConfig = {
  sessionTag: 'MyMusicPlayer',
  sessionType: avsession.SessionType.AUDIO,
  persistent: true,  // 新增:会话是否持久化(应用切后台不销毁)
};
const session = await avsession.createAVSession(context, config);

5.3 权限变更

HarmonyOS 6 中,AVSession 不再需要额外权限声明,但分布式会话场景需要新增:

// module.json5
{
  "requestPermissions": [
    {
      "name": "ohos.permission.DISTRIBUTED_DATASYNC",
      "reason": "$string:distributed_media_reason",
      "usedScene": {
        "abilities": ["EntryAbility"],
        "when": "inuse"
      }
    }
  ]
}

六、总结

知识点 核心内容
AVSession 本质 媒体应用与系统媒体控制中心的桥梁,基于发布-订阅模式
会话创建 createAVSession(context, tag, type) 三要素:上下文、标签、类型
会话激活 必须调用 activate() 才能被系统发现,这是最易遗忘的步骤
元数据设置 setAVMetadata() 设置标题、艺术家、封面等信息,assetId 必须唯一
播放状态 setAVPlaybackState() 需要持续更新,否则锁屏进度条不动
命令监听 通过 on('play') / on('pause') 等监听系统下发的控制命令
多会话管理 同一时刻只有一个顶级会话,切换时先 deactivate 再 activate
会话销毁 用完必须 destroy(),在页面/Ability 销毁时清理
HarmonyOS 6 新增分布式会话、歌词展示、错误状态上报等能力

💡 一句话总结:AVSession 是媒体应用的「身份证」,让系统认识你、展示你、控制你。创建-设元数据-激活-监听命令-实时更新状态-销毁,这六步走完,你的媒体应用就拥有了专业的系统级媒体控制能力。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。