【鸿蒙实战开发】HarmonyOS本地媒体会话

200 篇文章 0 订阅
200 篇文章 0 订阅

交互过程

本地媒体会话的数据源均在设备本地,交互过程如图所示。

image.png

此过程中涉及两大角色,媒体会话提供方和媒体会话控制方。

说明

媒体会话控制方为系统应用,三方应用可以成为媒体会话提供方。

本地媒体会话中,媒体会话提供方通过媒体会话管理器和媒体会话控制方进行信息交互:

  1. 媒体会话提供方通过AVSessionManager创建AVSession对象。

  2. 媒体会话提供方通过AVSession对象,设置会话元数据(媒体ID、标题、媒体时长等)、会话播放属性(播放状态、播放倍速、播放位置等)等。

  3. 媒体会话控制方通过AVSessionManager创建AVSessionController对象。

  4. 媒体会话控制方通过AVSessionController对象可以监听对应会话元数据变化、播放属性变化等。

  5. 媒体会话控制方通过AVSessionController对象还可以向媒体会话发送控制命令。

  6. 媒体会话提供方通过AVSession对象可以监听来自媒体会话控制方的控制命令,例如:“play”播放、“playNext”播放下一首、“fastForward”快进、 “setSpeed”设置播放倍数等。

媒体会话管理器

媒体会话管理器(AVSessionManager),提供了管理AVSession的能力,可以创建AVSession、创建AVSessionController、发送系统控制事件,也支持对AVSession的状态进行监听。

实际上,AVSessionManager与AVSession、AVSessionController对象不同,并不是一个具体的对象,它是媒体会话的根命名域。在实际编程过程中,可以通过如下方式引入:

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

根命名域中的所有方法都可以作为AVSessionManager的方法。

例如,媒体会话提供方通过AVSessionManager创建媒体会话的示例如下所示:

// 创建session
let context: Context = getContext(this);
async function createSession() {
  let session: AVSessionManager.AVSession = await AVSessionManager.createAVSession(context, 'SESSION_NAME', 'audio');
  console.info(`session create done : sessionId : ${session.sessionId}`);
}

更多关于AVSessionManager的方法,可以参考API文档。

媒体会话提供方

音视频应用在实现音视频功能的同时,需要作为媒体会话提供方接入媒体会话,在媒体会话控制方(例如播控中心)中展示媒体相关信息,及响应媒体会话控制方下发的播控命令。

基本概念

  • 媒体会话元数据(AVMetadata): 用于描述媒体数据相关属性,包含标识当前媒体的ID(assetId),上一首媒体的ID(previousAssetId),下一首媒体的ID(nextAssetId),标题(title),专辑作者(author),专辑名称(album),词作者(writer),媒体时长(duration)等属性。

  • 媒体播放状态(AVPlaybackState):用于描述媒体播放状态的相关属性,包含当前媒体的播放状态(state)、播放位置(position)、播放倍速(speed)、缓冲时间(bufferedTime)、循环模式(loopMode)、是否收藏(isFavorite)、正在播放的媒体Id(activeItemId)、自定义媒体数据(extras)等属性。

接口说明

媒体会话提供方使用的关键接口如下表所示。接口返回值有两种返回形式:callback和promise,下表中为callback形式接口,promise和callback只是返回值方式不一样,功能相同。

更多API说明请参见API文档。

image.png
image.png

开发步骤

音视频应用作为媒体会话提供方接入媒体会话的基本步骤如下所示:

  1. 通过AVSessionManager的方法创建并激活媒体会话。
import { avSession as AVSessionManager } from '@kit.AVSessionKit';

// 开始创建并激活媒体会话
// 创建session
let context: Context = getContext(this);
async function createSession() {
  let type: AVSessionManager.AVSessionType = 'audio';
  let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);
  await session.activate();
  console.info(`session create done : sessionId : ${session.sessionId}`);
}
  1. 跟随媒体信息的变化,及时设置媒体会话信息。需要设置的媒体会话信息主要包括:

    • 媒体会话元数据AVMetadata。
    • 媒体播放状态AVPlaybackState。

音视频应用设置的媒体会话信息,会被媒体会话控制方通过AVSessionController相关方法获取后进行显示或处理。

import { avSession as AVSessionManager } from '@kit.AVSessionKit';
import { BusinessError } from '@kit.BasicServicesKit';

