BackgroundAudioController.ets 12.4 KB
import { Context, WantAgent, wantAgent } from '@kit.AbilityKit'
import { avSession as AVSessionManager } from '@kit.AVSessionKit'
import { backgroundTaskManager } from '@kit.BackgroundTasksKit'
import { BusinessError } from '@kit.BasicServicesKit'
import { DateTimeUtils, EmitterEventId, EmitterUtils, Logger } from 'wdKit/Index'
import { Events, PlayerConstants } from '../constants/PlayerConstants'
import { WDPlayerController } from './WDPlayerController'
import { image } from '@kit.ImageKit'
import { JSON } from '@kit.ArkTS'

const TAG = "BackgroundAudioController"

export class BackgroundAudioController {

  private static bgAudioController: BackgroundAudioController
  private constructor() {
    EmitterUtils.receiveEvent(EmitterEventId.APP_ENTER_BACKGROUD, () => {
      this.startContinuousTask()
    })
  }
  public static sharedController() {
    if (!BackgroundAudioController.bgAudioController) {
      BackgroundAudioController.bgAudioController = new BackgroundAudioController()
    }
    return BackgroundAudioController.bgAudioController
  }

  public gotContextFunc?: () => Context
  public avplayerController?: WDPlayerController
  private lastSession?: AVSessionManager.AVSession

  private applyedLongTaskPlay: boolean = false

  private lastProgress: number = 0.0
  private hasSetupProgress: boolean = false
  private playing: boolean = false
  private lastItemAssetId?: string
  private lastItemTitle?: string
  private lastItemMediaImage?: Resource
  private lastItemArtist?: string
  private lastItemTotalDuration: number = 0

  // 开始创建并激活媒体会话
  // 创建session
  async createSession() {
    if (!this.gotContextFunc) { return }

    if (this.lastSession == null) {
      this.destorySession()
      let type: AVSessionManager.AVSessionType = 'audio';
      let session = await AVSessionManager.createAVSession(this.gotContextFunc(),'SESSION_NAME', type);
      this.lastSession = session
    }

    // 激活接口要在元数据、控制命令注册完成之后再执行
    await this.lastSession?.activate();
    Logger.debug(TAG, `session create done : sessionId : ${this.lastSession?.sessionId}`);

    this.lastProgress = 0
    this.hasSetupProgress = false
  }

  destorySession() {
    if (this.lastSession) {
      this.lastSession.deactivate();
      this.lastSession.destroy();
    }
  }

  //设置播放元数据
  async setSessionMetaData(assetId: string, title: string, mediaImage: Resource, artist: string) {
    this.lastItemAssetId = assetId.length > 0 ? assetId : ""
    this.lastItemTitle = title || ""
    this.lastItemMediaImage = mediaImage
    this.lastItemArtist = artist || ""
  }

  async setSessionMetaDataWithDuration(assetId: string, title: string, mediaImage: Resource, artist: string, duration: number) {
    Logger.debug(TAG, `SetAVMetadata assetId: ${assetId}, title: ${title}, mediaImage: ${mediaImage}, artist: ${artist}, duration: ${duration}`);

    if (!this.gotContextFunc) { return }
    let pixelMapImage:image.PixelMap | null = null
    try {
      const data:Uint8Array = await this.gotContextFunc().resourceManager.getMediaContent(mediaImage)
        pixelMapImage = await image.createPixelMap(data.buffer as ArrayBuffer, {
          editable: false,
          alphaType: image.AlphaType.UNKNOWN,
          srcPixelFormat: image.PixelMapFormat.RGB_888,
          size: { width:300, height:300 }
        })
    } catch (e) {
      Logger.error(TAG, `SetAVMetadata ` + JSON.stringify(e));
    } finally {
        // Logger.debug(TAG, `SetAVMetadata ` + JSON.stringify(pixelMapImage));
      let metadata: AVSessionManager.AVMetadata = {
        assetId: assetId.length > 0 ? assetId : "fake-asset-id",
        title: title.length > 0 ? title : "  ",
        mediaImage: "https://cdnjdphoto.aikan.pdnews.cn/WapApi/800/launcher_icon.png" ,//pixelMapImage ?? ("file://" + mediaImage.id),
        artist: artist.length > 0 ? artist : "人日日报",
        duration: duration
      };

      try {
        await this.lastSession?.setAVMetadata(metadata)
        Logger.debug(TAG, `SetAVMetadata successfully`);
      } catch (err) {
        Logger.error(TAG, `Failed to set AVMetadata. Code: ${err.code}, message: ${err.message}`);
      } finally {
        pixelMapImage?.release()
      }
    }

  }

