鸿蒙媒体开发【媒体会话-提供方】音频和视频

媒体会话-提供方

介绍

本示例主要展示了媒体会话(媒体提供方)的相关功能,使用@ohos.multimedia.avsession等接口实现媒体提供方与媒体播控中心自定义信息的交互功能。

注意: 此示例仅展示媒体提供方的相关功能,如果需要媒体会话提供的完整的自定义信息交互功能,请将本示例与系统播控中心共同使用。

效果预览

使用说明
基础操作
  1. 打开媒体提供方示例应用。
  2. 点击播放按钮,应用的播放状态发生变化,进度开始刷新。
  3. 点击暂停按钮,应用的播放状态开始变化,进度停止刷新。
  4. 点击上一首按钮,应用界面展示播放列表中的上一首歌曲的信息。
  5. 点击下一首按钮,应用界面展示播放列表中的下一首歌曲的信息。
  6. 点击播放,拖动进度条,播放进度改变。
  7. 点击收藏,收藏按钮点亮。
  8. 点击播放模式,可以切换不同的播放模式(注:播放模式未实现功能,仅实现与播控中心状态同步)。
进阶操作(与媒体播控中心一起使用)
  1. 点击本应用播放、暂停、上一首、下一首按钮,可以发现媒体播控中心中,该会话的状态同步改变。
  2. 点击媒体播控中心的对应按钮,可以发现本应用中按钮状态同步改变。
  3. 媒体播控中心可以获取到本应用的正在播放内容、播放模式、歌词、进度、收藏状态、媒体资源金标、历史歌单等信息。