let context: Context = getContext(this);
async function setSessionInfo() {
  // 假设已经创建了一个session,如何创建session可以参考之前的案例
  let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', 'audio');
  // 播放器逻辑··· 引发媒体信息与播放状态的变更
  // 设置必要的媒体信息
  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}`);
  });
  // 简单设置一个播放状态 - 暂停 未收藏
  let playbackState: AVSessionManager.AVPlaybackState = {
    state:AVSessionManager.PlaybackState.PLAYBACK_STATE_PAUSE,
    isFavorite:false
  };
  session.setAVPlaybackState(playbackState, (err) => {
    if (err) {
      console.error(`Failed to set AVPlaybackState. Code: ${err.code}, message: ${err.message}`);
    } else {
      console.info(`SetAVPlaybackState successfully`);
    }
  });
  // 设置一个播放列表
  let queueItemDescription_1: AVSessionManager.AVMediaDescription = {
    assetId: '001',
    title: 'music_name',
    subtitle: 'music_sub_name',
    description: 'music_description',
    mediaImage: "PIXELMAP_OBJECT",
    extras: {'extras':'any'}
  };
  let queueItem_1: AVSessionManager.AVQueueItem = {
    itemId: 1,
    description: queueItemDescription_1
  };
  let queueItemDescription_2: AVSessionManager.AVMediaDescription = {
    assetId: '002',
    title: 'music_name',
    subtitle: 'music_sub_name',
    description: 'music_description',
    mediaImage: "PIXELMAP_OBJECT",
    extras: {'extras':'any'}
  };
  let queueItem_2: AVSessionManager.AVQueueItem = {
    itemId: 2,
    description: queueItemDescription_2
  };
  let queueItemsArray = [queueItem_1, queueItem_2];
  session.setAVQueueItems(queueItemsArray).then(() => {
    console.info(`SetAVQueueItems successfully`);
  }).catch((err: BusinessError) => {
    console.error(`Failed to set AVQueueItem, error code: ${err.code}, error message: ${err.message}`);
  });
  // 设置媒体播放列表名称
  let queueTitle = 'QUEUE_TITLE';
  session.setAVQueueTitle(queueTitle).then(() => {
    console.info(`SetAVQueueTitle successfully`);
  }).catch((err: BusinessError) => {
    console.info(`Failed to set AVQueueTitle, error code: ${err.code}, error message: ${err.message}`);
  });
}
  1. 设置用于被媒体会话控制方拉起的UIAbility。当用户操作媒体会话控制方的界面时,例如点击播控中心的卡片,可以拉起此处配置的UIAbility。

    设置UIAbility时通过WantAgent接口实现,更多关于WantAgent的信息请参考WantAgent。

import { wantAgent } from '@kit.AbilityKit';
import { avSession as AVSessionManager } from '@kit.AVSessionKit';
import { wantAgent } from '@kit.AbilityKit';

let context: Context = getContext(this);
async function getWantAgent() {
  let type: AVSessionManager.AVSessionType = 'audio';
  // 假设已经创建了一个session,如何创建session可以参考之前的案例
  let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);
  let wantAgentInfo: wantAgent.WantAgentInfo = {
    wants: [
      {
        bundleName: 'com.example.musicdemo',
        abilityName: 'MainAbility'
      }
    ],
    operationType: wantAgent.OperationType.START_ABILITIES,
    requestCode: 0,
    wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
  }
  wantAgent.getWantAgent(wantAgentInfo).then((agent) => {
    session.setLaunchAbility(agent);
  })
}
  1. 设置一个即时的自定义会话事件,以供媒体控制方接收到事件后进行相应的操作。

说明

通过dispatchSessionEvent方法设置的数据不会保存在会话对象或AVSession服务中。

  import { avSession as AVSessionManager } from '@kit.AVSessionKit';
import { BusinessError } from '@kit.BasicServicesKit';

let context: Context = getContext(this);
async function dispatchSessionEvent() {
  // 假设已经创建了一个session,如何创建session可以参考之前的案例
  let type: AVSessionManager.AVSessionType = 'audio';
  let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);
  let eventName = 'dynamic_lyric';
  await session.dispatchSessionEvent(eventName, {lyric : 'This is my lyric'}).then(() => {
    console.info(`Dispatch session event successfully`);
  }).catch((err: BusinessError) => {
    console.error(`Failed to dispatch session event. Code: ${err.code}, message: ${err.message}`);
  })
}
  1. 设置与当前会话相关的自定义媒体数据包,以供媒体控制方接收到事件后进行相应的操作。

说明
通过setExtras方法设置的数据包会被存储在AVSession服务中,数据的生命周期与会话一致;会话对应的Controller可以使用getExtras来获取该数据。

 import { avSession as AVSessionManager } from '@kit.AVSessionKit';
import { BusinessError } from '@kit.BasicServicesKit';

let context: Context = getContext(this);
async function setExtras() {
  // 假设已经创建了一个session,如何创建session可以参考之前的案例
  let type: AVSessionManager.AVSessionType = 'audio';
  let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);
  await session.setExtras({extra : 'This is my custom meida packet'}).then(() => {
    console.info(`Set extras successfully`);
  }).catch((err: BusinessError) => {
    console.error(`Failed to set extras. Code: ${err.code}, message: ${err.message}`);
  })
}
  1. 注册播控命令事件监听,便于响应用户通过媒体会话控制方,例如播控中心,下发的播控命令。

    在Session侧注册的监听分为固定播控命令和高级播控事件两种。

    6.1 固定控制命令的监听

说明
媒体会话提供方在注册相关固定播控命令事件监听时,监听的事件会在媒体会话控制方的getValidCommands()方法中体现,即媒体会话控制方会认为对应的方法有效,进而根据需要触发相应暂不使用时的事件。为了保证媒体会话控制方下发的播控命令可以被正常执行,媒体会话提供方请勿进行无逻辑的空实现监听。

Session侧的固定播控命令主要包括播放、暂停、上一首、下一首等基础操作命令,详细介绍请参AVControlCommand

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

let context: Context = getContext(this);
async function setListenerForMesFromController() {
  // 假设已经创建了一个session,如何创建session可以参考之前的案例
  let type: AVSessionManager.AVSessionType = 'audio';
  let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);
  // 一般在监听器中会对播放器做相应逻辑处理
  // 不要忘记处理完后需要通过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
  });
}

6.2 高级播控事件的监听

Session侧的可以注册的高级播控事件主要包括:

  • skipToQueueItem: 播放列表其中某项被选中的事件。
  • handleKeyEvent: 按键事件。
  • outputDeviceChange: 播放设备变化的事件。
  • commonCommand: 自定义控制命令变化的事件。
import { avSession as AVSessionManager } from '@kit.AVSessionKit';

let context: Context = getContext(this);
async function setListenerForMesFromController() {
  // 假设已经创建了一个session,如何创建session可以参考之前的案例
  let type: AVSessionManager.AVSessionType = 'audio';
  let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);
  // 一般在监听器中会对播放器做相应逻辑处理
  // 不要忘记处理完后需要通过set接口同步播放相关信息,参考上面的用例
  session.on('skipToQueueItem', (itemId) => {
    console.info(`on skipToQueueItem , do skip task`);
    // do some tasks ···
  });
  session.on('handleKeyEvent', (event) => {
    console.info(`on handleKeyEvent , the event is ${JSON.stringify(event)}`);
    // do some tasks ···
  });
  session.on('outputDeviceChange', (device) => {
    console.info(`on outputDeviceChange , the device info is ${JSON.stringify(device)}`);
    // do some tasks ···
  });
  session.on('commonCommand', (commandString, args) => {
    console.info(`on commonCommand , command is ${commandString}, args are ${JSON.stringify(args)}`);
    // do some tasks ···
  });
}
  1. 获取当前媒体会话自身的控制器,与媒体会话对应进行通信交互。
   import { avSession as AVSessionManager } from '@kit.AVSessionKit';

let context: Context = getContext(this);
async function createControllerFromSession() {
  // 假设已经创建了一个session,如何创建session可以参考之前的案例
  let type: AVSessionManager.AVSessionType = 'audio';
  let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);

  // 通过已有session获取一个controller对象
  let controller = await session.getController();

  // controller可以与原session对象进行基本的通信交互,比如下发播放命令
  let avCommand: AVSessionManager.AVControlCommand = {command:'play'};
  controller.sendControlCommand(avCommand);

  // 或者做状态变更监听
  controller.on('playbackStateChange', 'all', (state) => {

    // do some things
  });

  // controller可以做的操作还有很多,具体可以参考媒体会话控制方相关的说明
}
  1. 音视频应用在退出,并且不需要继续播放时,及时取消监听以及销毁媒体会话释放资源。

取消播控命令监听的示例代码如下所示 :

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

let context: Context = getContext(this);
async function unregisterSessionListener() {
  // 假设已经创建了一个session,如何创建session可以参考之前的案例
  let type: AVSessionManager.AVSessionType = 'audio';
  let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);

  // 取消指定session下的相关监听
  session.off('play');
  session.off('pause');
  session.off('stop');
  session.off('playNext');
  session.off('playPrevious');
  session.off('skipToQueueItem');
  session.off('handleKeyEvent');
  session.off('outputDeviceChange');
  session.off('commonCommand');
}

销毁媒体会话示例代码如下所示:

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

let context: Context = getContext(this);
async function destroySession() {
  // 假设已经创建了一个session,如何创建session可以参考之前的案例
  let type: AVSessionManager.AVSessionType = 'audio';
  let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);
  // 主动销毁已创建的session
  session.destroy((err) => {
    if (err) {
      console.error(`Failed to destroy session. Code: ${err.code}, message: ${err.message}`);
    } else {
      console.info(`Destroy : SUCCESS `);
    }
  });
}

应用接入AVSession场景介绍

音视频应用在实现音视频功能的同时,需要接入媒体会话即AVSession Kit,下文将提供一些典型的接入AVSession的展示和控制场景,方便开发者根据场景进行适配。

对于不同的场景,将会在系统的播控中心看到不同的UI呈现。同时,在不同的场景下,应用的接入处理也需要遵循不同的规范约束。

哪些场景下需要接入AVSession

AVSession会对后台的音频播放、VOIP通话做约束,所以通常来说,长音频应用、听书类应用、长视频应用、VOIP类应用等都需要接入AVSession。当应用在没有创建接入AVSession的情况下进行了上述业务,那么系统会在检测到应用后台时,停止对应的音频播放,静音通话声音,以达到约束应用行为的目的。这种约束,应用上架前在本地就可以验证。

对于其他使用到音频播放的应用,比如游戏,直播等场景,接入AVSession不是必选项,只是可选,取决于应用是否有后台播放的使用诉求。若应用需要后台播放,那么接入AVSession仍然是必须的,否则业务的正常功能会受到限制。

当应用需要实现后台播放等功能时,需要使用BackgroundTasks Kit(后台任务管理)的能力,申请对应的长时任务,避免进入挂起(Suspend)状态。

接入流程

应用接入AVSession流程分为如下几个步骤:

  1. 确定应用需要创建的会话类型,创建对应的会话,不同类型决定了播控中心展示的控制模板样式。
  2. 按需创建后台任务。
  3. 设置必要的元数据(Metadata),以在播控中心展示响应的信息,包括不限于:当前媒体的ID(assetId),上一首媒体的ID(previousAssetId),下一首媒体的ID(nextAssetId),标题(title),专辑作者(author),专辑名称(album),词作者(writer),媒体时长(duration)等属性。
  4. 设置播放相关的状态,包括不限于:当前媒体的播放状态(state)、播放位置(position)、播放倍速(speed)、缓冲时间(bufferedTime)、循环模式(loopMode)、是否收藏(isFavorite)、正在播放的媒体Id(activeItemId)、自定义媒体数据(extras)等属性。
  5. 按需注册不同的控制命令,包括不限于:播放/暂停、上下一首、快进快退、收藏、循环模式、进度条。
  6. 应用退出或者无对应业务时,注销会话。

创建不同类型的会话

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

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

  • video类型,播控中心的控制样式为:快退,上一首,播放/暂停,下一首,快进。

  • voice_call类型,通话类型。

使用代码示例:

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

// 开始创建并激活媒体会话
// 创建session
let context: Context = getContext(this);
async function createSession() {
  let type: AVSessionManager.AVSessionType = 'audio';
  let session = await AVSessionManager.createAVSession(context,'SESSION_NAME', type);

  // 激活接口要在元数据、控制命令注册完成之后再执行
  await session.activate();
  console.info(`session create done : sessionId : ${session.sessionId}`);
}

创建后台任务

当应用需要实现后台播放等功能时,需要使用BackgroundTasks Kit(后台任务管理)的能力,申请对应的长时任务,避免进入挂起(Suspend)状态。

对媒体类播放来说,需要申请AUDIO_PLAYBACK BackgroundMode的长时任务。

设置元数据

通用元数据

应用可以通过setAVMetadata把会话的一些元数据信息设置给系统,从而在播控中心界面进行展示,包括不限制:当前媒体的ID(assetId),上一首媒体的ID(previousAssetId),下一首媒体的ID(nextAssetId),标题(title),专辑作者(author),专辑名称(album),词作者(writer),媒体时长(duration)等。

import { avSession as AVSessionManager } from '@kit.AVSessionKit';
import { BusinessError } from '@kit.BasicServicesKit';

let context: Context = getContext(this);
async function setSessionInfo() {
  // 假设已经创建了一个session,如何创建session可以参考之前的案例
  let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', 'audio');
  // 设置必要的媒体信息
  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}`);
  });
 }

