/*
 * 预览页面
 * EZOpenSDK
 *
 * Created by junecheng on 2024/3/14.
 * Copyright (c) 2024年 Ezviz. All rights reserved.
 */
import fs from '@ohos.file.fs'
import { fileUri } from '@kit.CoreFileKit'
import { photoAccessHelper } from '@kit.MediaLibraryKit'
import emitter from '@ohos.events.emitter'

import {
  EZCameraInfo,
  EZDeviceInfo,
  EZError,
  EZErrorCode,
  EZMessageCode,
  EZOpenSDK,
  EZPlayer,
  EZPlayerCallback,
  EZPlayerAdditionalInfoCallback,
  EZPTZAction,
  EZPTZCommand,
  EZTimeUtil,
  EZVideoLevelType,
  EZVoiceChangeType,
  EZPMPlayPrivateTokenInfo,
  EZDevicePtzAngleInfo,
  EZDeviceDetailPublicInfo,
  EZVideoQualityInfo,
  OnLoudnessListener
} from '@ohos/EZOpenSDK'

import EZToastUtil from '../../utils/EZToastUtil'
import PermissionUtil from '../../utils/PermissionUtil'
import { PreferenceUtil } from '../../utils/PreferenceUtil'
import TimeUtil from '../../utils/EZTimeUtil'
import EZMediaFileUtil from '../../utils/EZMediaFileUtil'
import EZBusinessTool from '../../businesstool/EZBusinessTool'
import EZLog from '../../utils/EZLog'
import RouterManager from '../../global/RouterManager/RouterManager'
import EmitterEvents from '../../global/EmitterEvents'
import DemoConfig from '../../global/DemoConfig'
import GlobalKit from '../../global/GlobalKit'

import { NavigationView } from '../../global/view/NavigationView'
import VerifyCodeDialog from '../../global/view/VerifyCodeDialog'
import SheetDialog from '../../global/view/SheetDialog'
import ToolBar from './view/ToolBar'
import OperateView, { EZOperateButton } from './view/OperateView'
import PTZView from './view/PTZView'
import PTZAngleView from './view/PTZAngleView'
import TalkView from './view/TalkView'
import LocalRecordView from './view/LocalRecordView'

import PlayRect from './Model/PlayRect'

@Entry
@Component
struct EZRealPlayPage {
  private readonly TAG: string = `${EZRealPlayPage.name}:`
  /// ------------- 以下为设备相关信息 -------------
  @State navTitle: string = ''
  private deviceInfo: EZDeviceInfo | null = null
  private cameraInfo: EZCameraInfo | null = null
  private cameraIndex: number = 0
  // 是否是双目设备，如果接入的设备没有双目设备，可以忽略相关逻辑
  @State isMultiChannelDevice: boolean = false
  /// ------------- 以上为设备相关信息 -------------

  /// ------------- 以下为预览播放器区域相关 -------------
  xComponentController: XComponentController = new XComponentController()
  xComponentController2: XComponentController = new XComponentController()
  previewPlayerComponentID: string = 'PreviewPlayerComponentID'
  previewPlayerComponentID2: string = 'PreviewPlayerComponentID2'
  // 预览播放器
  player: EZPlayer | null = null
  // 播放器是否在播放中
  @State isPlaying: boolean = false
  // 取流加载视图
  @State isStreamLoadingShow: boolean = false
  // 播放错误信息
  @State errorMessage: ResourceStr = ''
  // 播放错误信息状态
  @State isErrorMessageShow: boolean = false
  // 取流方式
  @State streamFetchType: string = ''
  // 解码方式
  @State streamDecodeType: string = ''
  // 封面图
  @State coverImagePath: string = ''
  // 第二个画面封面图
  @State coverImagePath2: string = ''
  // 封面图是否显示
  @State isCoverImageShow: boolean = false
  // 播放器正中央-开始播放按钮显示状态
  @State isStartPlayBtnShow: boolean = false
  // 取流详情数据
  deviceDetailPublicInfo: EZDeviceDetailPublicInfo | null = null
  // 取流开始时间
  startTime: number = 0
  /// ------------- 以上为预览播放器区域相关 -------------

  /// ------------- 以下为对讲播放器区域相关 -------------
  // 对讲播放器
  talkPlayer: EZPlayer | null = null
  // 是否在启动对讲，开启预览的时候需要设置为false
  isStartingTalk: boolean = false
  // 选择的是当前设备对讲还是nvr下的通道对讲。true:当前设备对讲 false:nvr下的通道对讲
  isDeviceTalkback: boolean = true
  // 对讲启动过程动画
  @State isTalkbackLoadingShow: boolean = false
  // 是否是半双工对讲
  @State isHalfDuplex: boolean = false
  // 本地音频音量大小
  @State voiceTalkLoudness: number = -90
  /// ------------- 以上为对讲播放器区域相关 -------------

  /// ------------- 以下为录像相关 -------------
  // 录像计时视图显示状态
  @State isLocalRecordViewShow: boolean = false
  // 录像中-橙色小圆点显示状态
  @State isLocalRecordCircleShow: boolean = false
  // 录像录像时长
  @State localRecordDuration: string = ''
  // 录像定时器ID，刷新录像时间
  localRecordTimerIntervalID: number = 0
  // 录像总秒数
  localRecordSeconds: number = 0
  /// ------------- 以上为录像相关 -------------

  /// ------------- 以下为工具栏区域相关 -------------
  // 播放器声音是否开启，默认开启
  @State isOpenSound: boolean = true
  // 当前清晰度文本
  @State currentQualityTitle: ResourceStr = ''
  // 码流定时器ID，刷新每秒接收到的码流文件大小
  refreshPlayUITimerIntervalID: number = 0
  // 播放接收到的码流总大小
  streamPrevious: number = 0
  // 每秒接收到的码流大小
  @State streamFlowValue: string = ''
  // 设备默认清晰度数组
  videoQualityInfos: Array<EZVideoQualityInfo> =
    [new EZVideoQualityInfo(getContext().resourceManager.getStringSync($r('app.string.device_quality_low')),
      EZVideoLevelType.EZVideoLevelLow),
      new EZVideoQualityInfo(getContext().resourceManager.getStringSync($r('app.string.device_quality_median')),
        EZVideoLevelType.EZVideoLevelMiddle),
      new EZVideoQualityInfo(getContext().resourceManager.getStringSync($r('app.string.device_quality_high')),
        EZVideoLevelType.EZVideoLevelHigh)]
  // 设备清晰度数组，清晰度选择框数据源
  videoQualitys: Array<string> = []
  /// ------------- 以上为工具栏区域相关 -------------

