鸿蒙开发5.0【AVPlayer音频后台播放】

场景描述

音乐播放是媒体最重要的组成之一,以下是AVPlayer将Audio媒体资源(比如mp3等)转码为可听见的音频模拟信号,并通过输出设备进行播放。

场景一:使用avPlayer进行后台播放音乐

想要实现应用后台播放,那么接入AVSession是必须的,否则业务的正常功能会同时受到限制,也必须有[BackgroundTasks Kit](后台任务管理)的能力,申请对应的长时任务,避免进入挂起(Suspend)状态。

应用不申请后台任务会被冻结,不注册AVSession会被暂停。

步骤一:创建avPlayer实现音频播放

创建avPlayer并加载音频资源

async avPlayerFdSrcDemo() {
  // 创建avPlayer实例对象
  avPlayer = await media.createAVPlayer();
  // 创建状态机变化回调函数
  this.setAVPlayerCallback(avPlayer);
  // 通过UIAbilityContext的resourceManager成员的getRawFd接口获取媒体资源播放地址
  let context = getContext(this) as common.UIAbilityContext;
  let fileDescriptor = await context.resourceManager.getRawFd('123.mp3');
  // 返回类型为{fd,offset,length},fd为HAP包fd地址,offset为媒体资源偏移量,length为播放长度
  let avFileDescriptor: media.AVFileDescriptor =
    { fd: fileDescriptor.fd, offset: fileDescriptor.offset, length: fileDescriptor.length };
  this.isSeek = true; // 支持seek操作
  // 为fdSrc赋值触发initialized状态机上报
  avPlayer.fdSrc = avFileDescriptor;
}

注册avPlayer回调函数

// 注册avplayer回调函数
setAVPlayerCallback(avPlayer: media.AVPlayer) {
  // seek操作结果回调函数
  avPlayer.on('seekDone', (seekDoneTime: number) => {
    console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
  })
  // error回调监听函数,当avPlayer在操作过程中出现错误时调用 reset接口触发重置流程
  avPlayer.on('error', (err: BusinessError) => {
    console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
    avPlayer.reset(); // 调用reset重置资源,触发idle状态
  })
  // 状态机变化回调函数
  avPlayer.on('stateChange', async (state: string, reason: media.StateChangeReason) => {
    switch (state) {
      case 'idle': // 成功调用reset接口后触发该状态机上报
        console.info('AVPlayer state idle called.');
        avPlayer.release(); // 调用release接口销毁实例对象
        break;
      case 'initialized': // avplayer 设置播放源后触发该状态上报
        console.info('AVPlayer state initialized called.');
        avPlayer.prepare();
        break;
      case 'prepared': // prepare调用成功后上报该状态机
        console.info('AVPlayer state prepared called.');
        avPlayer.audioInterruptMode=audio.InterruptMode.SHARE_MODE;
        avPlayer.play(); // 调用播放接口开始播放
        break;
      case 'playing': // play成功调用后触发该状态机上报
        console.info('AVPlayer state playing called.');
        break;
      case 'paused': // pause成功调用后触发该状态机上报
        console.info('AVPlayer state paused called.');
        avPlayer.play(); // 再次播放接口开始播放
        break;
      case 'completed': // 播放结束后触发该状态机上报
        console.info('AVPlayer state completed called.');
        avPlayer.stop(); //调用播放结束接口
        break;
      case 'stopped': // stop接口成功调用后触发该状态机上报
        console.info('AVPlayer state stopped called.');
        avPlayer.reset(); // 调用reset接口初始化avplayer状态
        break;
      case 'released':
        console.info('AVPlayer state released called.');
        break;
      default:
        console.info('AVPlayer state unknown called.');
        break;
    }
  })
}

步骤二:创建AVSession,使音频接入播控中心