歌词

对于长音频来说,播控中心提供了歌词的展示页面,对于应用来说,接入也比较简单,只需要把歌词内容设置给系统。播控中心会解析歌词内容,并根据播放进度进行同步的刷新。

import { avSession as AVSessionManager } from '@kit.AVSessionKit';
import { BusinessError } from '@kit.BasicServicesKit';

let context: Context = getContext(this);
async function setListener() {
  // 假设已经创建了一个session,如何创建session可以参考之前的案例
  let type: AVSessionManager.AVSessionType = 'audio';
  let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);

  // 把歌词信息设置给AVSession
  let metadata: AVSessionManager.AVMetadata = {
    assetId: '0',
    title: 'TITLE',
    mediaImage: 'IMAGE',
    lyric: 'http://www.test.lyric',
  };
  session.setAVMetadata(metadata).then(() => {
    console.info(`SetAVMetadata successfully`);
  }).catch((err: BusinessError) => {
    console.error(`Failed to set AVMetadata. Code: ${err.code}, message: ${err.message}`);
  });

}

历史歌单

历史歌单(历史播放列表)的接入,需要应用只需要先注册支持后台启动模式(executeMode为background)的播放歌单(PlayMusicList)意图,再实现相应的意图调用接口即可,具体实现参意图框架歌单播放意图的意图注册意图调用

