前言

在开发专业的音频或视频类应用时,仅仅在应用内部实现播放逻辑是远远不够的。当用户将应用退至后台或者锁定手机屏幕时,他们期望依然能够通过系统的下拉控制中心或是连接的蓝牙耳机来操控播放进度。如果应用无法响应这些系统级的外部指令,整体的用户体验将大打折扣。

HarmonyOS 6 提供了 AVSession 模块来解决这一业务闭环。该模块充当了应用内部媒体状态与系统全局播控中心之间的标准数据桥梁。本文将带领开发者结合此前封装的 AVPlayer 播放器,完整接入 AVSession 播控会话。我们将实现应用切入后台后的音乐持续播放,精准同步歌曲元数据至锁屏界面,并接管系统控制台发出的外部控制指令,打造一个符合现代操作系统规范的专业级后台音乐服务。

一、 播控中枢 AVSession 的运行机制

在鸿蒙多媒体架构中,AVSession 被定义为音视频会话管理的核心枢纽。操作系统的全局播控中心并不直接干预应用程序内部的底层播放器实例,而是通过读取应用注册在系统中的 AVSession 状态来实时更新外部 UI 界面。

开发者需要利用 avSession 模块创建一个针对当前应用的媒体会话实例。创建该实例时必须明确声明会话的业务类型。对于音乐类应用通常指定为音频类型。会话一旦激活,系统便会在下拉通知中心和锁屏界面为其分配一个专属的媒体展示卡片。应用程序必须承担起向该会话通道持续输送最新播放状态和媒体元数据的责任,以此维持外部界面的信息准确性。

二、 状态上报 同步元数据与播放进度

要让系统的锁屏界面正确显示当前播放的歌曲名称、歌手身份以及专辑封面,开发者需要构建底层的 AVMetadata 元数据对象,并将其高频上报给 AVSession 会话实例。

除了静态的元数据,动态的播放状态同步显得更加关键。当内部 AVPlayer 的运行状态发生物理改变时,业务代码必须同步构建 AVPlaybackState 对象。该对象不仅记录了当前的核心播放状态(包括播放中、已暂停、缓冲中等),还严格包含了当前的播放时间轴进度以及倍速信息。

import { avSession } from '@kit.AVSessionKit';

async function updateSystemControlCenter(session: avSession.AVSession, isPlaying: boolean, position: number) {
  // 封装并推送静态媒体元数据
  const metadata: avSession.AVMetadata = {
    assetId: 'song_track_01',
    title: '鸿蒙星河交响曲',
    artist: 'Harmony Band',
    // 封面支持传递沙箱物理路径或网络链接
    mediaImage: 'https://www.example.com/cover.jpg'
  };
  await session.setAVMetadata(metadata);

  // 封装并推送动态播放状态
  const playbackState: avSession.AVPlaybackState = {
    state: isPlaying ? avSession.PlaybackState.PLAYBACK_STATE_PLAY : avSession.PlaybackState.PLAYBACK_STATE_PAUSE,
    position: {
      elapsedTime: position,
      updateTime: new Date().getTime()
    }
  };
  await session.setAVPlaybackState(playbackState);
}

三、 指令下发 响应全局物理与虚拟按键

操作系统的控制台以及外设下发的控制指令被称为媒体命令。AVSession 实例支持注册多种核心控制命令的监听回调函数。

当用户在系统的锁屏界面点击下一首按钮或者按下蓝牙耳机的实体播放键时,系统底层会精准地向对应的 AVSession 派发相应的动作指令。应用程序在此回调通道中捕获该事件后,需主动调度内部封装的 AVPlayer 实例去执行对应的媒体切换逻辑。一个专业完备的音频应用,通常必须接管的指令包含播放、暂停、上一首、下一首以及进度条拖拽。

import { avSession } from '@kit.AVSessionKit';
import { media } from '@kit.MediaKit';

function registerSessionCommands(session: avSession.AVSession, player: media.AVPlayer) {
  // 拦截系统发出的播放指令
  session.on('play', () => {
    console.info('[AVSession] Received play command from system');
    player.play();
  });
  
  // 拦截系统发出的暂停指令
  session.on('pause', () => {
    console.info('[AVSession] Received pause command from system');
    player.pause();
  });

  // 拦截切换下一首的业务指令
  session.on('playNext', () => {
    console.info('[AVSession] User clicked next track on lock screen');
    // 在此处执行业务层面的下一首媒体资源替换逻辑
  });
}

四、 长时任务 突破后台资源冻结限制

按照鸿蒙系统的底层功耗调度策略,普通的应用程序在退至后台短暂时间后,其进程会被系统级挂起以节约电池电量。在此种受限状态下,应用内部的 AVPlayer 也会因为彻底失去 CPU 时间片而被迫停止硬件解码。