  /// ------------- 以下为操控区域按钮是否可用相关 -------------
  @State ptzAngeleInfo: EZDevicePtzAngleInfo | null = null
  // 云台按下时的指令，Left | Right 时显示水平角度尺，Up | Down 时显示垂直角度尺，-1时隐藏
  @State touchDownPtzCommand: EZPTZCommand | -1 = -1
  // 是否支持云台
  @State isSupportPTZ: boolean = false
  // 是否支持对讲
  @State isSupportTalk: boolean = false
  // 是否支持截图
  @State isSupportCapture: boolean = false
  // 是否支持录像
  @State isSupportLocalRecord: boolean = false
  // 云台控制视图是否显示
  @State isPtzViewShow: boolean = false
  // 对讲控制视图是否显示
  @State isTalkViewShow: boolean = false
  // 是否在录像中
  @State isLocalRecording: boolean = false
  // 本地录像文件路径
  localRecordFilePath: string = ''
  // 双目设备第二个轨道录像文件路径
  localRecordFilePath2: string = ''

  /// ------------- 以上为操控区域按钮是否可用相关 -------------


  // ------------------------ 生命周期 ------------------------

  aboutToAppear() {
    this.initData()// 数据初始化
    this.initView()// 视图初始化设置
    this.initEZPlayer()// 播放器初始化
  }

  async aboutToDisappear(): Promise<void> {
    await EZOpenSDK.releasePlayer(this.player)
    await EZOpenSDK.releasePlayer(this.talkPlayer)
  }

  async onPageShow(): Promise<void> {
    // 开始播放；返回前台或者从其他页面跳转回来后，也需要重新取流
    await this.playerStartRealPlay()
  }

  async onPageHide(): Promise<void> {
    // 退到后台或者跳转到其他页面后，停止取流
    await this.playerStopRealPlay()
    if (this.isTalkViewShow) {
      await this.talkViewCloseAction()
    }
  }

  // ------------------------ 播放器回调函数 player callback ------------------------

  private playerCallback: EZPlayerCallback = {
    /*
     * 播放器播放成功消息回调
     */
    didReceivedMessage: async (messageCode: number) => {
      EZLog.debug(this.TAG, `player didReceivedMessage messageCode: ${messageCode}`)
      if (messageCode == EZMessageCode.PLAYER_REALPLAY_START) {
        // 1.视图状态设置
        await this.playerPlayResult(true)
        // 2.设置解码状态 & 取流方式UI
        await this.showDecodeType()
        await this.showStreamFetchType()
        this.player?.getDeviceDetailInfo((info) => {
          this.deviceDetailPublicInfo = info
        })
        // 3.声音设置
        if (!this.isOpenSound) {
          await this.player?.closeSound()
        }
      } else if (messageCode == EZMessageCode.PLAYER_VIDEO_SIZE_CHANGED) {
        // 获取画面宽高，电子放大的时候需要用
        this.mVideoWidth = await this.player?.getVideoWidth() ?? 0
        this.mVideoHeight = await this.player?.getVideoHeight() ?? 0
        this.zoomRect.set(0, 0, this.mVideoWidth, this.mVideoHeight)
        this.zoomRect2.set(0, 0, this.mVideoWidth, this.mVideoHeight)
      } else if (messageCode == EZMessageCode.PLAYER_NET_CHANGED) {
        await this.playerStopRealPlay()
        await this.playerStartRealPlay()
      }
    },
    /*
     * 播放器播放失败消息回调
     */
    didPlayFailed: async (error: EZError) => {
      await this.playerPlayResult(false)
      this.isStartPlayBtnShow = false

      if (error.code == EZErrorCode.EZ_SDK_NEED_VALIDATECODE) {
        this.showSetPassword()
      } else if (error.code == EZErrorCode.EZ_SDK_VALIDATECODE_NOT_MATCH) {
        this.showRetry()
      } else {
        EZToastUtil.showToast(`error code is ${error.code}`)
        this.errorMessage = $r('app.string.device_play_fail', `${error.code}`)
        this.isErrorMessageShow = true
        this.isPlaying = false
      }
    }
  }
  private talkPlayerCallback: EZPlayerCallback = {
    /*
     * 对讲成功消息回调
     */
    didReceivedMessage: async (messageCode: number) => {
      EZLog.debug(this.TAG, `talkPlayer didReceivedMessage messageCode: ${messageCode}`)
      if (messageCode == EZMessageCode.PLAYER_VOICE_TALK_START) {
        this.isTalkbackLoadingShow = false
        this.isTalkViewShow = true
        let isSupportTalk = EZBusinessTool.isSupportTalk(this.deviceInfo, this.cameraInfo)
        this.isHalfDuplex = isSupportTalk == 3 // 3 为半双工
        /**
         非国标设备需要关闭播放器player的声音，设备和手机对讲都是通过talkPlayer来传输音频数据的，所以需要关闭player播放器的声音，否则画面码流和对讲码流中同时播放声音，导致回音(萤石设备都是萤石协议，属于非国标设备)
         国标设备不能关闭。国标设备taklPlayer只负责采集手机端的声音，设备端的声音是通过player来播放的 */
        if (this.deviceInfo?.devProtoEnum == 0) {
          await this.talkPlayer?.closeSound()
        }
      } else if (messageCode == EZMessageCode.PLAYER_VOICE_TALK_END) {
        // 对讲结束后把player播放器的声音开回来
        if (this.isOpenSound) {
          await this.player?.openSound()
        }
      } else {
        EZToastUtil.showToast(`didReceivedMessage messageCode：${messageCode}`)
      }
    },
    /*
     * 对讲失败消息回调
     */
    didPlayFailed: (error: EZError) => {
      this.isTalkbackLoadingShow = false
      this.isTalkViewShow = false
      if (error.code == EZErrorCode.EZ_SDK_NOT_SUPPORT_TALK) {
        let msg = getContext().resourceManager.getStringSync($r('app.string.not_support_talk'))
        EZToastUtil.showToast(`${msg}(${error.code})`)
      } else {
        EZToastUtil.showToast(`talkPlayer error code is ${error.code}`)
      }
    }
  }
  private talkPlayerVoiceTalkLoudnessCallback: OnLoudnessListener = {
    onReportLoudness: (loudness: number): void => {
      EZLog.info(this.TAG, `VoiceTalkLoudness--->${loudness}`)
      this.voiceTalkLoudness = loudness
    }
  }
  private playerAdditionalInfoCallback: EZPlayerAdditionalInfoCallback = {
    /*
   * 设备云台角度数据回调
   */
    devicePtzAngleInfo: (info: EZDevicePtzAngleInfo) => {
      this.ptzAngeleInfo = info
    },
    /*
     * 取流数据中获取开门token回调
     */
    playPrivateTokenInfo: (tokenInfo: EZPMPlayPrivateTokenInfo) => {
      EZLog.info(this.TAG, `token--->${tokenInfo.token}`)
    }
  }
  // ------------------------ Actions ------------------------