  //设置播放状态
  setSessionPlayStatus(playStatus: number) {
    let needUpdateProgress = false
    let playbackStatus = AVSessionManager.PlaybackState.PLAYBACK_STATE_PLAY
    switch (playStatus){
      case PlayerConstants.STATUS_PAUSE: {
        playbackStatus = AVSessionManager.PlaybackState.PLAYBACK_STATE_PAUSE
      } break
      case PlayerConstants.STATUS_START: {
        playbackStatus = AVSessionManager.PlaybackState.PLAYBACK_STATE_PLAY
        needUpdateProgress = true
      } break
      case PlayerConstants.STATUS_STOP: {
        playbackStatus = AVSessionManager.PlaybackState.PLAYBACK_STATE_STOP
      } break
      case PlayerConstants.STATUS_ERROR: {
        playbackStatus = AVSessionManager.PlaybackState.PLAYBACK_STATE_ERROR
      } break
      case PlayerConstants.STATUS_COMPLETION: {
        playbackStatus = AVSessionManager.PlaybackState.PLAYBACK_STATE_COMPLETED
      } break
      default: {
        playbackStatus = AVSessionManager.PlaybackState.PLAYBACK_STATE_IDLE
      } break
    }
    let playbackState: AVSessionManager.AVPlaybackState = {
      state:playbackStatus,
      // isFavorite:false
    };
    this.lastSession?.setAVPlaybackState(playbackState, (err: BusinessError) => {
      if (err) {
        Logger.error(TAG, `Failed to set AVPlaybackState. Code: ${err.code}, message: ${err.message}`);
      } else {
        Logger.debug(TAG, `SetAVPlaybackState 设置播放状态成功 ` + playStatus);
        if (needUpdateProgress) {
          this.hasSetupProgress = false
        }
      }
    });
  }

  //设置进度,单位ms
  async setSessionPlayProgress(progressDuration: number, totalDuration: number) {
    this.setSessionPlayProgressForce(progressDuration, totalDuration, false)
  }
  async setSessionPlayProgressForce(progressDuration: number, totalDuration: number, force: boolean) {
    if (totalDuration <= 0) {
      return
    }
    let newProgress = progressDuration / totalDuration
    if (force == false && Math.abs(newProgress - this.lastProgress) < 0.01) {
      return
    }
    // Logger.debug(TAG, `set progress: ` + progressDuration + " duration: " + totalDuration);
    this.lastProgress = newProgress

    if (this.lastItemAssetId != undefined) {
      this.lastItemTotalDuration = totalDuration
      await this.setSessionMetaDataWithDuration(
        this.lastItemAssetId ?? "",
        this.lastItemTitle ?? "",
        this.lastItemMediaImage!,
        this.lastItemArtist ?? "",
        totalDuration
      )
      this.lastItemAssetId = undefined
    }

    if (force == false && this.hasSetupProgress) {
      return
    }
    this.hasSetupProgress = true
    Logger.debug(TAG, `set progress: ` + DateTimeUtils.secondToTime(progressDuration / 1000)
      + " duration: " + DateTimeUtils.secondToTime(totalDuration / 1000));

    // 设置状态: 播放状态,进度位置,播放倍速,缓存的时间
    let playbackState: AVSessionManager.AVPlaybackState = {
      state: AVSessionManager.PlaybackState.PLAYBACK_STATE_PLAY, // 播放状态
      position: {
        elapsedTime: progressDuration, // 已经播放的位置,以ms为单位
        updateTime: new Date().getTime(), // 应用更新当前位置时的时间戳,以ms为单位
      },
      // duration: totalDuration,
      // speed: 1.0, // 可选,默认是1.0,播放的倍速,按照应用内支持的speed进行设置,系统不做校验
      // bufferedTime: totalDuration, // 可选,资源缓存的时间,以ms为单位
    };
    this.lastSession?.setAVPlaybackState(playbackState, (err) => {
      if (err) {
        Logger.error(TAG, `Failed to set AVPlaybackState. Code: ${err.code}, message: ${err.message}`);
      } else {
        Logger.debug(TAG, `SetAVPlaybackState successfully`);
      }
    });
  }