要实现真正的后台无缝音乐播放,业务层必须向系统权限中心申请音频播放类别的长时任务(Continuous Task)。开发者通过 backgroundTaskManager 模块发起系统请求,明确声明当前应用正在执行合法且耗时的后台音频播放业务。申请一旦获批,系统不仅会保持当前应用进程在后台的持续存活,还会在屏幕顶部的状态栏区域常驻一个微型提示图标,明确告知用户当前存在运行中的后台多媒体服务。长时任务保活与 AVSession 状态同步的有机结合,共同构成了现代后台音频业务的完整底层基石。

五、 综合实战 后台音乐播控组件

为了提供一个可供直接编译体验的工程示例,我们封装了一个全局级别的音频服务单例。该服务深度集成了 AVPlayer 音频底层解码、AVSession 系统状态同步以及 BackgroundTask 背景保活长时任务。

你可以直接在真机设备上运行以下完整代码。点击开始播放后将应用程序切换至后台,随后下拉操作系统的通知中心或直接锁定手机屏幕,即可清晰观察到系统定制的媒体播放卡片,并能够直接通过该全局卡片反向操控应用内部的音乐播放状态。

import { media } from '@kit.MediaKit';
import { avSession } from '@kit.AVSessionKit';
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
import { wantAgent } from '@kit.AbilityKit';
import { common } from '@kit.AbilityKit';
import { promptAction } from '@kit.ArkUI';

// 构建统筹后台播放与系统会话的音频服务核心类
class BackgroundAudioService {
  private static instance: BackgroundAudioService;
  private avPlayer: media.AVPlayer | null = null;
  private session: avSession.AVSession | null = null;
  private context: common.UIAbilityContext | null = null;
  
  // 维护内部状态以驱动系统控制台刷新
  private isPlaying: boolean = false;
  private currentPosition: number = 0;

  public static getInstance(): BackgroundAudioService {
    if (!BackgroundAudioService.instance) {
      BackgroundAudioService.instance = new BackgroundAudioService();
    }
    return BackgroundAudioService.instance;
  }

  // 服务初始化 注入上下文并构建两大核心实例
  public async init(context: common.UIAbilityContext) {
    this.context = context;
    try {
      this.avPlayer = await media.createAVPlayer();
      // 创建标识为音频业务的全局系统会话
      this.session = await avSession.createAVSession(context, 'MusicSession', 'audio');
      
      this.bindPlayerEvents();
      this.bindSessionCommands();
      
      console.info('[AudioService] AVPlayer and AVSession initialized successfully');
    } catch (err) {
      console.error(`[AudioService] Initialization failed ${(err as Error).message}`);
    }
  }

  // 挂载系统控制中心的外部事件拦截器
  private bindSessionCommands() {
    if (!this.session) return;

    this.session.on('play', () => {
      this.play();
    });

    this.session.on('pause', () => {
      this.pause();
    });

    this.session.on('playNext', () => {
      promptAction.showToast({ message: '系统控制台触发下一首指令' });
      // 业务逻辑 在此切换新的 URL 并重置播放器
    });

    this.session.on('playPrevious', () => {
      promptAction.showToast({ message: '系统控制台触发上一首指令' });
    });
  }

  // 挂载底层播放器的内部状态监听器
  private bindPlayerEvents() {
    if (!this.avPlayer) return;

    this.avPlayer.on('stateChange', async (state: media.AVPlayerState) => {
      switch (state) {
        case 'initialized':
          this.avPlayer!.prepare();
          break;
        case 'prepared':
          this.avPlayer!.play();
          break;
        case 'playing':
          this.isPlaying = true;
          this.syncSessionState();
          break;
        case 'paused':
          this.isPlaying = false;
          this.syncSessionState();
          break;
        case 'completed':
          this.isPlaying = false;
          this.syncSessionState();
          break;
      }
    });

    this.avPlayer.on('timeUpdate', (time: number) => {
      this.currentPosition = time;
      // 为防止极高频的通信开销 仅在特定条件下全量同步时间进度
      if (time % 1000 < 200) {
        this.syncSessionState();
      }
    });
  }

  // 核心方法 向系统控制中心推流同步最新元数据与播放状态
  private async syncSessionState() {
    if (!this.session) return;

    try {
      // 填充控制面板的静态展示信息
      const metadata: avSession.AVMetadata = {
        assetId: 'demo_track_01',
        title: '鸿蒙后台保活演示曲目',
        artist: 'HarmonyOS Developer',
      };
      await this.session.setAVMetadata(metadata);

      // 填充控制面板的动态按钮状态与进度条
      const playbackState: avSession.AVPlaybackState = {
        state: this.isPlaying ? avSession.PlaybackState.PLAYBACK_STATE_PLAY : avSession.PlaybackState.PLAYBACK_STATE_PAUSE,
        position: {
          elapsedTime: this.currentPosition,
          updateTime: new Date().getTime()
        }
      };
      await this.session.setAVPlaybackState(playbackState);

      // 激活会话以确保展示层接收数据
      await this.session.activate();
    } catch (err) {
      console.error(`[AudioService] Sync state failed ${(err as Error).message}`);
    }
  }