  /** 暂停/播放Action */
  playAction = async () => {
    if (this.isPlaying) {
      // 暂停前获取最后一帧作为封面
      this.coverImagePath = await this.player?.capturePicture() ?? ''
      this.coverImagePath2 = await this.player?.capturePicture(1) ?? ''
      await this.playerPlayResult(false)
    } else {
      this.isStartPlayBtnShow = false
      await this.playerStartRealPlay()
    }
  }
  /** 点击声音按钮 */
  voiceAction = async () => {
    if (this.isOpenSound) {
      await this.player?.closeSound()
    } else {
      await this.player?.openSound()
    }
    this.isOpenSound = !this.isOpenSound
  }
  /** 清晰度列表显示Action */
  qualityAction = () => {
    if (this.deviceDetailPublicInfo && this.deviceDetailPublicInfo.videoQualityInfos) {
      this.videoQualityInfos = this.deviceDetailPublicInfo.videoQualityInfos
    }

    this.videoQualitys = []
    for (let i = 0; i < this.videoQualityInfos.length; i++) {
      this.videoQualitys.push(this.videoQualityInfos[i].videoQualityName)
    }
    this.selectVideoLevelDialogController.open()
  }
  // 选择清晰度类型弹窗
  selectVideoLevelDialogController: CustomDialogController = new CustomDialogController({
    customStyle: true,
    alignment: DialogAlignment.Bottom,
    builder: SheetDialog({
      headerTitle: $r('app.string.device_quality_select_tip'),
      titles: this.videoQualitys,
      confirmClick: async (index) => {
        let videoLevelType = this.videoQualityInfos[index].videoLevel as EZVideoLevelType
        this.qualitySelectedAction(videoLevelType)
      }
    }),
    autoCancel: true,
    offset: { dx: 0, dy: -25 }
  })
  /** 清晰度选择Action */
  qualitySelectedAction = (videoLevelType: EZVideoLevelType) => {
    this.isTalkViewShow = false
    this.isStreamLoadingShow = true
    // 如果是正在播放时调用该接口，设置清晰度成功以后必须让EZPlayer调用stopRealPlay再调用startRealPlay重新取流才成完成画面清晰度的切换
    EZOpenSDK.setVideoLevel(this.cameraInfo!.deviceSerial, this.cameraInfo!.cameraNo, videoLevelType, async (error) => {
      if (error) {
        EZToastUtil.showToast(`${error.message}`)
        return
      }
      await this.playerStopRealPlay()

      // 刷新缓存数据
      this.cameraInfo!.videoLevel = videoLevelType
      emitter.emit({ eventId: EmitterEvents.EZDeviceListPageRefresh })
      this.currentQualityTitle = EZBusinessTool.getDeviceQualityTitle(videoLevelType)

      await this.playerStartRealPlay()
    })
  }
  /** 竖屏->横屏Action */
  largeAction = () => {
    EZToastUtil.showToast('Demo暂未实现')
  }
  /** 控制按钮Action：云台、对讲、截图、录像 */
  operateButtonAction = async (type: EZOperateButton, isRecording?: boolean) => {
    switch (type) {
      case EZOperateButton.EZOperateButton_PTZ:
        this.isPtzViewShow = true
        break
      case EZOperateButton.EZOperateButton_Talk:
        await this.talkAction()
        break
      case EZOperateButton.EZOperateButton_Capture:
        await this.captureAction()
        break
      case EZOperateButton.EZOperateButton_Record:
        isRecording ? await this.startLocalRecordAction() : await this.stopLocalRecordAction()
        this.isLocalRecordViewShow = isRecording!
        break
    }
  }
  /** 云台控制-手指按下Action */
  ptzControlButtonTouchDownAction = (command: EZPTZCommand) => {
    this.touchDownPtzCommand = command
    EZOpenSDK.controlPTZEx(this.cameraInfo!.deviceSerial, this.cameraInfo!.cameraNo,
      command, EZPTZAction.EZPTZActionStart, 2, (error) => {
        EZLog.error(this.TAG, `controlPTZEx start error is ${error}`)
      })
  }
  /** 云台控制-手指抬起Action */
  ptzControlButtonTouchUpAction = (command: EZPTZCommand) => {
    EZOpenSDK.controlPTZEx(this.cameraInfo!.deviceSerial, this.cameraInfo!.cameraNo,
      command, EZPTZAction.EZPTZActionStop, 2, (error) => {
        EZLog.error(this.TAG, `controlPTZEx stop error is ${error}`)
        this.touchDownPtzCommand = -1
      })
  }