设置歌单:

音乐类应用在播放内容时,可以通过setAVMetadata接口设置当前播放内容的歌单信息,歌单信息由下面几个字段组成:

  • avQueueName: 歌单的名称,接入歌单必选
  • avQueueId: 歌单的唯一标识id,接入歌单必选
  • avQueueImage: 歌单的图片资源,接入歌单必选

系统媒体信息根据应用上报实时刷新,若应用接入歌单,则确保一直上报歌单数据。

歌单播放:

当应用向系统设置歌单后,用户在播控中心界面可以对歌单进行播放控制,系统会将歌单的唯一标识id传回应用,应用会在意图调用接口中获取到歌单的id,如后台模式需要实现方法onExecuteInUIAbilityBackgroundMode,歌单会包装在参数param中传递回应用,具体代码实现参考PlayMusicList意图调用。

歌单启动播放后,应用仍然通过AVSessionController来接收控制命令。

需要注意的是:

  1. 应用接入历史歌单,就相当于接入了系统后台冷启动播放。
  2. 若应用只注册后台模式的PlayMusicList意图,但没有通过setAVMetadata设置歌单内容,请务必支持无歌单Id的PlayMusicList意图后台播放,系统后台冷启动会启动空歌单Id的歌单意图播放,由应用来决定播放内容