具体实现

  • 界面相关的实现都封装在pages/Index.ets下,源码参考:[pages/Index.ets]

    /*

    • Copyright (C) 2024 Huawei Device Co., Ltd.
    • Licensed under the Apache License, Version 2.0 (the "License");
    • you may not use this file except in compliance with the License.
    • You may obtain a copy of the License at
    • http://www.apache.org/licenses/LICENSE-2.0
    • Unless required by applicable law or agreed to in writing, software
    • distributed under the License is distributed on an "AS IS" BASIS,
    • WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    • See the License for the specific language governing permissions and
    • limitations under the License.
      */

    import { avSession } from '@kit.AVSessionKit';
    import MediaData from '../model/MediaData';
    import Logger from '../common/utils/Logger';
    import { ProviderFeature } from '../viewmodel/ProviderManager';
    import Constants from '../common/constants/Constants';

    const TAG = 'Index';

    @Entry
    @Component
    struct Index {
    @StorageLink('SeekPosition') seekPosition: number = 0;
    @StorageLink('CurrentLoopMode') currentLoopMode: avSession.LoopMode.LOOP_MODE_SEQUENCE = 0;
    @StorageLink('IsPlaying') isPlaying: boolean = false;
    @StorageLink('isFavorMap') isFavorMap: Map<String, boolean> | null = null;
    @StorageLink('CurrentPlayItem') currentPlayItem: avSession.AVQueueItem | null = null;
    @StorageLink('CurrentAVMetadata') currentAVMetadata: avSession.AVMetadata | null = null;
    @StorageLink('CurrentImage') currentImage: PixelMap | null = null;
    @State isProgressSliding: Boolean = false;
    private providerFeature: ProviderFeature | undefined = undefined;
    private sliderTimer?: number;

    async aboutToAppear() {
      this.providerFeature = await ProviderFeature.getInstance();
    }
    
    aboutToDisappear() {
      this.providerFeature!.unInit();
    }
    
    onPageHide() {
      // The application page is returned to the background, and a long-time task keep-alive playback is applied for.
      this.providerFeature!.startContinuousTask();
    }
    
    build() {
      Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
        Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
          Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
            Row() {
              Image($r('app.media.ic_down'))
                .width($r('app.float.icon_width'))
                .height($r('app.float.icon_height'))
            }
            .width(Constants.PERCENT_FULL)
            .height($r('app.float.icon_area_height'))
            .margin({ top: $r('app.float.icon_area_marin_top') })
            .justifyContent(FlexAlign.Start)
            .alignItems(VerticalAlign.Bottom)
    
            Image(this.currentImage ? this.currentImage : '')
              .width(Constants.PERCENT_FULL)
              .margin({ top: $r('app.float.image_margin_top') })
              .id('CurrentImage')
    
            Row() {
              Text(this.currentAVMetadata!.title ? this.currentAVMetadata!.title : 'No title')
                .width(Constants.PERCENT_FULL)
                .fontSize($r('app.float.music_title_font_size'))
                .fontColor($r('app.color.music_title_font_color'))
                .fontWeight(FontWeight.Bold)
                .id('Title')
            }
            .height($r('app.float.title_area_height'))
            .alignItems(VerticalAlign.Bottom)
    
            Text(this.currentAVMetadata!.artist ? this.currentAVMetadata!.artist : 'No artist')
              .width(Constants.PERCENT_FULL)
              .height($r('app.float.artist_height'))
              .margin({ top: $r('app.float.artist_margin_top') })
              .fontSize($r('app.float.artist_margin_font_size'))
              .fontColor($r('app.color.music_title_font_color'))
              .id('Artist')
    
            Blank()
    
            Flex({
              direction: FlexDirection.Row,
              alignItems: ItemAlign.Start,
              alignContent: FlexAlign.Center,
              justifyContent: FlexAlign.Center
            }) {
              Slider({
                value: this.seekPosition,
                min: 0,
                max: Constants.ONE_HUNDRED,
                style: SliderStyle.OutSet
              })
                .trackThickness($r('app.float.slider_track_thickness_normal'))
                .blockColor(Color.White)
                .trackColor($r('app.color.slider_track_color'))
                .selectedColor($r('app.color.slider_selected_color'))
                .showSteps(false)
                .showTips(false)
                .onChange((value: number, mode: SliderChangeMode) => {
                  Logger.info(TAG, 'value:' + value + 'mode:' + mode.toString())
                  if (mode === SliderChangeMode.End) {
                    this.providerFeature!.seek(value);
                  }
                })
                .onTouch((event: TouchEvent) => {
                  Logger.info(TAG, 'progress touch: ' + event.type)
                  if (event.type === TouchType.Up) {
                    this.sliderTimer = setTimeout(() => {
                      this.isProgressSliding = false;
                    }, Constants.SLIDER_CHANGE_DURATION);
                  } else {
                    clearTimeout(this.sliderTimer);
                    this.isProgressSliding = true;
                  }
                })
            }
            .height($r('app.float.slider_area_height'))
    
            Flex({
              direction: FlexDirection.Row,
              alignItems: ItemAlign.Center,
              alignContent: FlexAlign.Center,
              justifyContent: FlexAlign.SpaceBetween
            }) {
              Button({ stateEffect: true }) {
                Image(this.isFavorMap!.get(this.currentAVMetadata!.assetId) ? $r('app.media.ic_public_favor_filled') :
                  $r('app.media.ic_public_favor'))
              }
              .width($r('app.float.normal_button_width'))
              .aspectRatio(1)
              .backgroundColor(Color.Transparent)
              .id('Favorite')
              .onClick(async () => {
                await this.providerFeature!.toggleFavorite();
              })
    
              Button({ stateEffect: true }) {
                Image($r('app.media.ic_previous'))
              }
              .width($r('app.float.normal_button_width'))
              .aspectRatio(1)
              .backgroundColor(Color.Transparent)
              .id('Previous')
              .onClick(async () => {
                this.providerFeature!.previous();
              })
    
              Button({ stateEffect: true }) {
                Image(this.isPlaying ? $r('app.media.ic_pause') : $r('app.media.ic_play'))
              }
              .height($r('app.float.play_button_height'))
              .aspectRatio(1)
              .backgroundColor(Color.Transparent)
              .id('PlayOrPause')
              .onClick(async () => {
                if (!this.isPlaying) {
                  await this.providerFeature!.play();
                } else {
                  await this.providerFeature!.pause();
                }
              })
    
              Button({ stateEffect: true }) {
                Image($r('app.media.ic_next'))
              }
              .width($r('app.float.normal_button_width'))
              .aspectRatio(1)
              .backgroundColor(Color.Transparent)
              .id('Next')
              .onClick(async () => {
                this.providerFeature!.next();
              })
    
              Button({ stateEffect: true }) {
                Image(MediaData.loopModeList[this.currentLoopMode!])
              }
              .width($r('app.float.normal_button_width'))
              .aspectRatio(1)
              .backgroundColor(Color.Transparent)
              .id('LoopMode')
              .onClick(async () => {
                this.providerFeature!.loopMode();
              })
            }
            .width(Constants.PERCENT_FULL)
            .height($r('app.float.play_button_height'))
            .margin({
              left: $r('app.float.button_row_margin_left'),
              right: $r('app.float.button_row_margin_left'),
              top: $r('app.float.button_row_margin_top'),
              bottom: $r('app.float.button_row_margin_bottom')
            })
          }
          .height(Constants.PERCENT_FULL)
          .width(Constants.PERCENT_FULL)
        }
        .margin({
          left: $r('app.float.page_margin_left'),
          right: $r('app.float.page_margin_left')
        })
      }
      .width(Constants.PERCENT_FULL)
      .height(Constants.PERCENT_FULL)
      .backgroundImage($r('app.media.ic_background'))
      .backgroundImageSize(ImageSize.Cover)
    }
    

    }

  • 使用@StorageLink来设置与逻辑代码同步更新的变量,当逻辑代码中对应的变量更新时,界面会同步的刷新。

    • 通过引入逻辑代码对应的类,创建出对象,实现对onClick事件的响应,关键代码段:

      import { ProviderFeature } from '../feature/ProviderFeature';
      this.providerFeature = await ProviderFeature.getInstance(); // 创建对象单例
      
      Button() {
        // 按钮的内容
      }
      .onClick(async () => {
        this.providerFeature.play(); // 通过类的对象来调用逻辑代码
      })
      
  • 逻辑相关的实现都封装在viewmodel/ProviderManger.ets下,源码参考:[viewmodel/ProviderManager.ets]

    /*

    • Copyright (C) 2024 Huawei Device Co., Ltd.
    • Licensed under the Apache License, Version 2.0 (the "License");
    • you may not use this file except in compliance with the License.
    • You may obtain a copy of the License at
    • http://www.apache.org/licenses/LICENSE-2.0
    • Unless required by applicable law or agreed to in writing, software
    • distributed under the License is distributed on an "AS IS" BASIS,
    • WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    • See the License for the specific language governing permissions and
    • limitations under the License.
      */

    import { wantAgent, common } from '@kit.AbilityKit';
    import { util } from '@kit.ArkTS';
    import { audio } from '@kit.AudioKit';
    import { avSession } from '@kit.AVSessionKit';
    import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
    import { BusinessError } from '@kit.BasicServicesKit';
    import { image } from '@kit.ImageKit';
    import { resourceManager } from '@kit.LocalizationKit';
    import { media } from '@kit.MediaKit';
    import Constants, { AVPlayerState } from '../common/constants/Constants';
    import GlobalContext from '../common/utils/GlobalContextUtils';
    import MediaPlayerUtils from '../common/utils/MediaPlayerUtils';
    import Logger from '../common/utils/Logger';
    import MediaData from '../model/MediaData';

    /**

    • Processes playback logic and interacts with the AVSession.
      */
      const TAG = 'ProviderFeature';

    export class ProviderFeature {
    private static providerSelf: ProviderFeature | undefined = undefined;
    private context: common.UIAbilityContext | undefined =
    GlobalContext.getContext().getObject(Constants.CONTEXT) as common.UIAbilityContext;
    private constants = new MediaData();
    private session: avSession.AVSession | null = null;
    private isPlayLink: SubscribedAbstractProperty<boolean> | null = null;
    private currentPlayItemLink: SubscribedAbstractProperty<avSession.AVQueueItem> | undefined = undefined;
    private currentLoopModeLink: SubscribedAbstractProperty<avSession.LoopMode> | undefined = undefined;
    private seekPositionLink: SubscribedAbstractProperty<number> | undefined = undefined;
    private currentAVMetadataLink: SubscribedAbstractProperty<avSession.AVMetadata> | undefined = undefined;
    private currentImageLink: SubscribedAbstractProperty<PixelMap> | undefined = undefined;
    private queueItems: Array<avSession.AVQueueItem> =
    [this.constants.queueItemFirst, this.constants.queueItemSecond, this.constants.queueItemThird];
    private queueItemPixelMapArray: Array<PixelMap> = [];
    private MetadataPixelMapArray: Array<PixelMap> = [];
    private resourceManager: resourceManager.ResourceManager = this.context!.resourceManager;
    private avMetadataList: Array<avSession.AVMetadata> =
    [this.constants.avMetadataFirst, this.constants.avMetadataSecond, this.constants.avMetadataThird];
    private currentState: avSession.AVPlaybackState = {
    state: avSession.PlaybackState.PLAYBACK_STATE_PAUSE
    };
    private constantsForControl: MediaData = new MediaData();
    private mediaPlayerUtil: MediaPlayerUtils = new MediaPlayerUtils();
    private avPlayer?: media.AVPlayer;
    private currentTime: number = 0;
    private isFavorMapLink: SubscribedAbstractProperty<Map<String, boolean>> | undefined = undefined;
    private playListAssetIdMap: Map<string, number> = new Map();
    private localLyric: string = '';

    static async getInstance(): Promise<ProviderFeature> {
      Logger.info(TAG, ' provider getInstance');
      if (!ProviderFeature.providerSelf) {
        Logger.info(TAG, ' new provider');
        ProviderFeature.providerSelf = new ProviderFeature();
        await ProviderFeature.providerSelf.init();
      }
      return ProviderFeature.providerSelf;
    }
    
    private constructor() {
      this.isPlayLink = AppStorage.setAndLink('IsPlaying', false);
      this.currentPlayItemLink =
        AppStorage.setAndLink<avSession.AVQueueItem>('CurrentPlayItem', {} as avSession.AVQueueItem);
      this.currentAVMetadataLink =
        AppStorage.setAndLink<avSession.AVMetadata>('CurrentAVMetadata', {} as avSession.AVMetadata);
      this.isFavorMapLink = AppStorage.setAndLink<Map<String, boolean>>('isFavorMap', new Map());
      this.currentImageLink = AppStorage.setAndLink<PixelMap>('CurrentImage', {} as PixelMap);
      this.currentLoopModeLink =
        AppStorage.setAndLink<avSession.LoopMode>('CurrentLoopMode', avSession.LoopMode.LOOP_MODE_SEQUENCE);
      this.seekPositionLink = AppStorage.setAndLink<number>('SeekPosition', 0);
      this.currentAVMetadataLink!.set(this.avMetadataList[0]);
      this.currentLoopModeLink!.set(avSession.LoopMode.LOOP_MODE_SEQUENCE);
    
      let map: Map<String, boolean> = new Map();
      map.set(this.avMetadataList[0].assetId, false);
      map.set(this.avMetadataList[1].assetId, false);
      map.set(this.avMetadataList[2].assetId, false);
      this.isFavorMapLink!.set(map);
    
      this.playListAssetIdMap.set(Constants.ENTITY_ID_FIRST_PLAY_LIST, 0);
      this.playListAssetIdMap.set(Constants.ENTITY_ID_SECOND_PLAY_LIST, 1);
      this.playListAssetIdMap.set(Constants.ENTITY_ID_THIRD_PLAY_LIST, 2);
    }
    
    /**
     * Initialize resources.
     */
    async init(): Promise<void> {
      this.avPlayer = await this.mediaPlayerUtil.init();
      this.localLyric = await this.getLocalLrc(Constants.LRC_NAME);
      await this.prepareImageResources();
      await this.prepareResourcesForController();
      await this.CreateAVSession();
      await this.InitFirstMusicState();
      await this.RegisterAVPlayerListener();
    }
    
    async unInit(): Promise<void> {
      this.UnRegisterListener();
      if (this.avPlayer) {
        this.unRegisterAVPlayerListener();
        this.avPlayer.release();
      }
    }
    
    /**
     * Reads local lyrics.
     */
    async getLocalLrc(fileName: string): Promise<string> {
      const value: Uint8Array = await this.context!.resourceManager.getRawFileContent(fileName);
      Logger.info(TAG, 'local lrc:' + fileName + ':' + value);
    
      if (!value) {
        Logger.error(TAG, 'get lyc fail:' + fileName);
        return Promise.reject('get lyc fail');
      }
      let textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true });
      let retStr: string = textDecoder.decodeWithStream(value, { stream: false });
      Logger.info(TAG, 'retStr: ' + retStr);
      return retStr;
    }
    
    /**
     * Start a long-time task.
     */
    async startContinuousTask(): Promise<void> {
      let wantAgentInfo: wantAgent.WantAgentInfo = {
        wants: [
          {
            bundleName: Constants.BUNDLE_NAME,
            abilityName: Constants.ABILITY_NAME
          }
        ],
        operationType: wantAgent.OperationType.START_ABILITY,
        requestCode: 0,
        wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
      };
      let tmpWantAgent = await wantAgent.getWantAgent(wantAgentInfo);
      // Sets the application page that can be invoked when a playback control card is clicked.
      this.session?.setLaunchAbility(tmpWantAgent);
      await backgroundTaskManager.startBackgroundRunning(this.context,
        backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK, tmpWantAgent);
    }
    
    /**
     * Close a long-time task.
     */
    async stopContinuousTask(): Promise<boolean> {
      Logger.info(TAG, 'stop background ');
      return new Promise((resolve) => {
        backgroundTaskManager.stopBackgroundRunning(this.context, (err, _) => {
          if (err) {
            Logger.error(TAG, `stop background startBackgroundRunning err ${err.stack}`);
            resolve(false);
          } else {
            Logger.info(TAG, `stop background startBackgroundRunning success`);
            resolve(true);
          }
        });
      });
    }
    
    /**
     * Create AVSession.
     */
    async CreateAVSession(): Promise<boolean> {
      Logger.info(TAG, `Start create AVSession`);
      let ret: boolean = true;
      if (this.session) {
        Logger.info(TAG, `has a session: ` + this.session.sessionId);
        return ret;
      }
      // Create an audio session. The Favorites, Previous, Pause, and Repeat buttons are displayed on the playback control page.
      // A developer can also create a session of the video type. The playback control page displays the fast-forward, rewind, and pause buttons.
      this.session = await avSession.createAVSession(this.context!, 'AVSessionDemo', 'audio');
      // Activate the session.
      this.RegisterSessionListener();
      await this.session?.activate().catch((err: BusinessError) => {
        if (err) {
          Logger.error(TAG, `Failed to activate AVSession, error info: ${JSON.stringify(err)}`);
          ret = false;
        }
      });
      return ret;
    }
    
    async InitFirstMusicState(): Promise<void> {
      Logger.info(TAG, ` InitFirstMusicState`);
      await this.mediaPlayerUtil.loadFromRawFile(Constants.MUSIC_FILE_NAME);
      this.isPlayLink!.set(false);
      this.currentImageLink!.set(this.MetadataPixelMapArray[0]);
      this.currentState.state = avSession.PlaybackState.PLAYBACK_STATE_PAUSE;
      await this.session?.setAVPlaybackState(this.currentState);
      await this.setAVMetadataToController(0);
      this.currentPlayItemLink!.set(this.queueItems![0]);
      this.currentAVMetadataLink!.set(this.avMetadataList[0]);
    }
    
    /**
     * Registers the session callback function.
     * Register the command as required. If the command is not supported, do not register it.
     * For the session of the audio type, implement the progress bar, favorites, next song, play/pause, and loop mode callback functions. If the functions are not supported, disable the interface or do not register the interface.
     * For a session of the video type, implement the fast-forward, rewind, next episode, and playback pause callback functions. If the functions are not supported, disable the interface or do not register the interface.
     */
    async RegisterSessionListener(): Promise<void> {
      // Processes playback commands.
      this.session?.on('play', async () => {
        Logger.info(TAG, `on play, do play task`);
        this.avPlayer?.play();
        this.isPlayLink!.set(true);
        this.currentState = {
          state: avSession.PlaybackState.PLAYBACK_STATE_PLAY,
          position: {
            elapsedTime: this.currentTime,
            updateTime: new Date().getTime(),
          }
        };
        await this.session?.setAVPlaybackState(this.currentState);
      });
    
      // Suspend instruction processing.
      this.session?.on('pause', async () => {
        Logger.info(TAG, `on pause, do pause task`);
        this.avPlayer?.pause();
        this.isPlayLink!.set(false);
        this.currentState = {
          state: avSession.PlaybackState.PLAYBACK_STATE_PAUSE,
          position: {
            elapsedTime: this.currentTime,
            updateTime: new Date().getTime(),
          }
        };
        await this.session?.setAVPlaybackState(this.currentState);
      });
    
      // Stop instruction processing.
      this.session?.on('stop', async () => {
        Logger.info(TAG, `on stop , do stop task`);
        this.avPlayer?.stop();
        this.isPlayLink!.set(false);
        this.currentState.state = avSession.PlaybackState.PLAYBACK_STATE_PAUSE;
        await this.session?.setAVPlaybackState(this.currentState);
      });
    
      // Next song/set instruction processing.
      this.session?.on('playNext', async () => {
        Logger.info(TAG, `on playNext , do playNext task`);
        let nextId: number = this.currentPlayItemLink!.get().itemId + 1;
        nextId = this.queueItems!.length > nextId ? nextId : nextId - this.queueItems!.length;
        await this.handleNewItem(nextId);
      });
    
      // Previous song/set instruction processing.
      this.session?.on('playPrevious', async () => {
        Logger.info(TAG, `on playPrevious , do playPrevious task`);
        let previousId: number = this.currentPlayItemLink!.get().itemId - 1;
        previousId = previousId < 0 ? previousId + this.queueItems!.length : previousId;
        await this.handleNewItem(previousId);
      });
    
      // Processes the progress bar dragging command.
      this.session?.on('seek', (position) => {
        Logger.info(TAG, 'on seek: seek to' + position);
        // Modify the playback progress based on the instruction.
        if (position >= this.currentAVMetadataLink!.get().duration!) {
          this.next();
        } else {
          this.avPlayer?.seek(position);
          this.currentState.position = {
            elapsedTime: position,
            updateTime: new Date().getTime()
          };
          this.session?.setAVPlaybackState(this.currentState);
        }
      });
    
      // Processes the favorite/like command for the audio session.
      this.session?.on('toggleFavorite', (assetId) => {
        // If a system callback message is received, the user clicks the favorites button when playing the song.
        // The app stores the favorites status based on the song ID and reports the current favorites status.
        Logger.info(TAG, 'on toggleFavorite session, do toggleFavorite task: ' + assetId);
        this.isFavorMapLink!.get().set(assetId, !this.isFavorMapLink!.get().get(assetId));
        this.currentState.isFavorite = this.isFavorMapLink!.get().get(assetId);
        this.session?.setAVPlaybackState(this.currentState);
      });
    
      // Cyclic mode instruction processing for audio session.
      this.session?.on('setLoopMode', (mode) => {
        Logger.info(TAG, 'on setLoopMode: ' + mode);
        // The value transferred by the playback control is not processed.
        // The value is switched based on the application sequence.
        let currentMode = this.currentLoopModeLink!.get();
        this.currentLoopModeLink!.set(currentMode === 3 ? 0 : currentMode + 1);
        // The playback status is updated. The cyclic mode after application processing is reported in the playback status.
        this.currentState.loopMode = this.currentLoopModeLink!.get();
        Logger.info(TAG, 'self setLoopMode: ' + this.currentState.loopMode);
        this.session?.setAVPlaybackState(this.currentState);
      });
    
      // Fast-forward command processing for video sessions.
      this.session?.on('fastForward', (skipInterval?: number) => {
        let currentTime: number =
          (skipInterval! * 1000 + this.avPlayer!.currentTime) > this.currentAVMetadataLink!.get().duration! ?
            this.currentAVMetadataLink!.get().duration! : (skipInterval! * 1000 + this.avPlayer!.currentTime);
        if (currentTime >= this.currentAVMetadataLink!.get().duration!) {
          this.next();
        } else {
          this.avPlayer?.seek(currentTime);
          this.currentState.position = {
            elapsedTime: currentTime,
            updateTime: new Date().getTime()
          };
          this.currentState.state = avSession.PlaybackState.PLAYBACK_STATE_PLAY;
          this.session?.setAVPlaybackState(this.currentState);
        }
      });
    
      // Rewind command processing, for video session.
      this.session?.on('rewind', (skipInterval?: number) => {
        let currentTime: number = (this.avPlayer!.currentTime - skipInterval! * 1000) <= 0 ?
          0 : (this.avPlayer!.currentTime - skipInterval! * 1000);
        this.avPlayer?.seek(skipInterval);
        Logger.info(TAG, ' currentTime' + JSON.stringify(currentTime));
        if (currentTime <= 0) {
          this.previous();
        } else {
          this.avPlayer?.seek(currentTime);
          this.currentState.position = {
            elapsedTime: currentTime,
            updateTime: new Date().getTime()
          };
          this.currentState.state = avSession.PlaybackState.PLAYBACK_STATE_PLAY;
          this.session?.setAVPlaybackState(this.currentState);
        }
      });
    }
    
    /**
     * Deregister a session callback.
     */
    async UnRegisterListener(): Promise<void> {
      if (this.session) {
        this.session.off('play');
        this.session.off('pause');
        this.session.off('stop');
        this.session.off('playNext');
        this.session.off('playPrevious');
        this.session.off('fastForward');
        this.session.off('rewind');
        this.session.off('seek');
        this.session.off('setLoopMode');
        this.session.off('toggleFavorite');
    
        // Destroys a created session.
        this.session.destroy((err) => {
          if (err) {
            Logger.info(TAG, `Destroy BusinessError: code: ${err.code}, message: ${err.message}`);
          } else {
            Logger.info(TAG, 'Destroy : SUCCESS');
          }
        });
      }
    }
    
    /**
     * Processing logic of the avplayer when the playback is paused.
     */
    async localPlayOrPause(): Promise<void> {
      Logger.info(TAG, 'localPlayOrPause start play' + this.avPlayer?.state);
      if (!this.avPlayer) {
        Logger.error(TAG, 'localPlayOrPause start play no avplayer');
        return;
      }
      if (this.avPlayer.state === AVPlayerState.PLAYING) {
        Logger.info(TAG, 'localPlayOrPause start play start pause');
        await this.avPlayer.pause();
        this.isPlayLink!.set(false);
      } else if (this.avPlayer.state === AVPlayerState.STOPPED) {
        Logger.info(TAG, 'localPlayOrPause start play from stopped');
        await this.avPlayer.prepare();
        await this.avPlayer.play();
        this.isPlayLink!.set(true);
      } else {
        Logger.info(TAG, 'localPlayOrPause start play');
        await this.avPlayer.play();
        this.isPlayLink!.set(true);
        Logger.info(TAG, 'localPlayOrPause start play done');
      }
      Logger.info(TAG, 'localPlayOrPause isPlay: ' + this.isPlayLink!.get());
    }
    
    /**
     * Set AVMetadata.
     */
    async setAVMetadataToController(itemId: number): Promise<void> {
      let avMetadata: avSession.AVMetadata = this.constantsForControl.avMetadataList[itemId];
      avMetadata.lyric = this.localLyric;
      Logger.info(TAG, `setAVMetadataToController avMetadata: ` + JSON.stringify(avMetadata));
      await this.session?.setAVMetadata(avMetadata);
    }
    
    /**
     * In-app favorites button.
     */
    async toggleFavorite(): Promise<void> {
      this.isFavorMapLink!.get()
        .set(this.currentAVMetadataLink!.get().assetId,
          !this.isFavorMapLink!.get().get(this.currentAVMetadataLink!.get().assetId));
      this.currentState.isFavorite = this.isFavorMapLink!.get().get(this.currentAVMetadataLink!.get().assetId);
      Logger.info(TAG, ` Start do toggleFavorite task state isFavorite: ` + this.currentState.isFavorite);
      this.session?.setAVPlaybackState(this.currentState);
    }
    
    /**
     * In-app circulation mode.
     */
    async loopMode(): Promise<void> {
      let currentMode = this.currentLoopModeLink!.get();
      Logger.info(TAG, 'do setLooMode old: ' + currentMode);
      this.currentLoopModeLink!.set(currentMode === Constants.MAX_LOOP_MODE_COUNT ? 0 : ++currentMode);
      this.currentState.loopMode = this.currentLoopModeLink!.get();
      Logger.info(TAG, 'do setLooMode new: ' + this.currentState.loopMode);
      this.session?.setAVPlaybackState(this.currentState);
    }
    
    /**
     * Updates the playback status.
     */
    async play(): Promise<void> {
      Logger.info(TAG, `Start do play task`);
      await this.localPlayOrPause();
      let isFavor: boolean | undefined = this.isFavorMapLink!.get().get(this.currentAVMetadataLink!.get().assetId);
      Logger.info(TAG, `currentState assetId ` + this.currentAVMetadataLink!.get().assetId);
      Logger.info(TAG, `currentState isPlay ` + this.isPlayLink!.get());
      this.currentState = {
        state: this.isPlayLink!.get() ? avSession.PlaybackState.PLAYBACK_STATE_PLAY :
        avSession.PlaybackState.PLAYBACK_STATE_PAUSE,
        position: {
          elapsedTime: this.currentTime,
          updateTime: new Date().getTime()
        },
        isFavorite: isFavor
      };
      Logger.info(TAG, `currentState` + JSON.stringify(this.currentState));
      await this.session?.setAVPlaybackState(this.currentState);
    }
    
    /**
     * In-app pause button.
     */
    async pause(): Promise<void> {
      Logger.info(TAG, `on pause , do pause task`);
      await this.localPlayOrPause();
      let isFavor: boolean | undefined = this.isFavorMapLink!.get().get(this.currentAVMetadataLink!.get().assetId);
      Logger.info(TAG, `currentState assetId ` + this.currentAVMetadataLink!.get().assetId);
      Logger.info(TAG, `currentState isPlay ` + this.isPlayLink!.get());
      this.currentState = {
        state: this.isPlayLink!.get() ? avSession.PlaybackState.PLAYBACK_STATE_PLAY :
        avSession.PlaybackState.PLAYBACK_STATE_PAUSE,
        position: {
          elapsedTime: this.currentTime,
          updateTime: new Date().getTime()
        },
        isFavorite: isFavor
      };
      Logger.info(TAG, `currentState` + JSON.stringify(this.currentState));
      await this.session?.setAVPlaybackState(this.currentState);
    }
    
    /**
     * In-app previous song button.
     */
    async previous(): Promise<void> {
      Logger.info(TAG, `on playPrevious , do playPrevious task`);
      let previousId: number = this.currentPlayItemLink!.get().itemId - 1;
      previousId = previousId < 0 ? previousId + this.queueItems!.length : previousId;
      await this.handleNewItem(previousId);
    }
    
    /**
     * In-app next song button.
     */
    async next(): Promise<void> {
      Logger.info(TAG, `on playNext , do playNext task`);
      let nextId: number = this.currentPlayItemLink!.get().itemId + 1;
      nextId = this.queueItems!.length > nextId ? nextId : nextId - this.queueItems!.length;
      await this.handleNewItem(nextId);
    }
    
    /**
     * In-app progress bar.
     */
    async seek(value: number): Promise<void> {
      Logger.info(TAG, `on seek , do seek task to: ` + value);
      let currentPosition = value / 100 * this.currentAVMetadataLink!.get().duration!;
      if (currentPosition >= this.currentAVMetadataLink!.get().duration!) {
        this.next();
      } else {
        this.avPlayer?.seek(currentPosition);
        this.currentState.position = {
          elapsedTime: Math.floor(currentPosition),
          updateTime: new Date().getTime()
        };
        this.session?.setAVPlaybackState(this.currentState);
      }
    }
    
    /**
     * Processes the previous/next command.
     */
    async handleNewItem(itemId: number): Promise<void> {
      Logger.info(TAG, ' handleNewItem itemId: ' + itemId);
      this.currentImageLink!.set(this.MetadataPixelMapArray[itemId]);
      await this.setAVMetadataToController(itemId);
      this.currentPlayItemLink!.set(this.queueItems![itemId]);
      this.currentAVMetadataLink!.set(this.avMetadataList[this.currentPlayItemLink!.get().itemId]);
      await this.mediaPlayerUtil.loadFromRawFile(Constants.MUSIC_FILE_NAME);
      // The avplayer is ready to play.
      this.mediaPlayerUtil.on('prepared', () => {
        Logger.info(TAG, 'AVPlayer state prepared, start play');
        this.handleNewItemAVPlayback();
      });
    }
    
    async handleNewItemAVPlayback(): Promise<void> {
      await this.localPlayOrPause();
      let isFavor: boolean | undefined = this.isFavorMapLink!.get().get(this.currentAVMetadataLink!.get().assetId);
      Logger.info(`currentState assetId ` + this.currentAVMetadataLink!.get().assetId);
      Logger.info(`currentState isFavor ` + isFavor);
      this.currentState = {
        state: this.isPlayLink!.get() ? avSession.PlaybackState.PLAYBACK_STATE_PLAY :
        avSession.PlaybackState.PLAYBACK_STATE_PAUSE,
        position: {
          elapsedTime: this.currentTime,
          updateTime: new Date().getTime()
        },
        isFavorite: isFavor
      };
      Logger.info(`currentState` + JSON.stringify(this.currentState));
      await this.session?.setAVPlaybackState(this.currentState);
    }
    
    /**
     * Processes the playback of historical playlists.
     */
    async handleNewPlayListItem(avQueueId: string): Promise<void> {
      let assetId: number = this.playListAssetIdMap.get(avQueueId)!;
      await this.handleNewItem(assetId);
    }
    
    /**
     * Prepare media resources.
     */
    async prepareImageResources(): Promise<void> {
      Logger.info(TAG, `prepareImageResources in`);
      this.queueItemPixelMapArray.push(await this.saveRawFileToPixelMap('first.png'));
      this.queueItemPixelMapArray.push(await this.saveRawFileToPixelMap('second.png'));
      this.queueItemPixelMapArray.push(await this.saveRawFileToPixelMap('third.png'));
      this.MetadataPixelMapArray.push(await this.saveRawFileToPixelMap('first_with_background.png'));
      this.MetadataPixelMapArray.push(await this.saveRawFileToPixelMap('second_with_background.png'));
      this.MetadataPixelMapArray.push(await this.saveRawFileToPixelMap('third_with_background.png'));
      for (let i = 0; i < this.queueItemPixelMapArray.length; i++) {
        this.queueItems[i].description!.mediaImage = this.queueItemPixelMapArray[i];
        this.avMetadataList[i].mediaImage = this.MetadataPixelMapArray[i];
        this.avMetadataList[i].avQueueImage = this.queueItemPixelMapArray[i];
      }
      this.currentPlayItemLink!.set(this.queueItems![0]);
      this.currentImageLink!.set(this.MetadataPixelMapArray[0]);
      this.currentAVMetadataLink!.set(this.avMetadataList[0]);
    }
    
    async saveRawFileToPixelMap(rawFilePath: string): Promise<image.PixelMap> {
      let value: Uint8Array = await this.resourceManager.getRawFileContent(rawFilePath);
      let imageBuffer: ArrayBuffer = value.buffer as ArrayBuffer;
      let imageSource: image.ImageSource = image.createImageSource(imageBuffer);
      let imagePixel: image.PixelMap = await imageSource.createPixelMap({
        desiredSize: {
          width: Constants.IMAGE_PIXEL_MAP_WIDTH,
          height: Constants.IMAGE_PIXEL_MAP_WIDTH
        }
      });
      return imagePixel;
    }
    
    async prepareResourcesForController(): Promise<void> {
      Logger.info(TAG, `prepareResourcesForController in`);
      this.constantsForControl.avMetadataList[0].mediaImage = await this.saveRawFileToPixelMap('first.png');
      this.constantsForControl.avMetadataList[0].avQueueImage = await this.saveRawFileToPixelMap('first.png');
      this.constantsForControl.avMetadataList[1].mediaImage = await this.saveRawFileToPixelMap('second.png');
      this.constantsForControl.avMetadataList[1].avQueueImage = await this.saveRawFileToPixelMap('second.png');
      this.constantsForControl.avMetadataList[2].mediaImage = await this.saveRawFileToPixelMap('third.png');
      this.constantsForControl.avMetadataList[2].avQueueImage = await this.saveRawFileToPixelMap('third.png');
    }
    
    /**
     * Registers the avplayer event listener.
     */
    async RegisterAVPlayerListener(): Promise<void> {
      // Registers focus interrupt listening.
      Logger.info(TAG, ` RegisterAVPlayerListener`);
      this.avPlayer?.on('audioInterrupt', (info: audio.InterruptEvent) => {
        Logger.info(TAG, 'audioInterrupt success,and InterruptEvent info is:' + info);
        if (this.avPlayer?.state === AVPlayerState.PLAYING) {
          Logger.info(TAG, 'audio interrupt, start pause');
          this.avPlayer?.pause();
          this.currentState.state = avSession.PlaybackState.PLAYBACK_STATE_PAUSE;
          this.session?.setAVPlaybackState(this.currentState);
        }
      });
      // Registers the playback time change callback function.
      this.avPlayer?.on('timeUpdate', (time: number) => {
        // The function of obtaining the current location globally is added.
        this.currentTime = time;
        Logger.info(TAG, 'time update progress:' + time);
        this.seekPositionLink!.set(time / this.currentAVMetadataLink!.get().duration! * 100);
      });
    }
    
    /**
     * UnRegister the event listener for the avplayer.
     */
    async unRegisterAVPlayerListener(): Promise<void> {
      Logger.info(TAG, ` unRegisterAVPlayerListener`);
      // UnRegister the listening of focus interrupt.
      this.avPlayer?.off('audioInterrupt');
      // UnRegister the playback time change callback function.
      this.avPlayer?.off('timeUpdate');
      this.avPlayer?.off('stateChange');
      this.avPlayer?.off('seekDone');
      this.avPlayer?.off('error');
    }
    

    }

  • 应用的初始化相关操作

    • 链接变量

      通过AppStorage.SetAndLink()将逻辑代码中的变量与界面代码中使用@StorageLink声明的变量连接起来,通过set()get()操作来修改或获取变量的值,关键代码段:

      private isPlayLink: SubscribedAbstractProperty<boolean> = null;
      this.isPlayLink = AppStorage.SetAndLink('IsPlaying', false);
      this.isPlayLink.set(false); // 设置变量的值
      let currentState : boolean = this.isPlayLink.get(); // 获取变量的值
      
    • 创建并设置媒体会话

      通过接口createAVSession()创建媒体会话;

      通过接口activate()激活媒体会话;

      通过接口setAVMetadata()设置当前媒体的元数据,设置后媒体播控中心可以读取使用此信息;

      通过接口setAVPlaybackState()设置当前媒体的播放信息,包括播放状态、播放进度,设置后媒体播控中心可以读取使用此信息;

      通过接口on()开启对媒体播控中心控制命令的监听,对媒体播控中心的命令进行处理,请激活媒体会话后再调用;

    应用在运行中相关的操作

    • 切换歌曲

      在切换歌曲时,除了需要设置媒体提供方自身的状态,还需要使用接口setAVPlaybackState()与接口setAVMetadata()将当前播放状态与元数据同步给媒体播控中心。

    • 发送自定义数据包

      媒体提供方可以使用接口dispatchSessionEvent()与接口setExtras()来发送自定义数据包。