  // 置灰或禁用不支持的按钮
  stopUseFeatures() {
    // 取消指定session下的相关监听
    // this.lastSession?.off('playFromAssetId');
    // this.lastSession?.off('setSpeed');
    // this.lastSession?.off('setLoopMode');
    // this.lastSession?.off('toggleFavorite');
    // this.lastSession?.off('skipToQueueItem');
    // this.lastSession?.off('handleKeyEvent');
    // this.lastSession?.off('commonCommand');
    // this.lastSession?.off('rewind');
    // this.lastSession?.off('fastForward');
  }

  listenPlayEvents() {
    this.lastSession?.on('play', () => {
      Logger.debug(TAG, `on play `);
      this.avplayerController?.resume()
      this.hasSetupProgress = false
    });
    this.lastSession?.on('pause', () => {
      Logger.debug(TAG, `on pause `);
      this.avplayerController?.pause()
    });
    this.lastSession?.on('stop', () => {
      Logger.debug(TAG, `on stop `);
      this.avplayerController?.stop()
    });
    // this.lastSession?.on('playNext', () => {
    //   Logger.debug(TAG, `on playNext `);
    // });
    // this.lastSession?.on('playPrevious', () => {
    //   Logger.debug(TAG, `on playPrevious `);
    // });
    this.lastSession?.on('seek', (position: number) => {
      Logger.debug(TAG, `on seek , the time is ${position}`);
      if (this.lastItemTotalDuration == 0) {
        return
      }

      this.hasSetupProgress = true
      // 由于应用内seek可能会触发较长的缓冲等待,可以先把状态设置为 Buffering
      let playbackState: AVSessionManager.AVPlaybackState = {
        state: AVSessionManager.PlaybackState.PLAYBACK_STATE_BUFFERING, // 缓冲状态
      };
      this.lastSession?.setAVPlaybackState(playbackState, (err) => {
        if (err) {
          Logger.debug(TAG, `Failed to set AVPlaybackState. Code: ${err.code}, message: ${err.message}`);
        } else {
          Logger.debug(TAG, `SetAVPlaybackState seek buffering`);
        }
        // 应用响应seek命令,使用应用内播放器完成seek实现
        this.avplayerController?.setSeekMicroSecondsTime(position)
        this.setSessionPlayProgressForce(position, this.lastItemTotalDuration, true)
        setTimeout(() => {
          this.hasSetupProgress = false
        }, 1000)
      });
    });
  }

  // 开启后台长时任务
  startContinuousTask() {
    if (this.applyedLongTaskPlay) {
      return
    }
    let params: Record<string, string> = {
      "title": "开始后台任务",
      "content": "内容?",
      // "pushLink": pushLink,
      // "taskid": message.taskId,
      // "messageId": message.messageId,
      // "gtTransmitMsgLocalNotify": "1",
    }

    let wantAgentInfo: wantAgent.WantAgentInfo = {
      // 点击通知后,将要执行的动作列表
      // 添加需要被拉起应用的bundleName和abilityName
      wants: [
        {
          deviceId: '',
          bundleName: "com.peopledailychina.hosactivity",
          abilityName: "EntryAbility",
          // action: 'com.test.pushaction',
          // entities: [],
          // parameters:params,
        }
      ],
      // 指定点击通知栏消息后的动作是拉起ability
      actionType: wantAgent.OperationType.START_ABILITY,
      // 使用者自定义的一个私有值
      requestCode: 0,
      // 点击通知后,动作执行属性
      wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG],
    };

    // 通过wantAgent模块下getWantAgent方法获取WantAgent对象
    wantAgent.getWantAgent(wantAgentInfo).then((wantAgentObj: WantAgent) => {
      if (!this.gotContextFunc) { return }
      this.lastSession?.setLaunchAbility(wantAgentObj)
      backgroundTaskManager.startBackgroundRunning(this.gotContextFunc(),
        backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK,
        wantAgentObj).then(() => {
        Logger.debug(TAG, `Succeeded in operationing startBackgroundRunning.`);
        this.applyedLongTaskPlay = true
      }).catch((err: BusinessError) => {
        Logger.error(TAG, `Failed to operation startBackgroundRunning. Code is ${err.code}, message is ${err.message}`);
      });
    });
  }

  stopContinuousTask() {
    if (!this.gotContextFunc) { return }
    backgroundTaskManager.stopBackgroundRunning(this.gotContextFunc()).then(() => {
      Logger.debug(TAG, `Succeeded in operationing stopBackgroundRunning.`);
    }).catch((err: BusinessError) => {
      Logger.error(TAG, `Failed to operation stopBackgroundRunning. Code is ${err.code}, message is ${err.message}`);
    });
    this.applyedLongTaskPlay = false
  }

}