  // 申请后台长时任务保活机制
  private async startContinuousTask() {
    if (!this.context) return;
    try {
      const wantAgentInfo: wantAgent.WantAgentInfo = {
        wants: [
          {
            bundleName: this.context.abilityInfo.bundleName,
            abilityName: this.context.abilityInfo.name
          }
        ],
        operationType: wantAgent.OperationType.START_ABILITY,
        requestCode: 0,
        wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
      };
      
      const agent = await wantAgent.getWantAgent(wantAgentInfo);
      
      // 发起音频类别的长时任务请求
      await backgroundTaskManager.startBackgroundRunning(
        this.context,
        backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK,
        agent
      );
      console.info('[AudioService] Continuous task started');
    } catch (err) {
      console.error(`[AudioService] Continuous task failed ${(err as Error).message}`);
    }
  }

  // 停止后台保活任务
  private async stopContinuousTask() {
    if (!this.context) return;
    try {
      await backgroundTaskManager.stopBackgroundRunning(this.context);
      console.info('[AudioService] Continuous task stopped');
    } catch (err) {
      console.error(`[AudioService] Stop task failed ${(err as Error).message}`);
    }
  }

  // 暴露给外部 UI 的控制接口
  public async loadAndPlay(url: string) {
    if (!this.avPlayer) return;
    await this.avPlayer.reset();
    
    // 启动后台保活机制防御进程冻结
    await this.startContinuousTask();
    
    this.avPlayer.url = url;
  }

  public async play() {
    if (this.avPlayer) {
      await this.avPlayer.play();
    }
  }

  public async pause() {
    if (this.avPlayer) {
      await this.avPlayer.pause();
    }
  }

  // 全面释放资源与会话通道
  public async destroy() {
    this.stopContinuousTask();
    
    if (this.session) {
      await this.session.deactivate();
      await this.session.destroy();
      this.session = null;
    }
    if (this.avPlayer) {
      await this.avPlayer.release();
      this.avPlayer = null;
    }
  }
}

const audioService = BackgroundAudioService.getInstance();


@Entry
@Component
struct GlobalAudioPlayerPage {
  @State isPlaying: boolean = false;
  // 提供一个稳定可用的远程测试音频地址
  private testAudioUrl = 'https://www.w3school.com.cn/i/horse.mp3';

  async aboutToAppear() {
    const context = getContext(this) as common.UIAbilityContext;
    await audioService.init(context);
  }

  aboutToDisappear() {
    audioService.destroy();
  }

  build() {
    Column() {
      Text('全局播控与后台长时任务实战')
        .fontSize(22)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 40, bottom: 40 })

      // 模拟音乐播放器封面效果
      Circle()
        .width(200)
        .height(200)
        .fill('#1F2937')
        .shadow({ radius: 20, color: 'rgba(0,0,0,0.2)', offsetY: 10 })
        .margin({ bottom: 40 })

      Button('加载并开启后台播放')
        .width('80%')
        .height(50)
        .backgroundColor('#0A59F7')
        .margin({ bottom: 20 })
        .onClick(() => {
          this.isPlaying = true;
          audioService.loadAndPlay(this.testAudioUrl);
          promptAction.showToast({ message: '音频已加载 请切至系统后台查看控制台' });
        })

      Row({ space: 20 }) {
        Button('应用内暂停')
          .layoutWeight(1)
          .height(50)
          .backgroundColor('#F75555')
          .onClick(() => {
            this.isPlaying = false;
            audioService.pause();
          })

        Button('应用内恢复')
          .layoutWeight(1)
          .height(50)
          .backgroundColor('#10C16C')
          .onClick(() => {
            this.isPlaying = true;
            audioService.play();
          })
      }
      .width('80%')

      Text('测试步骤指导\n1 点击上方加载按钮开启播放\n2 将当前应用滑动切至后台\n3 下拉通知中心或锁屏 观察系统媒体控制卡片\n4 在系统卡片上点击暂停 验证应用层状态同步响应')
        .fontSize(14)
        .fontColor('#6B7280')
        .lineHeight(24)
        .margin({ top: 40 })
        .padding(20)
        .backgroundColor('#F3F4F6')
        .borderRadius(12)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFFFFF')
    .alignItems(HorizontalAlign.Center)
  }
}

总结

将一款普通的音频应用升级为系统级媒体服务,其核心门槛在于彻底打通应用独立沙箱与系统全局调度中心之间的壁垒。

我们深入拆解了 AVSession 模块的运作原理。我们明确了通过 AVMetadataAVPlaybackState 两个关键数据结构,向系统持续输送最新媒体信息的数据同步规范。掌握了在会话通道上注册各类监听器,从而合法接管并响应系统控制台乃至外部蓝牙设备物理按键指令的技术手段。最后,通过配置申请长时任务权限,彻底突破了应用切入后台便遭遇线程挂起的硬件限制瓶颈。

掌握这套结合了底层解码、系统会话机制以及高阶资源保活的组合方案,是构建商业级音乐、播客类应用不可或缺的基础能力。

Logo

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

更多推荐