import { avSession as AVSessionManager } from '@kit.AVSessionKit';
import { BusinessError } from '@kit.BasicServicesKit';
 
let context: Context = getContext(this);
async function setListener() {
  // 假设已经创建了一个session,如何创建session可以参考之前的案例
  let type: AVSessionManager.AVSessionType = 'audio';
  let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);
  // 把歌单信息设置给AVSession
  let metadata: AVSessionManager.AVMetadata = {
  // 下面内容均由应用指定
  assetId: '0',
  avQueueName: 'myQueue',
  avQueueId: 'myQueue123',
  avQueueImage: "PIXELMAP_OBJECT",
  };
  session.setAVMetadata(metadata).then(() => {
  console.info(`SetAVMetadata successfully`);
  }).catch((err: BusinessError) => {
    console.error(`Failed to set AVMetadata. Code: ${err.code}, message: ${err.message}`);
  });
  // 上报播放状态,参考播放状态
}

媒体资源金标

对于长音频,播控中心提供了媒体资源金标的展示,媒体资源金标又可称为应用媒体音频音源的标识,目前暂时只支持展示AudioVivid标识。

对于应用来说,接入只需要在AVMetadata中通知系统,当前播放音频的音源标识,播控就会同步展示。

import { avSession as AVSessionManager } from '@kit.AVSessionKit';
import { BusinessError } from '@kit.BasicServicesKit';

let context: Context = getContext(this);
async function setListener() {
  // 假设已经创建了一个session,如何创建session可以参考之前的案例
  let type: AVSessionManager.AVSessionType = 'audio';
  let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);

  // 把媒体音源信息设置给AVSession
  let metadata: AVSessionManager.AVMetadata = {
    assetId: '0',
    title: 'TITLE',
    mediaImage: 'IMAGE',
    // 标识该媒体音源是AudioVivid
    displayTags: AVSessionManager.DisplayTag.TAG_AUDIO_VIVID,
  };
  session.setAVMetadata(metadata).then(() => {
    console.info(`SetAVMetadata successfully`);
  }).catch((err: BusinessError) => {
    console.error(`Failed to set AVMetadata. Code: ${err.code}, message: ${err.message}`);
  });
}

设置播放状态

通用播放状态

应用可以通过setAVPlaybackState。把当前的播放状态设置给系统,以在播控中心界面进行展示。

播放状态一般是在资源播放后会进行变化的内容,包括:当前媒体的播放状态(state)、播放位置(position)、播放倍速(speed)、缓冲时间(bufferedTime)、循环模式(loopMode)、是否收藏(isFavorite)、正在播放的媒体Id(activeItemId)、自定义媒体数据(extras)等。

import { avSession as AVSessionManager } from '@kit.AVSessionKit';
import { BusinessError } from '@kit.BasicServicesKit';

