# 媒体会话控制方

从API版本23开始，支持应用通过AVSession Kit获取媒体会话的播放信息和控制媒体会话的播放暂停等，实现对系统中的音视频应用进行统一的播放控制。该文档介绍媒体会话控制方接口能力及开发基本流程，包括获取媒体会话提供方的元数据，播放状态信息等，也可向媒体会话提供方发送命令及事件，控制媒体会话提供方的播放、暂停等。

#### 基本概念

*   媒体会话提供方：音视频应用在实现音视频功能的同时，需要作为媒体会话提供方接入媒体会话，并响应媒体会话控制方下发的播控命令。
    
*   媒体会话描述符（AVSessionDescriptor）：描述媒体会话的相关信息，包含标识媒体会话的ID（sessionId），媒体会话的类型type（音频Audio/视频Video），媒体会话自定义名称（sessionTag），媒体会话所属应用的信息（elementName）、是否为置顶会话（isTopSession）等。
    
*   媒体会话控制器（AVSessionController）：会话控制器，可用于查看会话ID，向会话发送命令及事件，获取会话元数据、播放状态信息等操作。
    
*   置顶会话（TopSession）：系统中优先级最高的媒体会话，例如当前处于正在播放状态的会话。
    

#### 接口说明

媒体会话控制方使用的关键接口包括两类：

1.  获取媒体会话描述符及媒体会话控制器，监听媒体会话的创建及销毁：通过AVSessionManager来调用，例如接口AVSessionManager.createController(sessionId)。
2.  获取媒体会话的元数据、播放状态信息等，监听媒体会话的元数据、播放状态变化等：通过AVSessionController对象来调用，例如接口controller.getAVPlaybackState()。

异步的JavaScript接口返回值有callback和promise两种返回形式。promise和callback只是返回值方式不一样，功能相同。

更多API说明请参见@ohos.multimedia.avsession (媒体会话管理)的模块描述。

#### 开发步骤

应用作为媒体会话控制方接入的基本步骤如下所示：

1.  应用申请受限开放权限ohos.permission.MANAGE\_MEDIA\_RESOURCES\_FOR\_PUBLIC。当前仅少量符合特殊场景的应用可在通过审批后，使用受限权限，其申请方式请参考：申请使用受限权限。
    
2.  监听媒体会话的创建、销毁以及当前最新播放的媒体会话变更，并创建媒体会话对应的AVSessionController，从而对系统中的音视频应用进行统一的播放控制。
    
    ```
    import { avSession } from '@kit.AVSessionKit';
    // ...
    
      // 获取媒体会话描述符及媒体会话控制器。
      async getAVSessionDescriptorsInfo(): Promise<void> {
        try {
          // 获取所有设置过媒体信息且注册过控制回调的会话的描述符信息。
          avSession.getAllSessionDescriptors().then(async (descriptors: avSession.AVSessionDescriptor[]) => {
            Log.info(TAG, 'Succeeded in getting all session descriptors, length: ' + descriptors.length);
            if (descriptors.length > 0) {
              avSession.createController(descriptors[0].sessionId).then(
                async (controller: avSession.AVSessionController) => {
                this.currentController = controller;
                this.currentControllerLink?.set(controller);
                await this.getControllerInfo();
                await this.listenControllerInfo();
                await this.getAVSessionValidCommands();
              });
            }
          });
    
          // 监听最新播放会话变更的事件。
          avSession.onTopSessionChange(async (descriptor: avSession.AVSessionDescriptor) => {
            Log.info(TAG, 'on topSessionChange : sessionTag: ' + descriptor.sessionTag);
            avSession.createController(descriptor.sessionId).then(async (controller: avSession.AVSessionController) => {
              this.currentController = controller;
              this.currentControllerLink?.set(controller);
              await this.getControllerInfo();
              await this.listenControllerInfo();
              await this.getAVSessionValidCommands();
            });
          });
    
          // 监听会话创建事件。
          avSession.onSessionCreate((descriptor: avSession.AVSessionDescriptor) => {
            Log.info(TAG, 'on sessionCreate: sessionTag: ' + descriptor.sessionTag);
          });
    
          // 监听会话的销毁事件。
          avSession.onSessionDestroy((descriptor: avSession.AVSessionDescriptor) => {
            Log.info(TAG, 'on sessionDestroy: ' + descriptor.sessionTag);
          });
        } catch (error) {
          if (error) {
            console.error(`avSession Error: Code: ${error.code}, message: ${error.message}`);
          }
        }
      }
    ```
    
3.  获取媒体会话提供方传递的当前播放曲目及播放状态等。
    
    ```
    async getControllerInfo(): Promise<void> {
      // 获取会话元数据。
      this.currentController?.getAVMetadata((error: BusinessError, metadata: avSession.AVMetadata) => {
        if (error) {
          Log.info(TAG, 'Failed to get AV metadata, code: ' + error.code + ', message: ' + error.message);
          return;
        }
        Log.info(TAG, 'Succeeded in getting AV metadata, assetId: ' + metadata.assetId);
        if (metadata?.mediaImage && metadata?.title && metadata?.artist) {
          this.metaDataLink?.set(metadata);
        }
      });
    
      // 获取播放状态。
      this.currentController?.getAVPlaybackState((error: BusinessError, state: avSession.AVPlaybackState) => {
        if (error) {
          Log.info(TAG, 'Failed to get AV playback state, code: ' + error.code + ', message: ' + error.message);
          return;
        }
        Log.info(TAG, 'Succeeded in getting AV playback state.');
        this.playbackStateLink?.set(state?.state);
      });
    
      // ...
    }
    ```
    