AVSession在构造方法中支持不同的类型参数,由 [AVSessionType]定义,不同的类型代表了不同场景的控制能力,对于播控中心来说,会展示不同的控制模版。

  • audio类型,播控中心的控制样式为:收藏,上一首,播放/暂停,下一首,循环模式。
  • video类型,播控中心的控制样式为:快退,上一首,播放/暂停,下一首,快进。
  • voice_call类型,通话类型。

创建AVSession

// 创建session
async  createSession() {
  let type: AVSessionManager.AVSessionType = 'audio';
  /*
   * context:应用上下文,提供获取应用程序环境信息的能力。
   * tag:会话的自定义名称。
   *type:会话类型。
   */
  let session = await AVSessionManager.createAVSession(context,'SESSION_NAME', type);
  // 设置必要的媒体信息
  let metadata: AVSessionManager.AVMetadata = {
    assetId: '0', // 由应用指定,用于标识应用媒体库里的媒体
    title: 'TITLE',
    mediaImage: 'IMAGE',
    artist: 'ARTIST',
  };
  session.setAVMetadata(metadata).then(() => {
    console.info(`SetAVMetadata successfully`);
  }).catch((err: BusinessError) => {
    console.error(`Failed to set AVMetadata. Code: ${err.code}, message: ${err.message}`);
  });
  //监听事件
  this.setListenerForMesFromController(session)
  // 激活接口要在元数据、控制命令注册完成之后再执行
  await session.activate();
  console.info(`session create done : sessionId : ${session.sessionId}`);
}

注:播控中心的显示必须要配上session.on控制命令的监听

async  setListenerForMesFromController(session: avSession.AVSession) {
  // 一般在监听器中会对播放器做相应逻辑处理
  // 不要忘记处理完后需要通过set接口同步播放相关信息,参考上面的用例
  session.on('play', () => {
    console.info(`on play , do play task`);
    // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('play')取消监听
    // 处理完毕后,请使用SetAVPlayState上报播放状态
  });
  session.on('pause', () => {
    console.info(`on pause , do pause task`);
    // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('pause')取消监听
    // 处理完毕后,请使用SetAVPlayState上报播放状态
  });
  session.on('stop', () => {
    console.info(`on stop , do stop task`);
    // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('stop')取消监听
    // 处理完毕后,请使用SetAVPlayState上报播放状态
  });
  session.on('playNext', () => {
    console.info(`on playNext , do playNext task`);
    // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('playNext')取消监听
    // 处理完毕后,请使用SetAVPlayState上报播放状态,使用SetAVMetadata上报媒体信息
  });
  session.on('playPrevious', () => {
    console.info(`on playPrevious , do playPrevious task`);
    // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('playPrevious')取消监听
    // 处理完毕后,请使用SetAVPlayState上报播放状态,使用SetAVMetadata上报媒体信息
  });
  session.on('fastForward', () => {
    console.info(`on fastForward , do fastForward task`);
    // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('fastForward')取消监听
    // 处理完毕后,请使用SetAVPlayState上报播放状态和播放position
  });
  session.on('rewind', () => {
    console.info(`on rewind , do rewind task`);
    // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('rewind')取消监听
    // 处理完毕后,请使用SetAVPlayState上报播放状态和播放position
  });

  session.on('seek', (time) => {
    console.info(`on seek , the seek time is ${time}`);
    // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('seek')取消监听
    // 处理完毕后,请使用SetAVPlayState上报播放状态和播放position
  });
  session.on('setSpeed', (speed) => {
    console.info(`on setSpeed , the speed is ${speed}`);
    // do some tasks ···
  });
  session.on('setLoopMode', (mode) => {
    console.info(`on setLoopMode , the loop mode is ${mode}`);
    // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('setLoopMode')取消监听
    // 应用自定下一个模式,处理完毕后,请使用SetAVPlayState上报切换后的LoopMode
  });
  session.on('toggleFavorite', (assetId) => {
    console.info(`on toggleFavorite , the target asset Id is ${assetId}`);
    // 如暂不支持该指令,请勿注册;或在注册后但暂不使用时,通过session.off('toggleFavorite')取消监听
    // 处理完毕后,请使用SetAVPlayState上报收藏结果isFavorite
  });
}