let context: Context = getContext(this);
async function setSessionInfo() {
  // 假设已经创建了一个session,如何创建session可以参考之前的案例
  let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', 'audio');

  // 播放器逻辑··· 引发媒体信息与播放状态的变更
  // 简单设置一个播放状态 - 暂停 未收藏
  let playbackState: AVSessionManager.AVPlaybackState = {
    state:AVSessionManager.PlaybackState.PLAYBACK_STATE_PAUSE,
    isFavorite:false
  };
  session.setAVPlaybackState(playbackState, (err: BusinessError) => {
   if (err) {
      console.error(`Failed to set AVPlaybackState. Code: ${err.code}, message: ${err.message}`);
    } else {
      console.info(`SetAVPlaybackState successfully`);
    }
  });
}

进度条

应用如果支持在播控中心展示进度,那么在媒体资源播放中,需要设置资源的时长、播放状态(暂停、播放)、播放位置、倍速,播控中心会使用这些信息进行进度的展示:

import { avSession as AVSessionManager } from '@kit.AVSessionKit';
import { BusinessError } from '@kit.BasicServicesKit';

let context: Context = getContext(this);
async function setListener() {
  // 假设已经创建了一个session,如何创建session可以参考之前的案例
  let type: AVSessionManager.AVSessionType = 'audio';
  let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);

  // 设置媒体资源时长
  let metadata: AVSessionManager.AVMetadata = {
    assetId: '0',
    title: 'TITLE',
    mediaImage: 'IMAGE',
    duration: 23000, // 资源的时长,以ms为单位
  };
  session.setAVMetadata(metadata).then(() => {
    console.info(`SetAVMetadata successfully`);
  }).catch((err: BusinessError) => {
    console.error(`Failed to set AVMetadata. Code: ${err.code}, message: ${err.message}`);
  });

  // 设置状态: 播放状态,进度位置,播放倍速,缓存的时间
  let playbackState: AVSessionManager.AVPlaybackState = {
    state: AVSessionManager.PlaybackState.PLAYBACK_STATE_PLAY, // 播放状态
    position: {
      elapsedTime: 1000, // 已经播放的位置,以ms为单位
      updateTime: new Date().getTime(), // 应用更新当前位置时的时间戳,以ms为单位
    },
    speed: 1.0, // 可选,默认是1.0,播放的倍速,按照应用内支持的speed进行设置,系统不做校验
    bufferedTime: 14000, // 可选,资源缓存的时间,以ms为单位
  };
  session.setAVPlaybackState(playbackState, (err) => {
    if (err) {
      console.error(`Failed to set AVPlaybackState. Code: ${err.code}, message: ${err.message}`);
    } else {
      console.info(`SetAVPlaybackState successfully`);
    }
  });
}

系统的播控中心会根据应用设置的信息自行进行播放进度的计算,而不需要应用实时更新播放进度;

但是应用需要如下状态发生变化的时候,再更新AVPlaybackState,否则系统会发生计算错误:

  • state
  • position
  • speed

应用在真实播放开始时,再上报进度起始position;若播放存在buffer状态,可以先上报播放状态为AVSessionManager.PlaybackState.PLAYBACK_STATE_BUFFERING,来通知系统不刷新进度。

关于进度条有一些特殊情况需要处理:

  1. 歌曲支持试听

    (1)应用不需要设置完整的歌曲时长,则只需要设置歌曲的试听时长。当应用仅设置歌曲的试听时长而不是完整时长,用户在播控中心触发进度控制时,应用收到的时长也是VIP试听时长内的相对时间戳位置,而不是完整歌曲的绝对时间戳位置,应用需要重新计算歌曲从零开始的绝对时间戳进行实际响应处理。

    (2)如果应用设置完整歌曲时长,但需要系统支持试听片段,也可以在播放时上报起始进度position,当收到的seek指令超过试听片段时,上报试听截止position,系统播控的进度会跟随回弹。

  2. 歌曲不支持试听

    如果歌曲不支持试听,那么理论上应用内也不支持播放,这时可以把 duration 设置为 -1,以通知系统不显示实际的时长。

  3. 广告等内容的时长设置

    对于有前贴广告、后贴广告的资源来说,建议这么处理:

    • 播放广告时,单独设置广告的时长 duration
    • 当进入到正片播放的时候,则重新设置一次新的时长,以与广告进行区分。

注册控制命令

应用接入AVSession,可以通过注册不同的控制命令来实现播控中心界面上的控制操作,即通过on接口注册不同的控制命令参数,即可实现对应的功能。

具体的接口参考接口注册。

说明

创建AVSession后,请先注册应用支持的控制命令,再激活 Session

媒体资源支持的控制命令列表:

