文档管理中心
FAQ媒体开发音频和视频音视频播控(AVSession)应用内直播切换后台后声音正常但无播控组件

应用内直播切换后台后声音正常但无播控组件

问题现象

在应用内打开直播,切换直播界面至后台时,音频播放仍能正常持续,听觉体验无中断。然而,系统并未触发预期的播控中心。

背景知识

  • AVSession Kit:音视频播控服务是系统提供的音视频管控服务,用于统一管理系统中所有音视频行为,帮助开发者快速构建音视频统一展示和控制能力。
  • 应用接入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. 按需注册不同的控制命令,包括不限于:播放/暂停、上下一首、快进快退、收藏、循环模式、进度条。应用退出或者无对应业务时,注销会话。

问题定位

  1. 查看问题日志信息进行分析,全局搜索关键词AVSession,可见此时当前的AVSession会话焦点(Session Focus)指向了另一个应用(进程ID:1654),并且有且只有这一个最高优先级媒体会话。
    收起
    自动换行
    深色代码主题
    复制
    1. 08-12 16:32:27.873 1654 22650 I C02B91/av_session/AVSession: [GetAllSessionDescriptors]GetAllSessionDescriptors with size=1, topSession:com.***
  2. 全局搜索代码关键词AVSessionManager进行定位,发现应用并未通过AVSessionManager接口创建并激活媒体会话。

分析结论

应用并未创建、接入AVSession会话,导致当应用进入后台时,看不到相应的播控中心。

修改建议

建议应用正确创建并接入AVSession,确保设置正确的元数据,且至少注册一条控制命令,否则播控中心不显示当前应用接入的播控中心。参考示例如下:

收起
自动换行
深色代码主题
复制
  1. import { avSession as AVSessionManager } from '@kit.AVSessionKit';
  2. import { BusinessError } from '@kit.BasicServicesKit';
  3. @Entry
  4. @Component
  5. struct avSessionExample {
  6. message: string = 'hello world';
  7. @State currentState: AVSessionManager.AVPlaybackState = {
  8. state: AVSessionManager.PlaybackState.PLAYBACK_STATE_INITIAL, // 播放状态。
  9. position: {
  10. elapsedTime: 0, // 已经播放的位置,以ms为单位。
  11. updateTime: new Date().getTime(), // 应用更新当前位置时的时间戳,以ms为单位。
  12. },
  13. };
  14. @State elapsedTime: number = 0;
  15. @State updateTime: number = 0;
  16. context = this.getUIContext().getHostContext() as Context;
  17. type: AVSessionManager.AVSessionType = 'video';
  18. session: AVSessionManager.AVSession | null = null;
  19. controller: VideoController = new VideoController();
  20. aboutToAppear(): void {
  21. this.initAvsession();
  22. }
  23. aboutToDisappear(): void {
  24. this.destroySession();
  25. }
  26. async initAvsession() {
  27. // 创建并激活媒体会话
  28. this.session = await AVSessionManager.createAVSession(this.context, 'SESSION_NAME', this.type);
  29. this.setAVMetadata();
  30. this.registerSessionListener();
  31. this.session.activate();
  32. }
  33. private registerSessionListener() {
  34. this.session!.on('play', () => {
  35. console.info(`on play , do pause task`);
  36. this.controller.start();
  37. });
  38. this.session!.on('pause', () => {
  39. this.controller.pause();
  40. });
  41. }
  42. // 设置播放状态
  43. private setAVPlaybackState(currentState: AVSessionManager.AVPlaybackState) {
  44. this.session!.setAVPlaybackState(currentState, (err) => {
  45. if (err) {
  46. console.error(`Failed to set AVPlaybackState. Code: ${err.code}, message: ${err.message}`);
  47. } else {
  48. console.info(`SetAVPlaybackState successfully`);
  49. }
  50. });
  51. }
  52. // 设置元数据
  53. private setAVMetadata() {
  54. let metadata: AVSessionManager.AVMetadata = {
  55. assetId: '0',
  56. title: 'TITLE',
  57. mediaImage: 'IMAGE',
  58. duration: 91000,
  59. };
  60. this.session!.setAVMetadata(metadata).then(() => {
  61. console.info(`SetAVMetadata successfully`);
  62. }).catch((err: BusinessError) => {
  63. console.error(`Failed to set AVMetadata. Code: ${err.code}, message: ${err.message}`);
  64. });
  65. }
  66. async destroySession() {
  67. this.session?.off('play');
  68. this.session?.off('pause');
  69. this.session?.destroy();
  70. }
  71. build() {
  72. Column() {
  73. Video({
  74. src: $r('app.media.videoTest'),
  75. controller: this.controller,
  76. })
  77. .width('100%')
  78. .height(300)
  79. .onStart(() => {
  80. console.info('onStart');
  81. this.currentState.state = AVSessionManager.PlaybackState.PLAYBACK_STATE_PLAY;
  82. this.updateTime = new Date().getTime();
  83. this.currentState.position = { elapsedTime: this.elapsedTime, updateTime: this.updateTime };
  84. this.setAVPlaybackState(this.currentState);
  85. })
  86. .onPause(() => {
  87. console.info('onPause');
  88. this.currentState.state = AVSessionManager.PlaybackState.PLAYBACK_STATE_PAUSE;
  89. this.updateTime = new Date().getTime();
  90. this.currentState.position = { elapsedTime: this.elapsedTime, updateTime: this.updateTime };
  91. this.setAVPlaybackState(this.currentState);
  92. })
  93. .onSeeked((playbackInfo: PlaybackInfo) => {
  94. console.info('onSeeked');
  95. this.elapsedTime = playbackInfo.time * 1000;
  96. this.updateTime = new Date().getTime();
  97. this.currentState.position = { elapsedTime: this.elapsedTime, updateTime: this.updateTime };
  98. this.setAVPlaybackState(this.currentState);
  99. })
  100. .onUpdate((playbackInfo: PlaybackInfo) => {
  101. console.info('onUpdate');
  102. this.elapsedTime = playbackInfo.time * 1000;
  103. this.currentState.position = { elapsedTime: this.elapsedTime, updateTime: this.updateTime };
  104. })
  105. }
  106. .width('100%')
  107. .height('100%')
  108. }
  109. }

应用如需长时间后台播放,则还需按需创建后台任务

在 FAQ 中进行搜索
请输入您想要搜索的关键词