目录

AVSession Controller

Starting from API version 23, apps can use AVSession Kit to obtain playback information of AVSessions and control playback actions such as play and pause, enabling unified playback control over audio and video apps in the system. This document describes the API capabilities and basic development process of the AVSession controller, including obtaining metadata and playback state information from AVSession providers, as well as sending commands and events to AVSession providers to control their playback, pause, and other actions.

Fundamental Concepts

Available APIs

The key APIs used by the controller are classified into the following types:

  1. Obtain AVSession descriptors and AVSession controllers, and listen for AVSession creation and destruction: called through AVSessionManager, for example, the API AVSessionManager.createController(sessionId).
  2. Obtain AVSession metadata and playback state information, and listen for changes to AVSession metadata and playback state: called through the AVSessionController object, for example, the API controller.getAVPlaybackState().

Asynchronous JavaScript APIs use either a callback or promise to return the result. The two forms differ only in the return method and provide the same functionality.

For more API details, see the module description of @ohos.multimedia.avsession.

How to Develop

The basic steps for an app to connect as an AVSession controller are as follows:

  1. The app applies for the restricted permission ohos.permission.MANAGE_MEDIA_RESOURCES_FOR_PUBLIC. Currently, only a small number of apps that meet specific scenarios can use restricted permissions after approval. For details about the application method, see Requesting Restricted Permissions.

  2. Listen for AVSession creation, destruction, and changes to the currently active AVSession, and create the AVSessionController corresponding to the AVSession, so as to perform unified playback control over audio and video apps in the system.

    import { avSession } from '@kit.AVSessionKit';
    // ...
    
      // Obtain the AVSession descriptor and AVSession controller.
      async getAVSessionDescriptorsInfo(): Promise<void> {
        try {
          // Obtain descriptor information for all sessions that have set media information and registered control callbacks.
          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();
              });
            }
          });
    
          // Listen for the event of the latest playback session change.
          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();
            });
          });
    
          // Listen for session creation events.
          avSession.onSessionCreate((descriptor: avSession.AVSessionDescriptor) => {
            Log.info(TAG, 'on sessionCreate: sessionTag: ' + descriptor.sessionTag);
          });
    
          // Listen for session destruction events.
          avSession.onSessionDestroy((descriptor: avSession.AVSessionDescriptor) => {
            Log.info(TAG, 'on sessionDestroy: ' + descriptor.sessionTag);
          });
        } catch (error) {
          if (error) {
            Log.error(TAG, `avSession Error: Code: ${error.code}, message: ${error.message}`);
          }
        }
      }
  3. Obtain the current track and playback state delivered by the AVSession provider.

    async getControllerInfo(): Promise<void> {
      // Obtain the session metadata.
      this.currentController?.getAVMetadata((error: BusinessError, metadata: avSession.AVMetadata) => {
        if (error) {
          Log.error(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);
        }
      });
    
      // Obtain the playback state.
      this.currentController?.getAVPlaybackState((error: BusinessError, state: avSession.AVPlaybackState) => {
        if (error) {
          Log.error(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. Listen for media information changes and other session events from the AVSession provider, so that the app can refresh the current track and playback state in a timely manner based on the callbacks.

    async listenControllerInfo(): Promise<void> {
      try {
        // Set the listener for metadata changes.
        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);
          }
        });
    
        // Set the listener for playback state changes.
        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) {
          Log.error(TAG, `listenControllerInfo Error: Code: ${error.code}, message: ${error.message}`);
        }
      }
    }
  5. Obtain the valid commands supported by the session, so that the app can perceive the commands supported by the AVSession provider.

    async getAVSessionValidCommands(): Promise<void> {
      try {
        // Obtain the valid commands supported by the session.
        this.currentController?.getValidCommands((error: BusinessError,
          validCommands: avSession.AVControlCommandType[]) => {
          if (error) {
            Log.error(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);
        });
    
        // Listener event for changes to valid commands supported by the session.
        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) {
          Log.error(TAG, `validCommand Error: Code: ${error.code}, message: ${error.message}`);
        }
      }
    }
  6. Control the playback behavior, for example, sending a command to operate (play/pause/previous/next) the item being played.

    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', // Switch to the previous track.
            }
            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', // Pause playback.
              }
              await this.currentController?.sendControlCommand(command);
            }
          } else {
            if (this.canPlay) {
              let command: avSession.AVControlCommand = {
                command: 'play', // Start playback.
              }
              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', // Switch to the next track.
            }
            await this.currentController?.sendControlCommand(command);
            Log.info(TAG, 'on next click ');
          }
        })
    }
  7. When the AVSession exits, the AVSession controller must promptly cancel listening and release resources.

    async listenAVSessionDestroy(): Promise<void> {
      try {
        // Register a listener for session destruction.
        this.currentController?.on('sessionDestroy', () => {
          Log.info(TAG, 'on sessionDestroy: SUCCESS');
          this.currentController?.destroy(() => { // Destroy the current controller. After destruction, the controller can no longer be used.
            Log.info(TAG, 'Succeeded in destroying.');
          });
        });
      } catch (error) {
        if (error) {
          Log.error(TAG, `destroy Error: Code: ${error.code}, message: ${error.message}`);
        }
      }
    }
    
    stopControl() {
      Log.info(TAG, 'stopControl');
      avSession.offSessionCreate();
      avSession.offTopSessionChange();
      Log.info(TAG, 'stopControl done');
    }