鸿蒙音视频开发:AVSession Kit 简介

424 阅读7分钟

AVSession Kit(Audio & Video Session Kit,音视频播控服务)是系统提供的音视频管控服务,用于统一管理系统中所有音视频行为,帮助开发者快速构建音视频统一展示和控制能力。

能力范围

  • 提供音视频统一管控能力,音视频类应用接入AVSession后,可以发送应用的数据(比如正在播放的歌曲、歌曲的播放状态等)到系统层面,用户可以通过系统播控中心语音助手等应用切换多个应用、多个设备播放。
  • 提供音频后台约束能力,音频接入AVSession后,将使应用后台播放的音视频可见可控。此功能需要同时申请后台任务
  • 未接入AVSession的应用在退到后台时,将会被强制暂停播放。
  • 基于上一条描述,解决应用在后台恶意播放,而用户无法找到对应应用无法关闭的问题。

基础概念

  • 媒体会话(AVSession)

    媒体会话是开发者的音视频应用提供给系统播控中心等媒体会话控制方的关键信息及两端之间进行信息交换的通道。其中可以做如下配置。

    1. 可以设置媒体信息
    2. 设置播放状态
    3. 设置播放列表
    4. 设置播放列表名称
    5. 设置用于被媒体会话空置方拉起的UIAbility
    6. 设置自定义会话事件
    7. 设置自定义媒体数据包
    8. 注册固定控制命令事件监听器,用于响应用户通过媒体会话控制方
    9. 注册高级播控事件监听器
    10. 可以获取当前媒体会话控制器(AVSessionController)
  • 媒体会话提供方 指接入媒体会话的音视频应用。

  • 媒体会话控制方

    接入媒体会话并具有全局管控音视频行为功能的应用,例如系统播控中心、语音助手等

  • 媒体会话控制器(AVSessionController)

可以控制媒体会话提供方的应用播放行为,也可以获取应用的播放信息,还可以监听音视频应用播放状态的变化,用于确保媒体会话信息在音视频应用和播控中心之间的同步。

  • 媒体会话管理器(AVSessionManager)

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

  • 本地媒体会话

    本地媒体会话在本地设备中的媒体会话提供方和媒体会话控制方之间建立连接,实现系统中音视频应用统一的媒体播放控制和媒体信息显示。

  • 分布式媒体会话

    分布式媒体会话在跨设备场景中的媒体会话提供方和媒体会话控制方之间建立连接,实现音视频应用跨设备的媒体播放控制和媒体信息显示。例如,将设备A中播放的内容投播到设备B,并在设备B中进行播放控制。

本地媒体会话

交互过程

image.png

  • 本地媒体会话的数据源均在设备本地
  • 媒体会话控制方为系统应用,三方应用可以成为媒体会话提供方。

媒体会话提供方

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

基本概念

  • 媒体会话元数据(AVMetadata): 用于描述媒体数据相关属性,包含标识当前媒体的IDassetId),上一首媒体的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文档

接口名说明
createAVSession(context: Context, tag: string, type: AVSessionType, callback: AsyncCallback): void10+创建媒体会话。一个UIAbility只能存在一个媒体会话,重复创建会失败。
setAVMetadata(data: AVMetadata, callback: AsyncCallback): void10+设置媒体会话元数据。
setAVPlaybackState(state: AVPlaybackState, callback: AsyncCallback): void10+设置媒体会话播放状态。
setLaunchAbility(ability: WantAgent, callback: AsyncCallback): void10+设置启动UIAbility。
getController(callback: AsyncCallback): void10+获取当前会话自身控制器。
getOutputDevice(callback: AsyncCallback): void10+获取播放设备相关信息。
activate(callback: AsyncCallback): void10+激活媒体会话。
deactivate(callback: AsyncCallback): void10+禁用当前会话。
destroy(callback: AsyncCallback): void10+销毁媒体会话。
setAVQueueItems(items: Array, callback: AsyncCallback): void 10+设置媒体播放列表。
setAVQueueTitle(title: string, callback: AsyncCallback): void10+设置媒体播放列表名称。
dispatchSessionEvent(event: string, args: {[key: string]: Object}, callback: AsyncCallback): void10+设置会话内自定义事件。
setExtras(extras: {[key: string]: Object}, callback: AsyncCallback): void10+设置键值对形式的自定义媒体数据包。
getOutputDeviceSync(): OutputDeviceInfo10+使用同步方法获取当前输出设备信息。

开发步骤

  • 创建媒体会话
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}`);
}
  • 创建并设置媒体会话元数据AVMetadata
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}`);
});
  • 创建并设置媒体播放状态AVPlaybackState
// 简单设置一个播放状态 - 暂停 未收藏
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}`);
});
  • 设置并设置用于被媒体会话控制方拉起的UIAbility
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.START_ABILITIES
    operationType: 2,
    requestCode: 0,
    wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
  }
  wantAgent.getWantAgent(wantAgentInfo).then((agent) => {
    session.setLaunchAbility(agent);
  })
}
  • 设置自定义会话事件

通过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}`);
  })
}
  • 设置自定义媒体数据包

通过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}`);
  })
}
  • 注册来自媒体会话控制方的事件的监听
    • 固定控制命令监听

媒体会话提供方在注册相关固定播控命令事件监听时,监听的事件会在媒体会话控制方的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
  });
}
  • 高级播控事件监听

    • 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 ···
  });
}
  • 获取媒体会话控制器
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可以做的操作还有很多,具体可以参考媒体会话控制方相关的说明
}
  • 取消播控命令监听
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。

  • 如接入AVSession,请按照下表根据应用的业务场景接入,应用上架前请根据此表格自检,以确保应用的基础体验。

判断原则如下:

  1. 应用按类型区分,正确接入所有必接项(**√ **)。
  2. 应用按照实际功能的有无,可选接入项如本身具备接入条件,则需要接入;如本身没有该功能,可不接入(О)。
  3. 应用按类型区分,无需接入的不接入(/)。
```

基础特性

子特性

具体功能

音乐类/听书类

视频类

直播类

浏览器类

新闻阅读类

Voip类

完整体验优化

基本接入要求

完整体验优化

基本接入要求

接入要求

接入AVSession

基础播控(必需)

播放信息

媒体封面

О

О

/

主标题

/

进度与时间

О

/

播放控制

播放/暂停

/

上下一首/集

/

/

/

/

按钮置灰

/

点击播控卡片跳转应用指定页面

/

音视频投播

通话设备切换

通话设备切换组件

/

/

/

/

/

/

/

基础播控(按需选择)

播放信息

副标题

О

О

О

О

О

О

/

滚动歌词

О

/

/

/

/

/

/

媒体音源特殊标识

О

О

О

О

/

/

/

/

播放控制

收藏

О

/

/

/

/

/

/

循环模式

О

/

/

/

/

/

/

快进/快退

/

/

О

/

О

/

/

播控增强

快捷播放

播放按钮一键冷启动播放

/

/

/

/

/

/

历史歌单

О

/

/

/

/

/

/

歌单推荐

О

/

/

/

/

/

/

特殊组件

统一音量组件

О

О

/

/

/

/

音视频投播

基础投播能力

Cast+协议音视频投播

О

О

О

О

О

/

DRM数字加密视频投播

/

/

О

О

/

/

/

/

DLNA协议音视频投播

О

О

О

О

О

/

投播能力增强

镜像投屏自动切换资源投播

О

О

/

/

/

/

投播组件

应用内投播组件(半模态)

О

О

О

О

О

/