控制命令功能说明
play播放命令。
pause暂停命令。
stop停止命令。
playNext播放下一首命令。
playPrevious播放上一首命令。
fastForward快进命令。
rewind快退命令。
playFromAssetId根据某个资源id进行播放命令。
seek跳转命令。
setSpeed设置播放速率命令。
setLoopMode设置循环模式命令。
toggleFavorite设置是否收藏命令。
skipToQueueItem设置播放列表其中某项被选中播放的命令。
handleKeyEvent设置按键事件的命令。
commonCommand设置自定义控制命令。

通话类应用支持的控制:

控制命令功能说明
answer接听电话的命令。
hangUp通话挂断的命令。
toggleCallMute通话静音或解除静音的命令。

不支持命令的处理

系统支持的控制命令对于不支持的控制,比如应用不支持“上一首”的命令处理,只需要使用off 接口注销对应的控制命令,系统的播控中心会相应的对该控制界面进行置灰处理,以明确告知用户此控制命令不支持。

1.  import { avSession as AVSessionManager } from '@kit.AVSessionKit';

3.  let context: Context = getContext(this);
4.  async function unregisterSessionListener() {
5.  // 假设已经创建了一个session,如何创建session可以参考之前的案例
6.  let type: AVSessionManager.AVSessionType = 'audio';
7.  let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);

9.  // 取消指定session下的相关监听
10.  session.off('play');
11.  session.off('pause');
12.  session.off('stop');
13.  session.off('playNext');
14.  session.off('playPrevious');
15.  }

快进快退

系统支持三种快进快退的时长,应用可以通过接口进行设置;同时注册快进快退的回调命令,以响应控制。

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

let context: Context = getContext(this);
async function unregisterSessionListener() {
  // 假设已经创建了一个session,如何创建session可以参考之前的案例
  let type: AVSessionManager.AVSessionType = 'audio';
  let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);

  // 取消指定session下的相关监听
  session.off('play');
  session.off('pause');
  session.off('stop');
  session.off('playNext');
  session.off('playPrevious');
}

收藏

音乐类应用实现收藏功能,那么需要注册收藏的控制响应on(‘toggleFavorite’)。

import { avSession as AVSessionManager } from '@kit.AVSessionKit';
import { BusinessError } from '@kit.BasicServicesKit';

let context: Context = getContext(this);
async function unregisterSessionListener() {
  // 假设已经创建了一个session,如何创建session可以参考之前的案例
  let type: AVSessionManager.AVSessionType = 'audio';
  let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);

  // 设置支持的快进快退的时长设置给AVSession
  let metadata: AVSessionManager.AVMetadata = {
    assetId: '0', // 由应用指定,用于标识应用媒体库里的媒体
    title: 'TITLE',
    mediaImage: 'IMAGE',
    skipIntervals: AVSessionManager.SkipIntervals.SECONDS_10,
  };
  session.setAVMetadata(metadata).then(() => {
    console.info(`SetAVMetadata successfully`);
  }).catch((err: BusinessError) => {
    console.error(`Failed to set AVMetadata. Code: ${err.code}, message: ${err.message}`);
  });

  session.on('fastForward', (time ?: number) => {
    console.info(`on fastForward , do fastForward task`);
    // do some tasks ···
  });
  session.on('rewind', (time ?: number) => {
    console.info(`on rewind , do rewind task`);
    // do some tasks ···
  });
}

循环模式

针对音乐类应用,系统的播控中心界面会默认展示循环模式的控制操作,目前系统支持四种固定的循环模式控制,参考: LoopMode。

播控中心支持固定的四种循环模式的切换,即: 随机播放、顺序播放、单曲循环、列表循环。应用收到循环模式切换的指令并切换后,需要向系统上报切换后的LoopMode。

若应用内支持的LoopMode不在系统固定的四个循环模式内,需要选择四个固定循环模式其一向系统上报,由应用自定。

实现参考:

import { avSession as AVSessionManager } from '@kit.AVSessionKit';
import { BusinessError } from '@kit.BasicServicesKit';

let context: Context = getContext(this);
async function setListener() {
 // 假设已经创建了一个session,如何创建session可以参考之前的案例
 let type: AVSessionManager.AVSessionType = 'audio';
 let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);

 // 应用启动时/内部切换循环模式,需要把应用内的当前的循环模式设置给AVSession
 let playBackState: AVSessionManager.AVPlaybackState = {
   loopMode: AVSessionManager.LoopMode.LOOP_MODE_SINGLE,
 };
 session.setAVPlaybackState(playBackState).then(() => {
   console.info(`set AVPlaybackState successfully`);
 }).catch((err: BusinessError) => {
   console.error(`Failed to set AVPlaybackState. Code: ${err.code}, message: ${err.message}`);
 });

 // 应用注册循环模式的控制监听
 session.on('setLoopMode', (mode) => {
   console.info(`on setLoopMode ${mode}`);
   // 应用收到设置循环模式的指令后,应用自定下一个模式,切换完毕后通过AVPlaybackState上报切换后的LoopMode
   let playBackState: AVSessionManager.AVPlaybackState = {
    loopMode: AVSessionManager.LoopMode.LOOP_MODE_SINGLE,
   };
   session.setAVPlaybackState(playBackState).then(() => {
     console.info(`set AVPlaybackState successfully`);
   }).catch((err: BusinessError) => {
     console.error(`Failed to set AVPlaybackState. Code: ${err.code}, message: ${err.message}`);
   });
 });

}