  /** 对讲Action */
  async talkAction() {
    let p = await PermissionUtil.checkAndRequestPermission(getContext(), ['ohos.permission.MICROPHONE'])
    if (p.valueOf()) {
      // 第一次请求麦克风权限时，点了‘允许’后，对讲dialog无法显示，需要延时一会
      setTimeout(() => {
        this.selectTalkbackItemsDialogController.open()
      }, 100)
    } else {
      EZLog.error(this.TAG, '获取麦克风权限失败')
      PermissionUtil.showGuideToSettingsDialog(getContext(), '打开设置',
        '由于对讲需要使用到麦克风,请在设置页打开麦克风权限后进行使用')
    }
  }

  /** 半双工对讲Action */
  halfDuplexTalkbackAction = (isHalfDuplexSpeakingStatus: boolean) => {
    this.talkPlayer?.audioTalkPressed(isHalfDuplexSpeakingStatus)
  }
  /** 对讲变声Action */
  talkVoiceChangeAction = (voiceChangeType: EZVoiceChangeType) => {
    EZToastUtil.showToast(`对讲变声模式：${voiceChangeType}`)
  }
  /** 对讲-页面关闭Action */
  talkViewCloseAction = async () => {
    await this.talkPlayer?.stopVoiceTalk()
  }
  // 选择对讲类型弹窗
  selectTalkbackItemsDialogController: CustomDialogController = new CustomDialogController({
    customStyle: true,
    alignment: DialogAlignment.Bottom,
    builder: SheetDialog({
      headerTitle: $r('app.string.talk_select_msg'),
      titles: [$r('app.string.device_talkback_current_device'), $r('app.string.device_talkback_nvr_channel_device')],
      confirmClick: async (index) => {
        this.isTalkbackLoadingShow = true
        this.isStartingTalk = true
        if (EZOpenSDK.isEnableSDKWithTKToken()) {
          this.talkPlayer?.setStreamToken(GlobalKit.getInstance().talkStreamToken)
        }
        switch (index) {
          case 0:
            this.isDeviceTalkback = true
            await this.talkPlayer?.startVoiceTalk()
            break
          case 1:
            this.isDeviceTalkback = false
            await this.talkPlayer?.startVoiceTalkNeedVoiceChannel(true)
            break
        }
      }
    }),
    autoCancel: true,
    offset: { dx: 0, dy: -25 }
  })

  /** 截图Action，截图保存到应用沙盒中，不需要申请权限；如果要将截图保存到相册，则需要申请WRITE_IMAGEVIDEO权限 */
  async captureAction() {
    let picPath = await this.player?.capturePicture()
    let r = await this.requestWriteImageVideoPermission()
    if (!r) {
      EZLog.error(`captureAction`, 'not apply WRITE_IMAGEVIDEO Permission')
      return
    }
    if (picPath) {
      await EZMediaFileUtil.saveMediaToAlbum(picPath, photoAccessHelper.PhotoType.IMAGE)
      await fs.unlink(picPath)
    }
    if (this.isMultiChannelDevice) {// 双目设备第二个播放器截图
      let picPath2 = await this.player?.capturePicture(1)
      if (picPath2) {
        await EZMediaFileUtil.saveMediaToAlbum(picPath2, photoAccessHelper.PhotoType.IMAGE)
        await fs.unlink(picPath)
      }
    }
  }

  /** 开始本地录像Action */
  async startLocalRecordAction() {
    // 文件目录、文件创建
    let dirPath = DemoConfig.PATH_LocalRecord
    if (!fs.accessSync(dirPath)) {
      await fs.mkdir(dirPath)
    }
    let fileName = EZTimeUtil.dateFormat(new Date(), 'yyyyMMddHHmmss')
    this.localRecordFilePath = `${dirPath}/${fileName}.mp4`
    await fs.open(this.localRecordFilePath, fs.OpenMode.CREATE)
    if (this.isMultiChannelDevice) {// 双目设备第二个画面转封装文件路径会拼接上_1
      this.localRecordFilePath2 = `${dirPath}/${fileName}_1.mp4`
      await fs.open(this.localRecordFilePath2, fs.OpenMode.CREATE)
    }
    // 定时器开启
    this.localRecordDuration = '00:00'
    this.localRecordSeconds = 0
    clearInterval(this.localRecordTimerIntervalID)
    this.localRecordTimerIntervalID = setInterval(() => {
      let currentTime = ++this.localRecordSeconds
      this.isLocalRecordCircleShow = currentTime % 2 != 0
      this.localRecordDuration = TimeUtil.formatSeconds(currentTime)
    }, 1000)
    // 开始录像
    await this.player?.startLocalRecordWithPathExt(this.localRecordFilePath)
  }

  /** 结束本地录像Action */
  async stopLocalRecordAction() {
    await this.player?.stopLocalRecordExt(async (result) => {
      if (result) {
        await EZMediaFileUtil.saveMediaToAlbum(this.localRecordFilePath, photoAccessHelper.PhotoType.VIDEO)
        await fs.unlink(this.localRecordFilePath)
        if (this.isMultiChannelDevice) {
          await EZMediaFileUtil.saveMediaToAlbum(this.localRecordFilePath2, photoAccessHelper.PhotoType.VIDEO)
          await fs.unlink(this.localRecordFilePath2)
        }
      }
    })
  }

  // ------------------------ ValidateCode Methods 验证码检验 ------------------------

  /*
   * 显示输入密码alert
   */
  showSetPassword() {
    this.verifyCodeDialogController.open()
  }

  /*
   * 显示输入密码alert
   */
  showRetry() {
    this.verifyCodeDialogController.open()
  }

