49776b61创建于 8月7日历史提交
/*
 * Copyright (c) 2026 Huawei Device Co., Ltd.
 * Licensed under Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with License.
 * You may obtain a copy of License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under 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 { common, wantAgent } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import Logger from '../Logger';
import { AudioControlCenter } from './AudioControlCenter';
import { MediaUtils } from '../MediaUtils';
import { AVCastManager } from './AVCastManager';

const TAG = 'AVSessionController';

export class AVSessionController {
  private static instance: AVSessionController;
  private context: common.UIAbilityContext | undefined = undefined;
  private avSession: avSession.AVSession | undefined = undefined;
  private audioControlCenter: AudioControlCenter | undefined = undefined;
  private avCastManager: AVCastManager | undefined = undefined;

  private constructor() {
  }

  public static getInstance(): AVSessionController {
    if (!AVSessionController.instance) {
      AVSessionController.instance = new AVSessionController();
    }
    return AVSessionController.instance;
  }

  // [Start initAVSession]
  public async initAVSession() {
    this.context = AppStorage.get('context') as common.UIAbilityContext;
    if (!this.context) {
      Logger.error(TAG, 'Session create failed: context is undefined');
      return;
    }

    this.audioControlCenter = AudioControlCenter.getInstance();
    if (!this.audioControlCenter) {
      Logger.error(TAG, 'Session create failed: audioControlCenter is undefined');
      return;
    }

    try {
      this.avSession = await avSession.createAVSession(this.context, 'PLAY_AUDIO', 'audio');
      await this.avSession.activate().catch(() => {
        Logger.info(TAG, `Session activate error`);
      });
      // [Start setExtras]
      this.avSession.setExtras({
        requireAbilityList: ['url-cast'],
      }).catch(() => {
        Logger.info(TAG, `Session setExtras error`);
      });
      await this.setAVMetadata();
      // [End setExtras]
      this.setLaunchAbility();
      this.setListenerForMesFromController();

      this.avCastManager = AVCastManager.getInstance();
      Logger.info(TAG, 'AVCastManager initialized');
    } catch (error) {
      Logger.error(TAG, 'initAVSession error');
    }
  }
  // [End initAVSession]

  // [Start setLaunchAbility]
  private setLaunchAbility() {
    if (!this.context) {
      return;
    }
    let wantAgentInfo: wantAgent.WantAgentInfo = {
      wants: [
        {
          bundleName: this.context.abilityInfo.bundleName,
          abilityName: this.context.abilityInfo.name
        }
      ],
      actionType: wantAgent.OperationType.START_ABILITIES,
      requestCode: 0,
      wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
    };
    wantAgent.getWantAgent(wantAgentInfo).then((agent) => {
      if (this.avSession) {
        this.avSession.setLaunchAbility(agent).catch(() => {
          Logger.error(TAG, `setLaunchAbility failed.`);
        });
      }
    }).catch((err: BusinessError) => {
      Logger.error(TAG, `getWantAgent failed: code: ${err.code}, message: ${err.message}`);
    });
  }
  // [End setLaunchAbility]

  // [Start setListenerForMesFromController]
  private setListenerForMesFromController() {
    if (!this.avSession) {
      return;
    }
    try {
      this.avSession.on('play', this.onPlay);
      this.avSession.on('pause', this.onPause);
      this.avSession.on('playNext', this.onPlayNext);
      this.avSession.on('playPrevious', this.onPlayPrevious);
      this.avSession.on('seek', this.onSeek);
    } catch (error) {
      Logger.error(TAG, 'audioControlCenter is undefined in onPlay');
    }
  }
  // [End setListenerForMesFromController]

  private onPlay: () => void = () => {
    Logger.info(TAG, 'onPlay received');

    if (!this.audioControlCenter) {
      Logger.error(TAG, 'audioControlCenter is undefined in onPlay');
      return;
    }
    this.audioControlCenter.play();
  }
  private onPause: () => void = () => {
    Logger.info(TAG, 'onPause received');
    if (!this.audioControlCenter) {
      Logger.error(TAG, 'audioControlCenter is undefined in onPause');
      return;
    }
    this.audioControlCenter.pause();
  }
  private onPlayNext: () => void = async () => {
    Logger.info(TAG, 'onPlayNext received');
    if (!this.audioControlCenter) {
      Logger.error(TAG, 'audioControlCenter is undefined in onPlayNext');
      return;
    }
    await this.audioControlCenter.nextChapter();
  }
  private onPlayPrevious: () => void = async () => {
    Logger.info(TAG, 'onPlayPrevious received');
    if (!this.audioControlCenter) {
      Logger.error(TAG, 'audioControlCenter is undefined in onPlayPrevious');
      return;
    }
    await this.audioControlCenter.previousChapter();
  }
  private onSeek: (curMs: number) => void = (curMs: number) => {
    Logger.info(TAG, `onSeek received: ${curMs}ms`);
    if (!this.audioControlCenter) {
      Logger.error(TAG, 'audioControlCenter is undefined in onSeek');
      return;
    }
    this.audioControlCenter.seekTo(curMs);
  }

  // [Start setAVMetadata]
  public async setAVMetadata() {
    try {
      if (!this.context || !this.avSession || !this.audioControlCenter) {
        return;
      }
      const currentChapterIndex = this.audioControlCenter.getCurrentChapterIndex();
      const chapter = this.audioControlCenter.getCurrentChapter();
      if (!chapter) {
        return;
      }

      const duration = this.audioControlCenter.getDuration();
      let metadata: avSession.AVMetadata = {
        assetId: `${currentChapterIndex}`,
        title: chapter.title,
        artist: chapter.author,
        duration: duration,
        avQueueName: 'My Audio Book',
        avQueueId: 'AudioBookQueueId1',
        filter: avSession.ProtocolType.TYPE_CAST_PLUS_STREAM | avSession.ProtocolType.TYPE_DLNA |
          avSession.ProtocolType.TYPE_CAST_PLUS_AUDIO
      };
      try {
        const queuePixelMap = await MediaUtils.getPixelMapFromResource(this.context, $r('app.media.image_cover'));
        metadata.avQueueImage = queuePixelMap;
      } catch (error) {
        Logger.error(TAG, `Failed to load queue image: ${JSON.stringify(error)}`);
      }
      // get lyric
      if (chapter.lyric) {
        const lrc = await MediaUtils.getLrcFromRawFile(this.context, chapter.lyric);
        if (lrc) {
          metadata.lyric = lrc;
        }
      }
      // get PixelMap
      if (chapter.avatar) {
        try {
          const pixelMap = await MediaUtils.getPixelMapFromResource(this.context, chapter.avatar);
          metadata.mediaImage = pixelMap;
        } catch (error) {
          Logger.error(TAG, `Failed to load avatar: ${JSON.stringify(error)}`);
        }
      }
      // set AVMetadata
      this.avSession.setAVMetadata(metadata).then(() => {
        Logger.info(TAG, 'setAVMetadata succeeded');
      }).catch((err: BusinessError) => {
        Logger.error(TAG, `setAVMetadata failed: code: ${err.code}, message: ${err.message}`);
      });
    } catch (error) {
      Logger.error(TAG, `setAVMetadata failed: ${JSON.stringify(error)}`);
    }
  }
  // [End setAVMetadata]

  // [Start setPlayState]
  public setPlayState(isPlay: boolean) {
    if (!this.avSession) {
      Logger.error(TAG, 'AVSession is undefined');
      return;
    }
    this.avSession.setAVPlaybackState({
      state: isPlay ? avSession.PlaybackState.PLAYBACK_STATE_PLAY : avSession.PlaybackState.PLAYBACK_STATE_PAUSE,
    }, (err: BusinessError) => {
      if (err) {
        Logger.error(TAG, `setPlayState failed: code: ${err.code}, message: ${err.message}`);
      } else {
        Logger.info(TAG, 'setPlayState succeeded');
      }
    });
  }
  // [End setPlayState]

  // [Start setProgressState]
  public setProgressState(ms: number) {
    if (!this.avSession) {
      return;
    }
    this.avSession.setAVPlaybackState({
      position: {
        elapsedTime: ms,
        updateTime: new Date().getTime()
      }
    }, (err: BusinessError) => {
      if (err) {
        Logger.error(TAG, `setProgressState failed: code: ${err.code}, message: ${err.message}`);
      } else {
        Logger.info(TAG, 'setProgressState succeeded');
      }
    });
  }
  // [End setProgressState]

  // [Start unregisterSessionListener]
  public async unregisterSessionListener() {
    if (!this.avSession) {
      return;
    }
    try {
      this.avSession.off('play');
      this.avSession.off('pause');
      this.avSession.off('playNext');
      this.avSession.off('playPrevious');
      this.avSession.off('seek');
      this.avSession.destroy().catch(() => {
        Logger.error(TAG, `avSession destroy failed`);
      });
    } catch (error) {
      let err: BusinessError = error;
      Logger.error(TAG, `setProgressState failed: code: ${err.code}, message: ${err.message}`);
    }
  }
  // [End unregisterSessionListener]

  public getAVSession(): avSession.AVSession | undefined {
    return this.avSession;
  }

  public getAVCastManager(): AVCastManager | undefined {
    return this.avCastManager;
  }
}