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

news2024/10/1 9:47:19

媒体会话-提供方

介绍

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

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

效果预览

1

使用说明
基础操作
  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);
    }
    

依赖

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

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

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

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

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

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

鸿蒙面经

在这里插入图片描述

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1979665.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024 年华数杯全国大学生数学建模竞赛C 题 老外游中国 完整成品文章分享

最近&#xff0c;“city 不 city”这一网络流行语在外国网红的推动下备受关注。随着我国过境免签政策的落实&#xff0c;越来越多外国游客来到中国&#xff0c;通过网络平台展示他们在华旅行的见闻&#xff0c;这不仅推动了中国旅游业的发展&#xff0c;更是在国际舞台上展现了…

大模型学习笔记 - LLM 解码与部署

LLM 解码与部署 LLM 解码与部署 1. 解码策略 1.1 背景 1.1 贪心搜所1.2 概率采样 1.2 贪心搜所改进 1.2.1 束搜索 &#xff08;保留前n个高概率的句子&#xff0c;最终选取整体概率高的生成&#xff09;1.2.2 长度惩罚 &#xff08;估计生成更长句子&#xff09;1.2.3 重复惩罚…

stm32入门-----硬件I2C读写MPU6050

目录 前言 一、stm32中I2C库函数介绍&#xff08;stm32f10x_i2c.h&#xff09; 1.初始化 2.使能操作 3.生成起始位和结束位标志 4.发送I2C从机地址 5.发送数据和接收数据 6.发送应答位 7.状态检测 二、硬件I2C读取MPU6050 1.电路连线图 2.主要工程文件 3.MPU6050.…

WordPress网站被入侵,劫持收录事件分析

7.15&#xff0c;网站被入侵&#xff0c;但是直到7月17日&#xff0c;我才发现被入侵。 16日&#xff0c;17日正常更新文章&#xff0c;17日查询网站收录数据时&#xff0c;在站长资源平台【流量与关键词】查询上&#xff0c;我发现了比较奇怪的关键词。 乱码关键词排名 起初…

案例分享:如何使用原生的NodeJs下载视频网站上的视频资源到本地生成MP4文件

如何使用原生的NodeJs下载视频网站上的视频资源到本地生成MP4文件 1、当下视频网站的视频资源无法通过常规手段下载的原因2、什么是M3U8是什么视频文件?3、如何下载M3U8文件中的TS文件并在本地合并为MP4文件?3.1 FFmpeg 是什么工具?3.2 安装 FFmpeg 工具3.3 使用 FFmpeg 工具…

每天五分钟深度学习:向量化方式完成逻辑回归模型的参数更新

本文重点 上一节课程中,我们学习了m个样本的前向传播的向量化,我们可以同时完成m个样本的前向传播,也就是m个样本z的计算,然后a的计算。本节课程我们将学习dw和db参数更新的向量化,最终得到整个逻辑回归算法的参数更新的向量化表示。 非向量化的逻辑回归梯度下降算法 如…

学习日志8.4--DHCP攻击防范

目录 DHCP饿死攻击 DHCP Sever仿冒攻击 DHCP攻击防范 DHCP动态主机配置协议&#xff0c;是给主机提供自动获取IP地址等配置信息的服务。在主机对DHCP服务器发送DHCP Discover请求之后&#xff0c;服务器回复offer&#xff0c;主机再回复request&#xff0c;最后服务器回复AC…

uni-app开发打包成H5部署到服务器

1. 点击发行 2. 点击进入manifest.json的h5配置里&#xff0c;根据自己的情况配置一些信息。一定要注意配置 “运行的基础路径”&#xff0c;如果出现空白页面或者静态文件404的情况&#xff0c;可能是因为这个路径没有配置好。 3. 填写域名 4. 点击发行后&#xff0c;控制台后…

如何让左右两个div各占50%,并且高度相同?