进度控制

应用如果支持进度显示,进一步也可以支持进度控制。应用需要响应seek的控制命令,那么当用户在播控中心的界面上进行拖动操作时,应用就会收到对应的回调。参考实现:

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

let context: Context = getContext(this);
async function setListener() {
 // 假设已经创建了一个session,如何创建session可以参考之前的案例
 let type: AVSessionManager.AVSessionType = 'audio';
 let session = await AVSessionManager.createAVSession(context, 'SESSION_NAME', type);

 session.on('seek', (position: number) => {
   console.info(`on seek , the time is ${JSON.stringify(position)}`);

   // 由于应用内seek可能会触发较长的缓冲等待,可以先把状态设置为 Buffering
   let playbackState: AVSessionManager.AVPlaybackState = {
     state: AVSessionManager.PlaybackState.PLAYBACK_STATE_BUFFERING, // 缓冲状态
   };
   session.setAVPlaybackState(playbackState, (err) => {
     if (err) {
       console.error(`Failed to set AVPlaybackState. Code: ${err.code}, message: ${err.message}`);
     } else {
       console.info(`SetAVPlaybackState successfully`);
     }
   });

   // 应用响应seek命令,使用应用内播放器完成seek实现

   // 应用内更新新的位置后,也需要同步更新状态给系统
   playbackState.state = AVSessionManager.PlaybackState.PLAYBACK_STATE_PLAY; // 播放状态
   playbackState.position = {
     elapsedTime: position, // 已经播放的位置,以ms为单位
     updateTime: new Date().getTime(), // 应用更新当前位置的时间戳,以ms为单位
   }
   session.setAVPlaybackState(playbackState, (err) => {
     if (err) {
       console.error(`Failed to set AVPlaybackState. Code: ${err.code}, message: ${err.message}`);
     } else {
       console.info(`SetAVPlaybackState successfully`);
     }
   });

 });
}

适配媒体通知

当前系统不直接向应用提供主动发送媒体控制通知的接口,那么当应用进入播放状态时,系统会自动发送通知,同时在通知和锁屏界面进行展示。

说明

  1. 目前仅audio类型的媒体会话会在通知入口展示,video类型暂时不支持展示。
  2. 通知中心、锁屏下的播控卡片的展示,由系统进行发送,并控制相应的生命周期。

写在最后

有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)文档用来跟着学习是非常有必要的。

希望这一份鸿蒙学习文档能够给大家带来帮助,有需要的小伙伴自行领取,限时开源,先到先得~无套路领取!!

如果你是一名有经验的资深Android移动开发、Java开发、前端开发、对鸿蒙感兴趣以及转行人员,可以直接领取这份资料

请点击→纯血版全套鸿蒙HarmonyOS学习文档

鸿蒙(HarmonyOS NEXT)5.0最新学习路线

在这里插入图片描述

路线图适合人群:

IT开发人员:想要拓展职业边界
零基础小白:鸿蒙爱好者,希望从0到1学习,增加一项技能。
技术提升/进阶跳槽:发展瓶颈期,提升职场竞争力,快速掌握鸿蒙技术

获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习文档

《鸿蒙 (HarmonyOS)开发入门教学视频》

在这里插入图片描述

《鸿蒙生态应用开发V3.0白皮书》

在这里插入图片描述

《鸿蒙 (OpenHarmony)开发基础到实战手册》

OpenHarmony北向、南向开发环境搭建

在这里插入图片描述

《鸿蒙开发基础》

●ArkTS语言
●安装DevEco Studio
●运用你的第一个ArkTS应用
●ArkUI声明式UI开发
.……
在这里插入图片描述

《鸿蒙开发进阶》

●Stage模型入门
●网络管理
●数据管理
●电话服务
●分布式应用开发
●通知与窗口管理
●多媒体技术
●安全技能
●任务管理
●WebGL
●国际化开发
●应用测试
●DFX面向未来设计
●鸿蒙系统移植和裁剪定制
……
在这里插入图片描述

《鸿蒙进阶实战》

●ArkTS实践
●UIAbility应用
●网络案例
……
在这里插入图片描述

获取以上完整鸿蒙HarmonyOS学习文档,请点击→纯血版全套鸿蒙HarmonyOS学习文档

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值