  @State verifyCodeDialogInputContent: string = ''
  verifyCodeDialogController: CustomDialogController = new CustomDialogController({
    customStyle: true,
    builder: VerifyCodeDialog({
      deviceSerial: this.cameraInfo?.deviceSerial,
      title: $r('app.string.setting_device_security_check'),
      message: $r('app.string.setting_input_code_plz'),
      placeholder: $r('app.string.device_input_vierify_code'),
      inputContent: this.verifyCodeDialogInputContent,
      confirmClick: async () => {
        this.player?.setPlayVerifyCode(this.verifyCodeDialogInputContent)
        await this.playerStartRealPlay()
      },
      cancelClick: () => {

      }
    }),
    alignment: DialogAlignment.Center,
    autoCancel: false
  })

  // ------------------------ 自定义方法 ------------------------

  initEZPlayer() {
    // 1、创建预览播放器
    this.player = EZOpenSDK.createPlayer(this.cameraInfo!.deviceSerial, this.cameraInfo!.cameraNo)
    // 2.配置播放器：设置回调，设置验证码，设置播放窗口等配置
    this.player.setPlayerCallback(this.playerCallback)
    this.player.setPlayerAdditionalInfoCallback(this.playerAdditionalInfoCallback)
    // 判断设备是否加密，加密就获取设备验证码填入到播放器的验证码接口里
    if (this.deviceInfo!.isEncrypt) {
      let verifyCode: string = PreferenceUtil.getString(getContext(this), this.cameraInfo!.deviceSerial) as string
      this.player.setPlayVerifyCode(verifyCode)
    }
    this.player?.setPlayerView(this.previewPlayerComponentID)
    if (this.isMultiChannelDevice) {
      this.player?.setPlayerView(this.previewPlayerComponentID2, 1)
    }
    // 3.创建对讲播放器
    this.talkPlayer = EZOpenSDK.createPlayer(this.cameraInfo!.deviceSerial, this.cameraInfo!.cameraNo)
    this.talkPlayer.setPlayerCallback(this.talkPlayerCallback)
    this.talkPlayer.setVoiceTalkLoudnessCallback(this.talkPlayerVoiceTalkLoudnessCallback, 0.15)// 设置对讲本地采集音量大小回调
  }

  /** 播放器开始播放 */
  async playerStartRealPlay() {
    this.isStartingTalk = false
    this.isStreamLoadingShow = true
    this.isErrorMessageShow = false
    if (EZOpenSDK.isEnableSDKWithTKToken()) {
      this.player?.setStreamToken(GlobalKit.getInstance().previewStreamToken)
    }
    this.startTime = new Date().getTime()
    await this.player?.startRealPlay()
    clearInterval(this.refreshPlayUITimerIntervalID)
    this.refreshPlayUITimerIntervalID = setInterval(async () => {
      await this.showStreamFlow()
      await this.showStreamFetchType()
    }, 1000)
  }

  /** 播放器停止播放 */
  async playerStopRealPlay() {
    // 缩放比例重置
    this.baseScale = 1
    this.realScale = 1
    clearInterval(this.refreshPlayUITimerIntervalID)
    if (this.isLocalRecordViewShow) {
      clearInterval(this.localRecordTimerIntervalID)
      await this.player?.stopLocalRecordExt((result) => {

      })
    }
    await this.player?.stopRealPlay()
  }

  /** 播放器播放 成功/失败 */
  async playerPlayResult(isSuccess: boolean) {
    if (isSuccess && this.startTime > 0) {
      let duration: number = new Date().getTime() - this.startTime
      EZToastUtil.showToast(`cost: ${duration} ms`)
      this.startTime = 0
    }
    this.isStreamLoadingShow = false
    this.isErrorMessageShow = !isSuccess
    this.isSupportPTZ = isSuccess && EZBusinessTool.isSupportPTZ(this.deviceInfo, this.cameraInfo)
    this.isSupportTalk = EZBusinessTool.isSupportTalk(this.deviceInfo, this.cameraInfo) > 0 // 未在预览时也能发起对讲
    this.isSupportCapture = isSuccess // 截图按钮
    this.isSupportLocalRecord = isSuccess // 录像按钮
    this.isCoverImageShow = !isSuccess // 封面图
    this.isPlaying = isSuccess
    this.isStartPlayBtnShow = !isSuccess
    if (!isSuccess) {
      await this.playerStopRealPlay()
    } else {

    }
  }

  /** 显示预览每秒接收到的码流大小 */
  async showStreamFlow() {
    let streamCurrent: number = await this.player?.getStreamFlow() as number
    let dif = (streamCurrent - this.streamPrevious) / 1024
    this.streamPrevious = streamCurrent
    if (dif < 0) {
      return
    }
    this.streamFlowValue = dif > 1024 ? `${(dif / 1024).toFixed(1)} MB/s` : `${(dif).toFixed(1)} k/s`
  }

  /** 显示取流方式 */
  async showStreamFetchType() {
    let type = await this.player?.getStreamFetchType() as number
    if (type >= 0) {
      this.streamFetchType = EZBusinessTool.getStreamType(type)
    }
  }

  /** 显示解码方式，软解或硬解 */
  async showDecodeType() {
    let typeString = await this.player?.getHDPriorityStatus() == 2 ? 'hard' : 'soft'
    this.streamDecodeType = `decode type: ${typeString}`
  }

  initData() {
    const routerParams = RouterManager.getParams() as Record<string, object>
    this.deviceInfo = routerParams.deviceInfo as EZDeviceInfo
    this.cameraIndex = routerParams.cameraIndex ? parseInt(`${routerParams.cameraIndex}`) : 0
    let array = this.deviceInfo.cameraInfo.length > 0 ? this.deviceInfo.cameraInfo : this.deviceInfo.subDeviceInfo
    this.cameraInfo = array[this.cameraIndex]
    this.isMultiChannelDevice = EZBusinessTool.isSupportMultiChannel(this.deviceInfo, this.cameraInfo)
  }