相关权限

  1. 长时任务权限ohos.permission.KEEP_BACKGROUND_RUNNING

    如果需要让媒体提供方应用在后台运行或响应命令,需要注册长时任务权限[ohos.permission.KEEP_BACKGROUND_RUNNING]

    请在需要后台运行的Ability的module.json5中添加以下配置:

    {
       "module": {
          "requestPermissions": [
            {
               "name": "ohos.permission.KEEP_BACKGROUND_RUNNING"
            }
          ]
       }
    }
    

    添加配置后,需要在逻辑代码中进行申请长时任务的操作,示例代码如下:

    async startContinuousTask(){
      let wantAgentInfo = {
        wants:[
          {
            bundleName: "com.samples.mediaprovider",
            abilityName:"EntryAbility"
          }
        ],
        operationType : WantAgent.OperationType.START_ABILITY,
        requestCode: 0,
        wantAgentFlags: [WantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
      };
      let want = await WantAgent.getWantAgent(wantAgentInfo);
      await backgroundTaskManager.startBackgroundRunning(globalThis.context, backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK,want);
    }
    

依赖

此示例仅展示媒体提供方的相关功能,如果需要媒体会话提供的完整的自定义信息交互功能,请将本示例与媒体播控中心共同使用。

以上就是本篇文章所带来的鸿蒙开发中一小部分技术讲解;想要学习完整的鸿蒙全栈技术。可以在结尾找我可全部拿到!

下面是鸿蒙的完整学习路线 ,展示如下:

除此之外,根据这个学习鸿蒙全栈学习路线,也附带一整套完整的学习【文档+视频】,内容包含如下

内容包含了:(ArkTS、ArkUI、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、鸿蒙南向开发、鸿蒙项目实战)等技术知识点。帮助大家在学习鸿蒙路上快速成长!

鸿蒙【北向应用开发+南向系统层开发】文档

鸿蒙【基础+实战项目】视频

鸿蒙面经

为了避免大家在学习过程中产生更多的时间成本,对比我把以上内容全部放在了↓↓↓想要的可以自拿喔!谢谢大家观看!

相关推荐
spiker_1 小时前
RabbitMQ 常见使用模式详解
分布式·rabbitmq
不能再留遗憾了1 小时前
RabbitMQ 高级特性——持久化
分布式·rabbitmq·ruby
成为大佬先秃头1 小时前
解决RabbitMQ设置TTL过期后不进入死信队列
分布式·中间件·rabbitmq·java-rabbitmq
羌俊恩2 小时前
视频服务器:GB28181网络视频协议
服务器·网络·音视频
cuijiecheng20183 小时前
音视频入门基础:AAC专题(3)——AAC的ADTS格式简介
音视频·aac
七夜zippoe4 小时前
分布式系统实战经验
java·分布式
nomi-糯米4 小时前
Fisco Bcos 2.11.0配置console控制台2.10.0及部署调用智能合约
分布式·网络安全·区块链·智能合约·分布式账本
喜欢猪猪5 小时前
Kafka是如何保证数据的安全性、可靠性和分区的
分布式·kafka
芊言芊语5 小时前
分布式消息服务Kafka版的详细解析和配置方式
分布式·kafka
Alluxio5 小时前
选择Alluxio来解决AI模型训练场景数据访问的五大理由
大数据·人工智能·分布式·ai·语言模型