如何设置两个div各占一半&#xff0c;并且高度随着内容增加&#xff0c;而且两边div的高度一致呢&#xff1f;默认会发现高度不一致&#xff0c;改用flex就可以了&#xff0c;另外发现传统的table也可以轻易实现。不知道不用flex的话是否可以实现。 方法1&#xff08;div实现&a…

二分+dp,CF 1993D - Med-imize

一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 D - Med-imize 二、解题报告 1、思路分析 对于n < k的情况直接排序就行 对于n > k的情况 最终的序列长度一定是 (n - 1) % k 1 这个序列是原数组的一个子序列 对于该序列的第一个元素&#xff0…

Spring中使用Async进行异步功能开发实战-以大文件上传为例

目录 前言 一、场景再现 1、Event的同步机制 二、性能优化 1、异步支持配置 2、自定义处理线程池扩展 3、将线程池配置类绑定到异步方法 三、总结 前言 在之前的博客中&#xff0c;曾将讲了在SpringBoot中如何使用Event来进行大文件上传的解耦&#xff0c;原文地址&…

算法回忆录(2)

6.输入一个非递减排列的整数数组nums,和一个目标值target。请找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值target,则输出0&#xff0c;0。请设计一个时间复杂度为0(log n)的算法解决此问题。 输入包括两行&#xff0c;第一行输入两个整数&#xff0c…

【电路笔记】-偏置晶体管

偏置晶体管 文章目录 偏置晶体管1、概述2、共发射极晶体管偏置3、集极反馈偏置4、双反馈晶体管偏置5、发射极反馈配置6、分压器晶体管偏置晶体管偏置是将晶体管直流工作电压或电流条件设置为正确电平的过程,以便晶体管可以正确放大任何交流输入信号 1、概述 双极晶体管的稳态…

DBA | 炼气期,关系数据库及六大范式(NF)理论概述!

[ 知识是人生的灯塔&#xff0c;只有不断学习&#xff0c;才能照亮前行的道路 ] 前言简述 描述&#xff1a;上一章&#xff0c;我们简单了解了关系类型数据库&#xff0c;以及其相关产品&#xff0c;此章节我们由浅入深的学习一下什么是关系型数据库&#xff0c;不过在讲解关系…

中国县城建设统计年鉴(2015-2022年)

数据年限&#xff1a;2015-2022年&#xff0c;年鉴时间即为数据时间 数据格式&#xff1a;pdfexcel 数据内容&#xff1a; 共分12个部分&#xff0c; 包括县城市政公用设施水平&#xff08;人口密度/人均日生活用水量/供水普及率/燃气普及率/人均道路面积/建成区路网密度/污水处…

51单片机—电动车报警器

一. 入门 1.1 开发环境的安装 用什么写代码--语言是C&#xff0c;环境是keilKeil C51是美国Keil Software公司出品的51系列兼容单片机C语言软件开发系统&#xff0c;与汇编相比&#xff0c;C语言在功能上、结构性、可读性、可维护性上有明显的优势&#xff0c;因而易学易用。…

江协科技51单片机学习- p37 红外遥控(外部中断)

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…

提供三方API接口、调用第三方接口API接口、模拟API接口(三)使用AOP切面编程实现signature签名验证

接着第一篇文章 提供三方API接口、调用第三方接口API接口、模拟API接口&#xff08;一&#xff09;通过signature签名验证&#xff0c;避免参数恶意修改 我们来继续优化&#xff1a; /*** 模拟后端校验签名* param request* param data* return* throws UnsupportedEncodingEx…

[环境配置]C4D OC渲染器解决缺少cudnn_8_0_4Octance正版缺少cudnn_8_0_4_win文件解决方法

关于Octance正版缺少cudnn_8_0_4_win文件解决方法 可在此处https://download.csdn.net/download/FL1623863129/89605383进行下载 放到对应文件位置即可 在计算机图形学领域&#xff0c;Cinema 4D&#xff08;C4D&#xff09;作为一款顶尖的专业3D建模、动画与渲染软件&#x…

学生管理系统之数据模拟与数据显示

学生管理系统之数据模拟与数据显示 设计一个单例 模拟数据 显示数据