步骤三:创建长时任务

在module.json5申请ohos.permission.KEEP_BACKGROUND_RUNNING权限:

"requestPermissions": [
  {
    "name": "ohos.permission.KEEP_BACKGROUND_RUNNING",
    "reason": "$string:app_name",
    "usedScene": {
      "abilities": [
        "FormAbility"
      ],
      "when":"always"
    }
  },
]

声明后台模式类型

在对应的UIAbility下配置backgroundModes

"backgroundModes": [
  // 长时任务类型的配置项
  "audioPlayback"
]

配置长时任务信息

let wantAgentInfo: wantAgent.WantAgentInfo = {
  // 点击通知后,将要执行的动作列表
  // 添加需要被拉起应用的bundleName和abilityName
  wants: [
    {
      bundleName: "com.example.avplayerdemo",
      abilityName: "com.example.avplayerdemo.EntryAbility"
    }
  ],
  // 指定点击通知栏消息后的动作是拉起ability
  actionType: wantAgent.OperationType.START_ABILITY,
  // 使用者自定义的一个私有值
  requestCode: 0,
  // 点击通知后,动作执行属性
  wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
};

申请长时任务

// 通过wantAgent模块下getWantAgent方法获取WantAgent对象
wantAgent.getWantAgent(wantAgentInfo).then((wantAgentObj: WantAgent) => {
  backgroundTaskManager.startBackgroundRunning(context,
    backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK, wantAgentObj).then(() => {
    console.info(`Succeeded in operationing startBackgroundRunning.`);
  }).catch((err: BusinessError) => {
    console.error(`Failed to operation startBackgroundRunning. Code is ${err.code}, message is ${err.message}`);
  });
});

场景二:在播放音乐过程中,有其它音频流(如:导航音、电话)进入,进行相关处理

在多个音频流同时播放场景下,如果系统不加管控,会造成多个音频流混音播放,容易让用户感到嘈杂,造成不好的用户体验。为了解决这个问题,系统预设了音频打断([InterruptEvent])策略,对多音频播放的并发进行管控。为满足应用对多音频并发策略的不同需求,音频打断策略预设了两种焦点模式,针对同一应用创建的多个音频流,应用可通过设置[焦点模式],选择由应用自主管控或由系统统一管控。

步骤一:完成上述场景一。

步骤二:设置焦点模式。

  • 共享焦点模式(SHARE_MODE):由同一应用创建的多个音频流,共享一个音频焦点。这些音频流之间的并发规则由应用自主决定,音频打断策略不会介入。当其他应用创建的音频流与该应用的音频流并发播放时,才会触发音频打断策略的管控。
  • 独立焦点模式(INDEPENDENT_MODE):应用创建的每一个音频流均会独立拥有一个音频焦点,当多个音频流并发播放时,会触发音频打断策略的管控。
avPlayer.audioInterruptMode=audio.InterruptMode.SHARE_MODE;

注:只允许在prepared/playing/paused/completed状态下设置。

步骤三:设置音频类型。

let audioRendererInfo: audio.AudioRendererInfo = {
  usage: audio.StreamUsage.STREAM_USAGE_NAVIGATION, // 音频流使用类型
  rendererFlags: 0 // 音频渲染器标志,0代表普通音频渲染器,1代表低时延音频渲染器。ArkTS接口暂不支持低时延音频渲染器。
}
avPlayer.audioRendererInfo=audioRendererInfo;

步骤四:创建监听音频焦点打断。

调用avPlayer的[on(‘audioInterrupt’)]函数进行监听,当收到音频打断事件(InterruptEvent)时,应用需根据其内容,做出相应的调整。