  initView() {
    this.navTitle = this.cameraInfo!.cameraName
    if (this.isMultiChannelDevice) {
      this.navTitle = this.deviceInfo?.deviceName ?
        this.deviceInfo?.deviceName : `${this.deviceInfo?.category}(${this.deviceInfo?.deviceSerial})`
    }
    this.currentQualityTitle = EZBusinessTool.getDeviceQualityTitle(this.cameraInfo!.videoLevel)
  }

  // 申请相册写入权限
  async requestWriteImageVideoPermission(): Promise<boolean> {
    let p = await PermissionUtil.checkAndRequestPermission(getContext(), ['ohos.permission.WRITE_IMAGEVIDEO'])
    if (p.valueOf()) {
      // 用户授权，可以继续访问目标操作
      EZLog.info(this.TAG, `requestWriteImageVideoPermission`, 'Succeeded in getting permissions.')
    } else {
      EZLog.error(this.TAG, `requestWriteImageVideoPermission:获取相册写入权限失败`)
      PermissionUtil.showGuideToSettingsDialog(getContext(), "打开设置",
        '由于将录像下载到您的相册,请在设置页打开`图片与视频`权限后进行使用')
    }
    return p
  }

  // ------------------------ 电子放大 ------------------------

  // 视频宽高
  private mVideoWidth: number = 0
  private mVideoHeight: number = 0
  // 基准放大比例
  private baseScale: number = 1
  private baseScale2: number = 1
  // 真实放大比例
  private realScale: number = 1
  private realScale2: number = 1
  // 最大放大比例
  private maxScale: number = 4
  private lastOffsetX: number = 0
  private lastOffsetY: number = 0
  private lastOffsetX2: number = 0
  private lastOffsetY2: number = 0
  // 放大区域
  private zoomRect: PlayRect = new PlayRect(0, 0, 0, 0)
  private zoomRect2: PlayRect = new PlayRect(0, 0, 0, 0)

  async zoomScale(scale: number, centerX: number, centerY: number) {
    if (scale > this.maxScale) {
      scale = this.maxScale
    }
    this.realScale = scale

    EZLog.info(this.TAG, `scale=${scale} centerX=${centerX} centerY=${centerY}`)

    if (scale == 1) {
      this.zoomRect?.set(0, 0, this.mVideoWidth, this.mVideoHeight)
    }
    if (scale == 1) {
      await this.player?.setDisplayRegion(-1, -1, -1, -1)
    } else {
      let surfaceRect = this.xComponentController.getXComponentSurfaceRect()
      const videoCenterX = this.zoomRect.left + centerX! * this.zoomRect.width() / surfaceRect.surfaceWidth
      const videoCenterY = this.zoomRect.top + centerY! * this.zoomRect.height() / surfaceRect.surfaceHeight

      const centerXRatio = videoCenterX / this.mVideoWidth
      const centerYRatio = videoCenterY / this.mVideoHeight

      const displayWidth = this.mVideoWidth / scale
      const displayHeight = this.mVideoHeight / scale

      this.zoomRect.left += (this.zoomRect.width() - displayWidth) * centerXRatio
      this.zoomRect.top += (this.zoomRect.height() - displayHeight) * centerYRatio
      this.zoomRect.right = this.zoomRect.left + displayWidth
      this.zoomRect.bottom = this.zoomRect.top + displayHeight

      this.judgeRect(this.zoomRect)
      EZLog.info(this.TAG, `scale=${scale} rect=${JSON.stringify(this.zoomRect)}`)

      const rect = new PlayRect(Math.round(this.zoomRect.left), Math.round(this.zoomRect.top),
        Math.round(this.zoomRect.right), Math.round(this.zoomRect.bottom))
      await this.player?.setDisplayRegion(rect.left, rect.top, rect.right, rect.bottom)
    }
  }

  async zoomMove(deltaX: number, deltaY: number) {
    let surfaceRect = this.xComponentController.getXComponentSurfaceRect()
    const videoDeltaX = -deltaX * this.zoomRect.width() / surfaceRect.surfaceWidth
    const videoDeltaY = -deltaY * this.zoomRect.height() / surfaceRect.surfaceHeight
    this.zoomRect.offset(videoDeltaX, videoDeltaY);

    this.judgeRect(this.zoomRect)
    EZLog.info(this.TAG, `zoomMove dx=${deltaX} dy=${deltaY} rect=${JSON.stringify(this.zoomRect)}`)

    const rect = new PlayRect(Math.round(this.zoomRect.left), Math.round(this.zoomRect.top),
      Math.round(this.zoomRect.right), Math.round(this.zoomRect.bottom))
    await this.player?.setDisplayRegion(rect.left, rect.top, rect.right, rect.bottom)
  }

  async zoomScale2(scale: number, centerX: number, centerY: number) {
    if (scale > this.maxScale) {
      scale = this.maxScale
    }
    this.realScale2 = scale

    EZLog.info(this.TAG, `scale=${scale} centerX=${centerX} centerY=${centerY}`)

    if (scale == 1) {
      this.zoomRect2?.set(0, 0, this.mVideoWidth, this.mVideoHeight)
    }
    if (scale == 1) {
      await this.player?.setDisplayRegion(-1, -1, -1, -1, 1)
    } else {
      let surfaceRect = this.xComponentController2.getXComponentSurfaceRect()
      const videoCenterX = this.zoomRect2.left + centerX! * this.zoomRect2.width() / surfaceRect.surfaceWidth
      const videoCenterY = this.zoomRect2.top + centerY! * this.zoomRect2.height() / surfaceRect.surfaceHeight

      const centerXRatio = videoCenterX / this.mVideoWidth
      const centerYRatio = videoCenterY / this.mVideoHeight

      const displayWidth = this.mVideoWidth / scale
      const displayHeight = this.mVideoHeight / scale

      this.zoomRect2.left += (this.zoomRect2.width() - displayWidth) * centerXRatio
      this.zoomRect2.top += (this.zoomRect2.height() - displayHeight) * centerYRatio
      this.zoomRect2.right = this.zoomRect2.left + displayWidth
      this.zoomRect2.bottom = this.zoomRect2.top + displayHeight

      this.judgeRect(this.zoomRect2)
      EZLog.info(this.TAG, `scale=${scale} rect=${JSON.stringify(this.zoomRect2)}`)

      const rect = new PlayRect(Math.round(this.zoomRect2.left), Math.round(this.zoomRect2.top),
        Math.round(this.zoomRect2.right), Math.round(this.zoomRect2.bottom))
      await this.player?.setDisplayRegion(rect.left, rect.top, rect.right, rect.bottom, 1)
    }
  }

