HarmonyOS 锁屏音频播放完整实践指南

引言

在音频类应用开发中,锁屏状态下的音频播放与播控能力是一项核心用户体验需求。当用户锁屏后,系统播控中心需要展示当前播放的音频信息,并支持用户进行播放/暂停、切歌、快进快退等操作。本文将详细介绍如何在 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 从后台恢复时的状态处理

当应用从后台(锁屏状态)切换回前台时,需要注意:

  1. 不要中断正在播放的音频:如果播放器正在播放,保持播放状态
  2. 同步最新的播放进度:确保切换时的进度是最新的
typescript 复制代码
function onAppForeground(): void {
  // 检查播放器状态
  if (playerState === 'playing' || playerState === 'paused' || playerState === 'prepared') {
    // 播放器正在工作中,不中断播放
    return;
  }
  
  // 其他恢复逻辑...
}

5.2 切换歌曲时的状态重置

每次切换到新的音频时,必须重置以下状态:

  • currentTime(当前播放时间):重置为 0
  • durationTime(总时长):重置为新音频的时长
  • 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:锁屏播控中心没有立即显示

原因:可能是元数据设置时机不对,或者会话未正确激活。

解决

  1. 确保 createAVSession() 后立即调用 activate()
  2. 在播放器开始播放后立即调用 setAVMetadata()setAVPlaybackState()
  3. 添加日志确认 API 调用成功

❌ 问题2:封面图片不显示

原因:封面图片的格式或大小不符合要求,或者下载失败。

解决

  1. 优先使用 PixelMap 格式(比 URL 更可靠)
  2. 控制图片大小(建议 400x400 像素以内)
  3. 确保图片下载成功(添加错误处理日志)

❌ 问题3:切换歌曲后进度条显示旧的进度

原因:切换歌曲时没有重置播放进度和时长,导致 AVSession 保存了旧的进度信息。

解决

  1. 每次切换歌曲时重置 currentTime = 0durationTime = 新时长
  2. 同步 setAVPlaybackState() 时将 elapsedTime 设为 0
  3. 同步 setAVMetadata() 时将 duration 设为新音频的时长

❌ 问题4:解锁后播放被中断

原因 :应用从后台切回前台时,onForeground 逻辑意外地重启了播放器。

解决

  1. onForeground 回调中先检查播放器当前状态
  2. 如果播放器正在 playing / paused / prepared,直接返回不做任何操作
  3. 只在播放器状态异常时才执行恢复逻辑

七、完整流程时序图

下面是一次完整的锁屏播放交互流程:

scss 复制代码
用户操作                     应用侧                    系统播控中心
  │                            │                            │
  ├─ 点击播放                 │                              │
  │ ─────────────────────────>│                            │
  │                            │ 1. 创建并激活 AVSession    │
  │                            │ 2. setAVMetadata()         │
  │                            │ 3. setAVPlaybackState()    │
  │                            │ ─────────────────────────> │
  │                            │                            │ 显示音频信息
  │                            │                            │ 和播放控制按钮
  │                            │                            │
  ├─ 锁屏                     │                            │
  │ ──────────────────────────┤───────────────────────────▶│
  │                            │                            │ 锁屏播控中心可见
  │                            │                            │
  │                            │                            │ 用户点击快进
  │                            │ 4. on('fastForward')       │ <───────────────────
  │                            │ <───────────────────────── │
  │                            │                            │
  │                            │ 5. 播放器 seek +15秒       │
  │                            │ 6. setAVPlaybackState()    │
  │                            │ ─────────────────────────▶ │ 更新进度条显示
  │                            │                            │
  │                            │                            │ 用户点击下一首
  │                            │ 7. on('playNext')          │ <───────────────────
  │                            │ <───────────────────────── │
  │                            │                            │
  │                            │ 8. 切换音频 + 重置进度     │
  │                            │ 9. setAVMetadata()         │
  │                            │ 10. setAVPlaybackState()   │
  │                            │ ─────────────────────────▶ │ 显示新音频信息
  │                            │                            │

八、总结

实现 HarmonyOS 锁屏音频播放的核心要点:

  1. 正确配置权限和后台模式:确保应用在后台可以继续播放
  2. 创建和管理 AVSession 生命周期:与播放状态保持一致
  3. 及时同步元数据和播放状态:让锁屏播控中心展示正确的信息
  4. 监听并响应系统播控命令:实现完整的双向交互
  5. 合理管理前后台切换和歌曲切换:避免状态错乱和播放中断

通过本文介绍的方法,你可以实现一个体验流畅、与系统播控中心完美集成的音频播放功能。


参考文档

相关推荐
90后的晨仔2 小时前
鸿蒙应用动态桌面图标功能实现完全指南
harmonyos
nashane2 小时前
HarmonyOS 6学习:JsCrash“闪退”法医指南——从FaultLog堆栈还原崩溃现场的终极手册
学习·华为·harmonyos
李二。3 小时前
鸿蒙OS NEXT 批量重命名工具:PC端文件管理的效率革命
华为·harmonyos
HwJack204 小时前
鸿蒙背景下 Cocos Creator 的三大 JS 引擎:JIT 与热更新的十字路口
javascript·华为·harmonyos
提子拌饭1334 小时前
Column 嵌套布局:多级 Column 实现复杂纵向结构——鸿蒙 HarmonyOS ArkTS 原生学习应用
学习·华为·harmonyos·鸿蒙·鸿蒙系统
前端不太难6 小时前
鸿蒙 App 分布式数据同步:架构设计 + Demo 实现
分布式·状态模式·harmonyos
腾科IT教育7 小时前
从“韬定律“到鸿蒙生态:国产芯片底层突围,如何重塑应用开发的游戏规则?
华为·harmonyos
坚果派·白晓明7 小时前
鸿蒙PC适配实战:simdjson 三方库移植攻略与 AtomCode Skills 提效之道
c++·harmonyos·三方库·skills·atomcode·c/c++三方库·c/c++三方库适配