avPlayer.on('audioInterrupt', async(interruptEvent: audio.InterruptEvent) => {
  // 先读取interruptEvent.forceType的类型,判断系统是否已强制执行相应操作
  // 再读取interruptEvent.hintType的类型,做出相应的处理
  if (interruptEvent.forceType === audio.InterruptForceType.INTERRUPT_FORCE) {
    // 强制打断类型(INTERRUPT_FORCE):音频相关处理已由系统执行,应用需更新自身状态,做相应调整
    switch (interruptEvent.hintType) {
      case audio.InterruptHint.INTERRUPT_HINT_PAUSE:
      // 此分支表示系统已将音频流暂停(临时失去焦点),为保持状态一致,应用需切换至音频暂停状态
      // 临时失去焦点:待其他音频流释放音频焦点后,本音频流会收到resume对应的音频打断事件,到时可自行继续播放
        isPlay = false; // 此句为简化处理,代表应用切换至音频暂停状态的若干操作
        break;
      case audio.InterruptHint.INTERRUPT_HINT_STOP:
      // 此分支表示系统已将音频流停止(永久失去焦点),为保持状态一致,应用需切换至音频暂停状态
      // 永久失去焦点:后续不会再收到任何音频打断事件,若想恢复播放,需要用户主动触发。
        isPlay = false; // 此句为简化处理,代表应用切换至音频暂停状态的若干操作
        break;
      case audio.InterruptHint.INTERRUPT_HINT_DUCK:
      // 此分支表示系统已将音频音量降低(默认降到正常音量的20%),为保持状态一致,应用需切换至降低音量播放状态
      // 若应用不接受降低音量播放,可在此处选择其他处理方式,如主动暂停等
        isDucked = true; // 此句为简化处理,代表应用切换至降低音量播放状态的若干操作
        break;
      case audio.InterruptHint.INTERRUPT_HINT_UNDUCK:
      // 此分支表示系统已将音频音量恢复正常,为保持状态一致,应用需切换至正常音量播放状态
        isDucked = false; // 此句为简化处理,代表应用切换至正常音量播放状态的若干操作
        break;
      default:
        break;
    }
  }
});

场景:

新播放的音频流
voip通话voip消息音乐视频游戏听书/听新闻导航
正在播放的音频流voip通话拒绝新的voip通话降低voip消息音量降低音乐音量降低视频音量降低游戏音量降低听书/听新闻音量降低导航音量
voip消息停止正在播放的voip消息停止正在播放的voip消息停止voip消息停止voip消息同时播放停止voip消息降低导航音量
音乐暂停音乐音乐暂停音乐停止音乐停止正在播放的音乐停止音乐同时播放
视频暂停视频视频暂停视频停止视频停止视频停止正在播放的视频停止视频
游戏暂停游戏游戏暂停游戏同时播放同时播放暂停游戏停止正在播放的游戏
听书/听新闻暂停听书/听新闻听书/听新闻暂停听书/听新闻停止听书/听新闻停止听书/听新闻停止听书/听新闻停止听书/听新闻
导航降低导航音量导航降低导航音量降低导航音量降低音乐音量降低视频音量降低游戏音量
以上就是本篇文章所带来的鸿蒙开发中一小部分技术讲解;想要学习完整的鸿蒙全栈技术。可以在结尾找我可全部拿到!
下面是鸿蒙的完整学习路线,展示如下:
1

除此之外,根据这个学习鸿蒙全栈学习路线,也附带一整套完整的学习【文档+视频】,内容包含如下

内容包含了:(ArkTS、ArkUI、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、鸿蒙南向开发、鸿蒙项目实战)等技术知识点。帮助大家在学习鸿蒙路上快速成长!

鸿蒙【北向应用开发+南向系统层开发】文档

鸿蒙【基础+实战项目】视频

鸿蒙面经

2

为了避免大家在学习过程中产生更多的时间成本,对比我把以上内容全部放在了↓↓↓想要的可以自拿喔!谢谢大家观看!
3

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值