  async zoomMove2(deltaX: number, deltaY: number) {
    let surfaceRect = this.xComponentController2.getXComponentSurfaceRect()
    const videoDeltaX = -deltaX * this.zoomRect2.width() / surfaceRect.surfaceWidth
    const videoDeltaY = -deltaY * this.zoomRect2.height() / surfaceRect.surfaceHeight
    this.zoomRect2.offset(videoDeltaX, videoDeltaY);

    this.judgeRect(this.zoomRect2)
    EZLog.info(this.TAG, `zoomMove dx=${deltaX} dy=${deltaY} rect=${JSON.stringify(this.zoomRect2)}`)

    const rect = new PlayRect(Math.round(this.zoomRect2.left), Math.round(this.zoomRect2.top),
      Math.round(this.zoomRect2.right), Math.round(this.zoomRect2.bottom))
    await this.player?.setDisplayRegion(rect.left, rect.top, rect.right, rect.bottom, 1)
  }

  /**
   * 电子放大，边际计算时进行校正
   */
  private judgeRect(rect: PlayRect) {
    let offsetX = 0
    let offsetY = 0
    if (rect.left < 0) {
      offsetX = Math.abs(rect.left)
    }
    if (rect.top < 0) {
      offsetY = Math.abs(rect.top)
    }
    if (rect.right > this.mVideoWidth) {
      offsetX = this.mVideoWidth - rect.right
    }
    if (rect.bottom > this.mVideoHeight) {
      offsetY = this.mVideoHeight - rect.bottom
    }
    rect.offset(offsetX, offsetY)
  }

  // ------------------------ UI ------------------------

  build() {
    Flex({ direction: FlexDirection.Column }) {
      NavigationView({ title: this.navTitle })
      RelativeContainer() {
        // 播放器视图
        this.RealPlayView()
        // 工具栏视图：开始/暂停、声音、清晰度、码流大小
        ToolBar({
          isPlaying: this.isPlaying,
          isOpenSound: this.isOpenSound,
          qualityTitle: this.currentQualityTitle,
          streamFlowValue: this.streamFlowValue,
          onPlayClick: this.playAction,
          onVoiceClick: this.voiceAction,
          onQualityClick: this.qualityAction,
          onLargeClick: this.largeAction
        })
        // 录像录像计时视图
        LocalRecordView({
          isLocalRecordViewShow: this.isLocalRecordViewShow,
          isLocalRecordCircleShow: this.isLocalRecordCircleShow,
          localRecordDuration: this.localRecordDuration
        })
        // 操控视图：云台、对讲、截图、录像
        OperateView({
          isSupportPTZ: this.isSupportPTZ,
          isSupportTalk: this.isSupportTalk,
          isSupportCapture: this.isSupportCapture,
          isSupportLocalRecord: this.isSupportLocalRecord,
          isLocalRecording: this.isLocalRecording,
          onOperateClick: this.operateButtonAction
        })
        // 云台控制视图
        PTZView({
          isPtzViewShow: this.isPtzViewShow,
          onTouchDown: this.ptzControlButtonTouchDownAction,
          onTouchUp: this.ptzControlButtonTouchUpAction
        })
        // 云台转动角度比例尺
        PTZAngleView({
          ptzAngele: this.ptzAngeleInfo,
          touchDownPtzCommand: this.touchDownPtzCommand
        })
        // 对讲加载进度
        LoadingProgress().width(40).aspectRatio(1)
          .visibility(this.isTalkbackLoadingShow ? Visibility.Visible : Visibility.Hidden)
          .alignRules({
            center: { 'anchor': '__container__', align: VerticalAlign.Bottom },
            middle: { 'anchor': '__container__', align: HorizontalAlign.Center }
          })
        // 对讲视图
        TalkView({
          isTalkViewShow: this.isTalkViewShow,
          isHalfDuplex: this.isHalfDuplex,
          voiceTalkLoudness: this.voiceTalkLoudness,
          onHalfDuplexTalkClick: this.halfDuplexTalkbackAction,
          onTalkVoiceChangeClick: this.talkVoiceChangeAction,
          onTalkViewCloseClick: this.talkViewCloseAction
        })
      }
      .id('content_relative_view')
    }
  }