4.  监听媒体会话提供方的媒体信息变化及会话其他事件，从而应用可以根据回调及时刷新播放的曲目及播放状态。
    
    ```
    async listenControllerInfo(): Promise<void> {
      try {
        // 设置元数据变化的监听事件。
        this.currentController?.on('metadataChange', 'all', (metadata: avSession.AVMetadata) => {
          Log.info(TAG, 'on metadataChange assetId: ' + metadata.assetId);
          if (metadata?.mediaImage && metadata?.title && metadata?.artist) {
            this.metaDataLink?.set(metadata);
          }
        });
    
        // 设置播放状态变化的监听事件。
        this.currentController?.on('playbackStateChange', ['state'], (playbackState: avSession.AVPlaybackState) => {
          Log.info(TAG, 'on playbackStateChange state: ' + playbackState.state);
          this.playbackStateLink?.set(playbackState.state);
        });
    
        // ...
      } catch (error) {
        if (error) {
          console.error(`listenControllerInfo Error: Code: ${error.code}, message: ${error.message}`);
        }
      }
    }
    ```
    
5.  获取会话支持的有效命令，从而应用可以感知媒体会话提供方支持的命令。
    
    ```
    async getAVSessionValidCommands(): Promise<void> {
      try {
        // 获取会话支持的有效命令。
        this.currentController?.getValidCommands((error: BusinessError,
          validCommands: avSession.AVControlCommandType[]) => {
          if (error) {
            Log.info(TAG, 'Failed to get valid commands, code: ' + error.code + ', message: ' + error.message);
            return;
          }
          Log.info(TAG, 'Succeeded in getting valid commands, size: ' + validCommands.length);
          this.validCommandsLink?.set(validCommands);
        });
    
        // 会话支持的有效命令变化监听事件。
        this.currentController?.on('validCommandChange', (validCommands: avSession.AVControlCommandType[]) => {
          Log.info(TAG, 'Succeeded in valid command change, size: ' + validCommands.length);
          this.validCommandsLink?.set(validCommands);
        });
      } catch (error) {
        if (error) {
          console.error(`validCommand Error: Code: ${error.code}, message: ${error.message}`);
        }
      }
    }
    ```
    
6.  控制媒体会话行为，例如发送用户对当前曲目的操作（播放/暂停/上一首/下一首等）命令。
    
    ```
    changeCallback() {
      this.canPlay = !!this.validCommands?.includes('play');
      this.canPause = !!this.validCommands?.includes('pause');
      this.canNext = !!this.validCommands?.includes('playNext');
      this.canPrevious = !!this.validCommands?.includes('playPrevious');
    }
    
    @Builder
    commandBuild() {
      Image($r('app.media.previous'))
        .width('24vp').height('24vp')
        .id('Previous')
        .onClick(async () => {
          if (this.canPrevious) {
            let command: avSession.AVControlCommand = {
              command: 'playPrevious', // 上一首指令。
            }
            await this.currentController?.sendControlCommand(command);
            Log.info(TAG, 'on previous click');
          }
        })
      Image(this.playbackState == avSession.PlaybackState.PLAYBACK_STATE_PLAY ? $r('app.media.stopmusic') : $r('app.media.playmusic'))
        .width('24vp')
        .height('24vp')
        .id('PlayOrPause:')
        .onClick(async () => {
          if (this.playbackState == avSession.PlaybackState.PLAYBACK_STATE_PLAY) {
            if (this.canPause) {
              let command: avSession.AVControlCommand = {
                command: 'pause', // 暂停指令。
              }
              await this.currentController?.sendControlCommand(command);
            }
          } else {
            if (this.canPlay) {
              let command: avSession.AVControlCommand = {
                command: 'play', // 播放指令。
              }
              await this.currentController?.sendControlCommand(command);
            }
          }
          Log.info(TAG, 'on play/pause click');
        })
      Image($r('app.media.next'))
        .width('24vp').height('24vp')
        .id('Next')
        .onClick(async () => {
          if (this.canNext) {
            let command: avSession.AVControlCommand = {
              command: 'playNext', // 下一首指令。
            }
            await this.currentController?.sendControlCommand(command);
            Log.info(TAG, 'on next click ');
          }
        })
    }
    ```
    
7.  媒体会话退出时，媒体会话控制方应及时取消监听，并释放资源。
    
    ```
    async listenAVSessionDestroy(): Promise<void> {
      try {
        // 注册会话销毁监听。
        this.currentController?.on('sessionDestroy', () => {
          Log.info(TAG, 'on sessionDestroy: SUCCESS');
          this.currentController?.destroy(() => { // 销毁当前控制器，销毁后当前控制器不可再用。
            Log.info(TAG, 'Succeeded in destroying.');
          });
        });
      } catch (error) {
        if (error) {
          console.error(`destroy Error: Code: ${error.code}, message: ${error.message}`);
        }
      }
    }
    
    stopControl() {
      Log.info(TAG, 'stopControl');
      avSession.offSessionCreate();
      avSession.offTopSessionChange();
      Log.info(TAG, 'stopControl done');
    }
    ```