c77fb700创建于 2025年1月16日历史提交
/*
 * Copyright (c) 2025 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 { media } from '@kit.MediaKit';
import { common, wantAgent } from '@kit.AbilityKit';
import { avSession } from '@kit.AVSessionKit';
import { formBindingData, formProvider } from '@kit.FormKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { resourceManager } from '@kit.LocalizationKit';
import { SongConstants } from '@ohos/constantsCommon';
import { AudioPlayerState, MusicPlayMode } from '../viewmodel/MusicData';
import { SongItem } from '../viewmodel/SongData';
import { CardData } from '../viewmodel/CardData';
import { BackgroundTaskManager } from './BackgroundTaskManager/BackgroundTaskManager';
import SongItemBuilder from './SongItemBuilder';
import { PreferencesUtil } from './PreferencesUtil';
import { MediaTools } from './MediaTools';
import { Logger } from './Logger';

const TAG = 'MediaService';

export class MediaService {
  public avPlayer?: media.AVPlayer;
  private context: common.UIAbilityContext | undefined = AppStorage.get('context');
  private session?: avSession.AVSession;
  private songItemBuilder: SongItemBuilder = new SongItemBuilder();
  private songItem: SongItem = new SongItem();
  private playMode: MusicPlayMode = MusicPlayMode.ORDER;
  private state: AudioPlayerState = AudioPlayerState.IDLE;
  private isFirst: boolean = true;
  private isPrepared: boolean = false;
  private musicIndex: number = 0;
  private songList: SongItem[] = [];
  private formIds: string[] = [];
  private isCurrent: boolean = true;
  private seekCall: (seekDoneTime: number) => void = (seekDoneTime: number) => {
    this.isCurrent = true;
    Logger.info(TAG, `AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
    this.setPlayState({
      position: {
        elapsedTime: this.getCurrentTime(),
        updateTime: new Date().getTime()
      }
    });
  };
  private errorCall: (err: BusinessError) => void = (err: BusinessError) => {
    Logger.error(TAG, `Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
    this.avPlayer?.reset();
  };
  private updateTimeCall: (updateTime: number) => void = (updateTime: number) => {
    if (this.isCurrent) {
      AppStorage.setOrCreate('currentTime', MediaTools.msToCountdownTime(updateTime));
      AppStorage.setOrCreate<number>('progress', updateTime);
    }
  };
  private stateCall: (state: string) => Promise<void> = async (state: string) => {
    switch (state) {
      case 'idle':
        Logger.info(TAG, 'AVPlayer state idle called.');
        this.state = AudioPlayerState.IDLE;
        this.songItem = await this.songItemBuilder.build(this.songList[this.musicIndex]);
        let url = this.songItemBuilder.getRealUrl();
        if (this.avPlayer && url) {
          let avFileDescriptor: media.AVFileDescriptor = { fd: url.fd, offset: url.offset, length: url.length };
          this.avPlayer.fdSrc = avFileDescriptor;
          Logger.info(TAG, 'loadAsset avPlayer.url:' + this.avPlayer.fdSrc);
        }
        break;
      case 'initialized':
        Logger.info(TAG, 'AVPlayer state initialized called.');
        this.state = AudioPlayerState.INITIALIZED;
        if (this.avPlayer) {
          this.avPlayer.prepare().then(() => {
            Logger.info(TAG, 'AVPlayer prepare succeeded.');
          }, (err: BusinessError) => {
            Logger.error(TAG, `Invoke prepare failed, code is ${err.code}, message is ${err.message}`);
          });
        }
        break;
      case 'prepared':
        Logger.info(TAG, 'AVPlayer state prepared called.');
        this.state = AudioPlayerState.PREPARED;
        this.isPrepared = true;
        if (this.avPlayer) {
          this.avPlayer.play();
        }
        this.setAVMetadata();
        Logger.info(TAG, 'AVPlayer prepared succeeded.');
        break;
      case 'playing':
        Logger.info(TAG, 'AVPlayer state playing called.');
        this.state = AudioPlayerState.PLAY;
        break;
      case 'paused':
        Logger.info(TAG, 'AVPlayer state paused called.');
        this.state = AudioPlayerState.PAUSE;
        break;
      case 'completed':
        Logger.info(TAG, 'AVPlayer state completed called.');
        this.state = AudioPlayerState.COMPLETED;
        this.playNextAuto(false);
        break;
      case 'stopped':
        Logger.info(TAG, 'AVPlayer state stopped called.');
        this.state = AudioPlayerState.STOP;
        if (this.avPlayer) {
          this.avPlayer.reset();
        }
        break;
      case 'released':
        Logger.info(TAG, 'AVPlayer state released called.');
        this.state = AudioPlayerState.RELEASED;
        break;
      default:
        Logger.info(TAG, 'AVPlayer state unknown called.');
        this.state = AudioPlayerState.UNKNOWN;
        break;
    }
    this.updateCardData();
    this.updateIsPlay(this.state === AudioPlayerState.PLAY);
  };
  private playCall: () => void = () => {
    Logger.info(TAG, `on play , do play task`);
    if (this.isFirst) {
      this.loadAssent(0);
    } else {
      this.play();
    }
  };
  private pauseCall: () => void = () => {
    Logger.info(TAG, `on pause , do pause task`);
    this.pause();
  };
  private playNextCall: () => void = () => {
    Logger.info(TAG, `on playNext , do playNext task`);
    this.playNextAuto(true);
  };
  private playPreviousCall: () => void = () => {
    Logger.info(TAG, `on playPrevious , do playPrevious task`);
    this.playPrevious();
  };
  private durationUpdateCall: (time: number) => void = (time: number) => {
    if (time > 0) {
      AppStorage.setOrCreate('totalTime', MediaTools.msToCountdownTime(time));
      AppStorage.setOrCreate('progressMax', time);
    } else {
      Logger.info(TAG, `on durationUpdate , get time failed or now is currently live.`);
    }
  };

  private setAVPlayerCallback() {
    if (!this.avPlayer) {
      return;
    }
    this.avPlayer.on('seekDone', this.seekCall);

    this.avPlayer.on('error', this.errorCall);

    this.avPlayer.on('timeUpdate', this.updateTimeCall);

    this.avPlayer.on('stateChange', this.stateCall);

    this.avPlayer.on('durationUpdate', this.durationUpdateCall);
  }

  private async createSession() {
    if (!this.context) {
      return;
    }
    this.session = await avSession.createAVSession(this.context, 'SESSION_NAME', 'audio');
    this.session.activate();
    Logger.info(TAG, `session create done : sessionId : ${this.session.sessionId}`);
    this.setAVMetadata();
    let wantAgentInfo: wantAgent.WantAgentInfo = {
      wants: [
        {
          bundleName: this.context.abilityInfo.bundleName,
          abilityName: this.context.abilityInfo.name
        }
      ],
      operationType: wantAgent.OperationType.START_ABILITIES,
      requestCode: 0,
      wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
    }
    wantAgent.getWantAgent(wantAgentInfo).then((agent) => {
      if (this.session) {
        this.session.setLaunchAbility(agent);
      }
    })
    this.setListenerForMesFromController();
  }

  private initAudioPlayer() {
    media.createAVPlayer().then(async avPlayer => {
      if (avPlayer !== null) {
        this.avPlayer = avPlayer;
        this.setAVPlayerCallback();
        this.createSession();
      }
    }).catch((error: BusinessError) => {
      Logger.error(TAG, 'this avPlayer: ', `catch error happened,error code is ${error.code}`)
    })
  }

  private async setListenerForMesFromController() {
    if (!this.session) {
      return;
    }
    this.session.on('play', this.playCall);
    this.session.on('pause', this.pauseCall);
    this.session.on('playNext', this.playNextCall);
    this.session.on('playPrevious', this.playPreviousCall);
  }

  private async unregisterSessionListener() {
    if (!this.session) {
      return;
    }
    this.session.off('play');
    this.session.off('pause');
    this.session.off('playNext');
    this.session.off('playPrevious');
  }

  private async setAVMetadata() {
    let id = this.musicIndex;
    try {
      if (this.context) {
        let mediaImage = await MediaTools.getPixelMapFromResource(this.context,
          this.songList[this.musicIndex].label as resourceManager.Resource);
        Logger.info(TAG, 'getPixelMapFromResource success' + JSON.stringify(mediaImage));
        let metadata: avSession.AVMetadata = {
          assetId: `${id}`,
          title: this.songList[this.musicIndex].title,
          artist: this.songList[this.musicIndex].singer,
          mediaImage: mediaImage,
          duration: this.getDuration()
        };
        if (this.session) {
          this.session.setAVMetadata(metadata).then(() => {
            Logger.info(TAG, 'SetAVMetadata successfully');
          }).catch((err: BusinessError) => {
            Logger.error(TAG, `SetAVMetadata BusinessError: code: ${err.code}, message: ${err.message}`);
          });
        }
      }
    } catch (error) {
      Logger.error(TAG, `SetAVMetadata try: code: ${(error as BusinessError).code},
       message: ${(error as BusinessError).message}`);
    }
  }

  private updateIsPlay(isPlay: boolean) {
    AppStorage.setOrCreate<boolean>('isPlay', isPlay);
    this.setPlayState({
      state: isPlay ? avSession.PlaybackState.PLAYBACK_STATE_PLAY : avSession.PlaybackState.PLAYBACK_STATE_PAUSE,
      position: {
        elapsedTime: this.getCurrentTime(),
        updateTime: new Date().getTime()
      }
    });
  }

  private getCurrentTime() {
    if (this.isPrepared && this.avPlayer) {
      return this.avPlayer.currentTime;
    }
    return 0;
  }

  private getDuration() {
    if (this.isPrepared && this.avPlayer) {
      return this.avPlayer.duration;
    }
    return 0;
  }

  private start(seekMs?: number) {
    Logger.info(TAG, 'AVPlayer play() isPrepared:' + this.isPrepared + ', state:' + this.state + ',seek:' + seekMs);
    if (this.avPlayer) {
      this.avPlayer.prepare().then(() => {
      }).catch((error: BusinessError) => {
        Logger.error(TAG, `start error ${JSON.stringify(error)}`)
        this.state = AudioPlayerState.ERROR;
        this.updateIsPlay(false);
        this.isPrepared = false;
      });
    }
  }

  private playNext() {
    Logger.info(TAG, 'playNext Index:' + this.musicIndex + ', length-1:' + (this.songList.length - 1));
    if (this.musicIndex === this.songList.length - 1) {
      this.loadAssent(0);
    } else {
      this.loadAssent(this.musicIndex + 1);
    }
  }

  private playRandom() {
    let num = Math.round(Math.random() * (this.songList.length - 1));
    if (this.musicIndex === num) {
      this.playRandom();
    } else {
      this.updateMusicIndex(num);
      this.loadAssent(num);
    }
    Logger.info(TAG, 'play Random:' + this.musicIndex);
  }

  private async reset() {
    Logger.info(TAG, 'reset()');
    await this.songItemBuilder.release();
    if (this.avPlayer) {
      await this.avPlayer.reset();
    }
    this.isPrepared = false;
  }

  private updateMusicIndex(musicIndex: number) {
    Logger.info(TAG, 'updateMusicIndex ===> ' + musicIndex);
    AppStorage.setOrCreate('selectIndex', musicIndex);
    if (this.musicIndex !== musicIndex) {
      this.musicIndex = musicIndex;
    }
    Logger.info(TAG, 'this.session !== undefined ===> ' + (this.session != undefined));
    if (this.session !== undefined) {
      this.setAVMetadata();
    }
  }

  private async setPlayState(playbackState: avSession.AVPlaybackState) {
    if (this.session) {
      this.session.setAVPlaybackState(playbackState, (err: BusinessError) => {
        if (err) {
          Logger.info(TAG, `SetAVPlaybackState BusinessError: code: ${err.code}, message: ${err.message}`);
        } else {
          Logger.info(TAG, 'SetAVPlaybackState successfully');
        }
      });
    }
  }

  private constructor() {
    let list: SongItem[] | undefined = AppStorage.get('songList');
    if (list) {
      this.songList = list;
    }
    this.songItemBuilder = new SongItemBuilder();
    this.initAudioPlayer();
  }

  public static getInstance(): MediaService {
    let mediaService: MediaService | undefined = AppStorage.get('mediaService');
    if (!mediaService) {
      mediaService = new MediaService();
      AppStorage.setOrCreate('mediaService', mediaService);
    }
    return mediaService;
  }

  /**
   * Play music by index.
   *
   * @param musicIndex
   */
  public async loadAssent(musicIndex: number) {
    if (musicIndex >= this.songList.length) {
      Logger.error(TAG, `current musicIndex ${musicIndex}`);
      return;
    }
    if (this.context != undefined) {
      BackgroundTaskManager.getBackgroundTaskManager().startContinuousTask(this.context);
    }
    this.updateMusicIndex(musicIndex);
    if (this.isFirst && this.avPlayer) {
      this.isFirst = false;
      this.songItem = await this.songItemBuilder.build(this.songList[this.musicIndex]);
      let url = this.songItemBuilder.getRealUrl();
      if (url) {
        let avFileDescriptor: media.AVFileDescriptor = { fd: url.fd, offset: url.offset, length: url.length };
        this.avPlayer.fdSrc = avFileDescriptor;
        Logger.info(TAG, 'loadAsset avPlayer.url:' + this.avPlayer.fdSrc);
      }
    } else {
      await this.stop();
    }
  }

  /**
   * Get whether the music is played for the first.
   *
   * @returns isFirst
   */
  public getFirst() {
    return this.isFirst;
  }

  /**
   * Set music play mode.
   *
   * @param playMode
   */
  public setPlayModel(playMode: MusicPlayMode) {
    this.playMode = playMode;
    Logger.info(TAG, 'setPlayModel mode: ' + this.playMode);
  }

  /**
   * Get music play mode.
   *
   * @returns playMode.
   */
  public getPlayMode() {
    return this.playMode;
  }

  /**
   * Seek play music.
   *
   * @param ms.
   */
  public seek(ms: number) {
    if (this.isPrepared && this.state != AudioPlayerState.ERROR && this.avPlayer) {
      let seekMode = this.getCurrentTime() < ms ? 0 : 1;
      let realTime = (ms <= 0 ? 0 : (ms >= this.getDuration() ? this.getDuration() : ms));
      this.isCurrent = false;
      this.avPlayer.seek(realTime, seekMode);
    }
  }

  /**
   * Play music.
   */
  public async play() {
    Logger.info(TAG, 'AVPlayer play() isPrepared:' + this.isPrepared + ', state:' + this.state);
    if (this.context != undefined) {
      BackgroundTaskManager.getBackgroundTaskManager().startContinuousTask(this.context);
    }
    if (!this.isPrepared) {
      this.start(0);
    } else if (this.avPlayer) {
      this.avPlayer.play().then(() => {
        Logger.info(TAG, 'progressTime play() current time:' + this.getCurrentTime());
        this.seek(this.getCurrentTime());
        this.updateIsPlay(true);
        this.state = AudioPlayerState.PLAY;
      })
    }
  }

  /**
   * Pause music.
   */
  public pause() {
    Logger.info(TAG, 'AVPlayer pause() isPrepared:' + this.isPrepared + ', state:' + this.state);
    if (this.isPrepared && this.state === AudioPlayerState.PLAY && this.avPlayer) {
      this.avPlayer.pause().then(() => {
        this.state = AudioPlayerState.PAUSE;
        this.updateIsPlay(false);
      });
    }
  }

  /**
   * Play next music.
   *
   * @param isFromControl
   */
  public playNextAuto(isFromControl: boolean) {
    Logger.info(TAG, 'playNextAuto mode:' + this.playMode);
    switch (this.playMode) {
      case MusicPlayMode.SINGLE_CYCLE:
        if (isFromControl) {
          this.playNext();
        } else if (this.avPlayer) {
          this.avPlayer.play();
        }
        break;
      case MusicPlayMode.ORDER:
        this.playNext();
        break;
      case MusicPlayMode.RANDOM:
        this.playRandom();
        break;
      default:
        break;
    }
  }

  /**
   * Play previous music.
   */
  public playPrevious() {
    switch (this.playMode) {
      case MusicPlayMode.RANDOM:
        this.playRandom();
        break;
      case MusicPlayMode.ORDER:
      case MusicPlayMode.SINGLE_CYCLE:
        if (this.musicIndex === 0) {
          this.updateMusicIndex(this.songList.length - 1);
        } else {
          this.updateMusicIndex(this.musicIndex - 1);
        }
        Logger.info(TAG, 'setLastIndex:' + this.musicIndex);
        this.loadAssent(this.musicIndex);
        break;
      default:
        break;
    }
  }

  /**
   * Stop music
   */
  public async stop() {
    Logger.info(TAG, 'stop()');
    if (this.isPrepared && this.avPlayer) {
      await this.avPlayer.stop();
      this.updateIsPlay(false);
      this.state = AudioPlayerState.PAUSE;
    }
  }

  /**
   * release avPlayer.
   */
  public release() {
    if (this.avPlayer && this.session && this.context) {
      this.updateIsPlay(false);
      this.stop();
      this.reset();
      this.avPlayer.release();
      this.state = AudioPlayerState.IDLE;
      if (this.context != undefined) {
        BackgroundTaskManager.getBackgroundTaskManager().stopContinuousTask(this.context);
      }
      this.unregisterSessionListener();
      this.session.destroy((err: BusinessError) => {
        if (err) {
          Logger.error(TAG, `Failed to destroy session. Code: ${err.code}, message: ${err.message}`);
        } else {
          Logger.info(TAG, `Destroy : SUCCESS `);
        }
      });
    }
  }

  /**
   * Update card data.
   */
  public async updateCardData() {
    try {
      if (!this.context) {
        return;
      }
      PreferencesUtil.getInstance().removePreferencesFromCache(this.context);
      this.formIds = await PreferencesUtil.getInstance().getFormIds(this.context);
      if (this.formIds === null || this.formIds === undefined) {
        Logger.error(TAG, 'WANG formIds is null');
        return;
      }

      let cardSongList: Array<SongItem> = [];
      if (this.musicIndex + SongConstants.ADD_INDEX_ONE === this.songList.length) {
        cardSongList = this.songList.slice(SongConstants.SLICE_START_ZERO, SongConstants.SLICE_END_THREE);
      } else if (this.musicIndex + SongConstants.ADD_INDEX_TWO === this.songList.length) {
        cardSongList.push(this.songList[this.songList.length - 1]);
        cardSongList.push(this.songList[0]);
        cardSongList.push(this.songList[1]);
      } else if (this.musicIndex + SongConstants.ADD_INDEX_THREE === this.songList.length) {
        cardSongList = this.songList.slice(this.songList.length - SongConstants.SLICE_INDEX_TWO,
          this.songList.length);
        cardSongList.push(this.songList[0]);
      } else {
        cardSongList = this.songList.slice(this.musicIndex + SongConstants.SLICE_INDEX_ONE,
          this.musicIndex + SongConstants.SLICE_INDEX_FOUR);
      }
      let formData: CardData = {
        isPlay: this.state === AudioPlayerState.PLAY,
        musicName: this.songList[this.musicIndex].title,
        musicCover: this.songList[this.musicIndex].label,
        musicSinger: this.songList[this.musicIndex].singer,
        cardSongList: cardSongList
      }
      let formInfo = formBindingData.createFormBindingData(formData);

      this.formIds.forEach(formId => {
        formProvider.updateForm(formId, formInfo).then(() => {
          Logger.info(TAG, 'WANG updateForm data succeed' + ', formId:' + formId);
        }).catch((error: BusinessError) => {
          Logger.error(TAG, 'updateForm err:' + JSON.stringify(error));
          if (error.code === SongConstants.ID_NO_EXIT && this.context) {
            PreferencesUtil.getInstance().removeFormId(this.context, formId);
          }
        })
      })
    } catch (error) {
      Logger.error(TAG, `updateCardData err: ${(error as BusinessError).code}`);
    }
  }

  /**
   * Update card data on destroy.
   */
  public async updateOnDestroy() {
    if (this.formIds === null || this.formIds === undefined) {
      Logger.error(TAG, 'formIds is null');
      return;
    }
    let formData: Record<string, boolean> = {
      'isPlay': false
    }
    let formInfo = formBindingData.createFormBindingData(formData);
    for (let index = 0; index < this.formIds.length; index++) {
      await formProvider.updateForm(this.formIds[index], formInfo);
    }
  }
}