  /** 播放器视图 */
  @Builder
  RealPlayView() {
    RelativeContainer() {
      // 播放器视图
      XComponent({
        id: this.previewPlayerComponentID,
        type: XComponentType.SURFACE,
        controller: this.xComponentController
      })
        .id('XComponent')
        .onLoad(() => {
          // 设置播放窗口
          this.xComponentController.setXComponentSurfaceSize({ surfaceWidth: 1920, surfaceHeight: 1080 })
          this.player?.setPlayerView(this.xComponentController.getXComponentSurfaceId())
        })
        .width('100%')
        .aspectRatio(16 / 9)
        .backgroundColor(Color.Black)
        .gesture(
          GestureGroup(GestureMode.Parallel,
            PinchGesture({ fingers: 2 })
              .onActionStart((event: GestureEvent) => {
                this.baseScale = this.realScale
              })
              .onActionUpdate(async (event: GestureEvent) => {
                let scale = this.baseScale * event.scale
                let centerX = vp2px(event.pinchCenterX)
                let centerY = vp2px(event.pinchCenterY)

                await this.zoomScale(scale, centerX, centerY)
              })
              .onActionEnd((event: GestureEvent) => {

              }),
            PanGesture()
              .onActionStart((event: GestureEvent) => {
                this.lastOffsetX = 0
                this.lastOffsetY = 0
              })
              .onActionUpdate(async (event: GestureEvent) => {
                const deltaX = vp2px(event.offsetX - this.lastOffsetX)
                const deltaY = vp2px(event.offsetY - this.lastOffsetY)
                await this.zoomMove(deltaX, deltaY)

                this.lastOffsetX = event.offsetX
                this.lastOffsetY = event.offsetY
              })
              .onActionEnd((event: GestureEvent) => {

              })))
      // 双目设备两个播放器之间的分割线
      Row().height(this.isMultiChannelDevice ? 5 : 0).backgroundColor(Color.Black).id('playerViewDividLine')
        .alignRules({
          top: { 'anchor': 'XComponent', align: VerticalAlign.Bottom },
          left: { 'anchor': 'XComponent', align: HorizontalAlign.Start },
          right: { 'anchor': 'XComponent', align: HorizontalAlign.End }
        })
      // 双目设备第二个播放器视图
      if (this.isMultiChannelDevice) {
        XComponent({
          id: this.previewPlayerComponentID2,
          type: XComponentType.SURFACE,
          controller: this.xComponentController2
        })
          .id('XComponent2')
          .onLoad(() => {
            // 设置播放窗口
            this.xComponentController2.setXComponentSurfaceSize({ surfaceWidth: 1920, surfaceHeight: 1080 })
            this.player?.setPlayerView(this.xComponentController2.getXComponentSurfaceId(), 1)
          })
          .aspectRatio(16 / 9)
          .backgroundColor(Color.Black)
          .alignRules({
            top: { 'anchor': 'playerViewDividLine', align: VerticalAlign.Bottom },
            left: { 'anchor': 'XComponent', align: HorizontalAlign.Start },
            right: { 'anchor': 'XComponent', align: HorizontalAlign.End }
          })
          .gesture(
            GestureGroup(GestureMode.Parallel,
              PinchGesture({ fingers: 2 })
                .onActionStart((event: GestureEvent) => {
                  this.baseScale2 = this.realScale2
                })
                .onActionUpdate(async (event: GestureEvent) => {
                  let scale = this.baseScale2 * event.scale
                  let centerX = vp2px(event.pinchCenterX)
                  let centerY = vp2px(event.pinchCenterY)

                  await this.zoomScale2(scale, centerX, centerY)
                })
                .onActionEnd((event: GestureEvent) => {

                }),
              PanGesture()
                .onActionStart((event: GestureEvent) => {
                  this.lastOffsetX2 = 0
                  this.lastOffsetY2 = 0
                })
                .onActionUpdate(async (event: GestureEvent) => {
                  const deltaX = vp2px(event.offsetX - this.lastOffsetX2)
                  const deltaY = vp2px(event.offsetY - this.lastOffsetY2)
                  await this.zoomMove2(deltaX, deltaY)

                  this.lastOffsetX2 = event.offsetX
                  this.lastOffsetY2 = event.offsetY
                })
                .onActionEnd((event: GestureEvent) => {

                })))
      }
      // 取流方式 / 解码方式
      Column() {
        Text(this.streamFetchType).fontSize(12).fontColor(Color.Green)
        Text(this.streamDecodeType).fontSize(12).fontColor(Color.Orange)
      }.alignItems(HorizontalAlign.End).margin({ top: 7, right: 7 })
      .alignRules({
        top: { 'anchor': 'XComponent', align: VerticalAlign.Top },
        right: { 'anchor': 'XComponent', align: HorizontalAlign.End }
      })

      // 暂停时显示的封面图
      Image(fileUri.getUriFromPath(this.coverImagePath))
        .width('100%')
        .aspectRatio(16 / 9)
        .visibility(this.isCoverImageShow ? Visibility.Visible : Visibility.Hidden)
        .alignRules({
          middle: { 'anchor': 'XComponent', align: HorizontalAlign.Center },
          center: { 'anchor': 'XComponent', align: VerticalAlign.Center },
        })
      // 双目设备第二个画面封面图
      if (this.isMultiChannelDevice) {
        Image(fileUri.getUriFromPath(this.coverImagePath2))
          .width('100%')
          .aspectRatio(16 / 9)
          .visibility(this.isCoverImageShow ? Visibility.Visible : Visibility.Hidden)
          .alignRules({
            middle: { 'anchor': 'XComponent2', align: HorizontalAlign.Center },
            center: { 'anchor': 'XComponent2', align: VerticalAlign.Center },
          })
      }
      // 播放器视图正中央 - 开始播放按钮
      Image($r('app.media.play'))
        .width(54)
        .aspectRatio(1)
        .visibility(this.isStartPlayBtnShow ? Visibility.Visible : Visibility.Hidden)
        .onClick(this.playAction)
        .id('playerBtn')
        .alignRules({
          middle: { 'anchor': 'XComponent', align: HorizontalAlign.Center },
          center: {
            'anchor': 'XComponent',
            align: this.isMultiChannelDevice ? VerticalAlign.Bottom : VerticalAlign.Center
          }
        })
      // 加载进度
      LoadingProgress().width(40).aspectRatio(1)
        .visibility(this.isStreamLoadingShow ? Visibility.Visible : Visibility.Hidden)
        .alignRules({
          middle: { 'anchor': 'playerBtn', align: HorizontalAlign.Center },
          center: { 'anchor': 'playerBtn', align: VerticalAlign.Center }
        })
      // 播放错误信息
      Text(this.errorMessage).fontColor(Color.White)
        .visibility(this.isErrorMessageShow ? Visibility.Visible : Visibility.Hidden)
        .alignRules({
          middle: { 'anchor': 'playerBtn', align: HorizontalAlign.Center },
          center: { 'anchor': 'playerBtn', align: VerticalAlign.Center }
        })
    }
    .width("auto").height("auto")
    .id('realplay_view')
    .alignRules({
      top: { 'anchor': '__container__', align: VerticalAlign.Top },
      middle: { 'anchor': '__container__', align: HorizontalAlign.Center }
    })
  }
}