WdWebLocalComponent.ets 12.4 KB
import router from '@ohos.router';
import { BridgeUtil, BridgeWebViewControl, Callback } from 'wdJsBridge';
import { Logger, WindowModel } from 'wdKit/Index';
import { performJSCallNative } from './JsBridgeBiz';
import { H5CallNativeType } from './H5CallNativeType';
import { Message } from 'wdJsBridge/src/main/ets/bean/Message';
import { DateTimeUtils,EmitterUtils,EmitterEventId } from 'wdKit'
import { window } from '@kit.ArkUI';
import { NativeCallH5Type,NativeCallH5Event,eventParams } from './NativeCallH5Type';
import { AudioSuspensionModel } from '../viewmodel/AudioSuspensionModel'
import { BusinessError } from '@kit.BasicServicesKit';
import { WebPoolManager } from '../webPool/WebPoolManager';
import { WebEvents } from '../webPool/WebLocalPool';

const TAG = 'WdWebLocalComponent';

@Component
export struct WdWebLocalComponent {
  webPoolTargetId: string = ''
  webviewControl: BridgeWebViewControl = new BridgeWebViewControl()
  onWebPrepared: () => void = () => {
  }
  @Prop backVisibility: boolean = false
  @Prop @Watch('onReloadStateChanged') reload: number = 0
  @State webHeight: Length = '100%'
  @Link isPageEnd: boolean
  @State videoUrl: string = ''
  @State positionWidth: number = 0
  @State positionHeight: number = 0
  @State positionLeft: number = 0
  @State positionTop: number = 0
  @State videoLandscape: string = '1'
  @State curRate: PlaybackSpeed = PlaybackSpeed.Speed_Forward_1_00_X
  @State sliderStartTime: string = '';
  @State currentTime: number = 0;
  @State durationTime: number = 0;
  @State progressOpacity: number = 1
  @State durationStringTime: string = '';
  private progressTimerNumber: number = 0
  @State isPause: boolean = true;
  controller: VideoController = new VideoController()
  @StorageProp('currentBreakpoint') @Watch("currentChanged")currentBreakpoint: string = 'sm';

  @State isFullScreen: boolean = false;
  @State isEndPlay: boolean = false;
  @State mode: CacheMode = CacheMode.None;

  @Consume @Watch('pageShowForUpdateData') pageShow :number
  @Consume @Watch('pageHideForUpdateData') pageHide :number

  private AudioSuspension = new AudioSuspensionModel()

  currentChanged(){
    ///折叠屏转换 暂停播放器
    this.controller.pause()
    this.positionWidth =  0
    this.positionHeight = 0


    this.webviewControl.refresh()
  }

  pageShowForUpdateData(){

    Logger.debug(TAG, 'APP_PAGE_SHOW'+this.isPageEnd);
    let params = {'event':NativeCallH5Event.NativeCallH5EventPageWillAppear} as eventParams;
    let jsonString = JSON.stringify(params);
    if (this.isPageEnd) {
      this.webviewControl.callHandle(NativeCallH5Type.jsCall_appNotifyEvent, jsonString, (data: string) => {
        Logger.debug(TAG, "from js data = " + data);
      })
    }
  }

  pageHideForUpdateData(){

  }

  aboutToAppear(): void {

    let eventHandler = WebPoolManager.sharedInstance().getWebEventHandler(this.webPoolTargetId)
    if (!eventHandler) {
      Logger.error(TAG, '当前target不应拿不到nodeController');
    } else {
      eventHandler.pageEndBlock = this.onPageEnd.bind(this)
      if (eventHandler.pageLoadEnd) {
        this.onPageEnd()

        // 开始复用
        this.startResuse()
      }
      eventHandler.currentPageOperateBlock = this.currentPageOperate.bind(this)
    }

    EmitterUtils.receiveEvent(EmitterEventId.AUDIO_CHANGE_STATUS, (status) => {
      Logger.debug(TAG, "接收音频播放状态 = " + status);
      if (!this.isPause) {
        this.controller.pause()
        this.cancelProgressTimer()
      }
    })
  }

  build() {
    Column() {
      Row() {
        Image($r("app.media.ic_back"))
          .width(44)
          .padding(13)
          .aspectRatio(1)
          .onClick(() => {
            router.back();
          })
      }.backgroundColor(Color.Black)
      .width('100%')
      .height(44)
      .visibility(this.backVisibility ? Visibility.Visible : Visibility.None)

      Row() {
        RelativeContainer() {
          NodeContainer(WebPoolManager.sharedInstance().getWebNodeController(this.webPoolTargetId))
            .id('web')
            .alignRules({
              top: { anchor: "__container__", align: VerticalAlign.Top },
            })

          if (this.videoUrl) {
            Stack({ alignContent: Alignment.Bottom }) {
              ForEach([this.videoUrl], (compIndex: number) => {
                ///解决播放器复用问题,后期优化(第二段视频播放器会先闪现第一段视频的画面)
                ListItem() {
                  this.videoComp()
                }
                .onClick(() => {
                  if (this.progressOpacity <= 0) {
                    this.progressOpacity = 1
                  } else {
                    this.progressOpacity = 0
                  }
                })
              })
            }
            .width(this.positionWidth)
            .height(this.positionHeight)
            .alignRules({
              top: { anchor: "__container__", align: VerticalAlign.Top },
            })
            .offset({
              x: this.positionLeft,
              y: this.positionTop
            })
            .id("video")
          }
        }
      }.width('100%')
      .height(this.webHeight)
    }
  }

  //webview 高度设置
  private setCurrentPageOperate8: (data: Message) => void = (data) => {

    Logger.debug('setCurrentPageOperate8: ' + data?.data?.webViewHeight)
    if (data.handlerName === H5CallNativeType.jsCall_currentPageOperate && data?.data?.operateType === '8') {
      if (typeof this.webHeight === 'number') {
        if (Number(data?.data?.webViewHeight) > this.webHeight) {
          this.webHeight = Number(data?.data?.webViewHeight)
        }
      }
      {
        this.webHeight = Number(data?.data?.webViewHeight) || '100%'
      }

      WebPoolManager.sharedInstance().getWebNodeController(this.webPoolTargetId)?.updateWebHeight(this.webHeight)
    }
  }
  // 暂停音频悬浮窗
  pauseAudioCom() {
    // 判断当前窗口是否已显示,使用callback异步回调。
    try {
      let data = this.AudioSuspension.floatWindowClass.get().isWindowShowing();
      // console.info(TAG, 'window is showing: ' + data);
      if(data) {
        this.AudioSuspension.playerController.get()?.pause();
      }
    } catch (exception) {
      console.error(TAG, `Failed to check whether the window is showing. Cause code: ${exception.code}, message: ${exception.message}`);
    }
  }
  //播放视频
  private setCurrentPageOperate9: (data: Message) => void = (data) => {
    if (data.handlerName === H5CallNativeType.jsCall_currentPageOperate && data?.data?.operateType === '9') {
      this.videoUrl = data?.data?.videoUrl || ''
      this.positionWidth = Number(data?.data?.positionWidth) || 0
      this.positionHeight = Number(data?.data?.positionHeight) || 0
      this.positionLeft = Number(data?.data?.positionLeft) || 0
      this.positionTop = Number(data?.data?.positionTop) || 0
      this.videoLandscape = data?.data?.videoLandscape || '1'
      this.startPlay()
    }
  }
  /**
   * 默认【CallNative】逻辑处理
   */
  private currentPageOperate: (data: Message, f: Callback) => void = (data: Message, f: Callback) => {
    this.setCurrentPageOperate8(data)
    this.setCurrentPageOperate9(data)
    performJSCallNative(data, f)
  }

  onPageEnd: (url?: string) => void = () => {
    Logger.debug(TAG, 'onPageEnd');
    this.onWebPrepared()
    this.isPageEnd = true
  }

  onReloadStateChanged() {
    Logger.info(TAG, `onReloadStateChanged:::refresh, this.reload: ${this.reload}`);
    if (this.reload > 0) {
      this.webviewControl.refresh()
    }
  }

  startResuse() {
    let params = {'event':NativeCallH5Event.NativeCallH5EventStartLoadingOnReuse} as eventParams;
    let jsonString = JSON.stringify(params);
    this.webviewControl.callHandle(NativeCallH5Type.jsCall_appNotifyEvent, jsonString, (data: string) => {
      Logger.debug(TAG, "开始复用,H5回调" + data);
    })
  }

  startPlay() {
    this.cancelProgressTimer()
    this.controller.start()
    this.startProgressTimer()
    this.pauseAudioCom()
  }

  startProgressTimer() {
    this.progressTimerNumber = setTimeout(() => {
      // animateTo({duration: 1000}, () => {
        this.progressOpacity = 0
      // })
    }, 4000)
  }
  cancelProgressTimer() {
    if (this.progressTimerNumber > 0) {
      clearTimeout(this.progressTimerNumber)
      this.progressTimerNumber = 0
    }
  }

  @Builder
  videoComp(){
    Video({
      src: this.videoUrl,
      currentProgressRate: this.curRate,
      controller: this.controller
    })
      .width(this.positionWidth)
      .height(this.positionHeight)
      .borderRadius(5)
      .controls(this.isFullScreen?true:false)
      .autoPlay(true)
      .objectFit(ImageFit.Contain)
      .onFinish(()=>{
        this.isEndPlay = true
        this.currentTime = 0
      })
      .onStart(() => {
        this.isPause = false
      })
      .onPause(() => {
        this.isPause = true
      })
      .onPrepared((event) => {
        if (event) {
          this.durationTime = event.duration
        }
      })
      .onUpdate((event) => {
        if (event) {
          this.currentTime = event.time
        }
      })
      .onFullscreenChange((fullscreen)=>{
        this.isFullScreen  = fullscreen.fullscreen
        if (this.isFullScreen) {
          WindowModel.shared.setPreferredOrientation(this.videoLandscape == '2' ?
          window.Orientation.PORTRAIT :
          window.Orientation.LANDSCAPE)
        }else {
          WindowModel.shared.setPreferredOrientation(window.Orientation.PORTRAIT)
        }
      })

    if (this.isEndPlay){
      Column(){
        Image($r('app.media.icon_replay')).width(40).height(40)
          .onClick(() => {
            this.isEndPlay = false
            this.progressOpacity = 1
            this.startPlay()
          })

        Text('重播').fontColor(Color.White).fontSize(14)
      }
      .backgroundColor("#80000000")
      .justifyContent(FlexAlign.Center)
      .width(this.positionWidth)
      .height(this.positionHeight)
    }else {
      Column(){
        Row() {
          Image(this.isPause ? $r('app.media.icon_play') : $r('app.media.icon_pause'))
            .interpolation(ImageInterpolation.Medium)
            .width(24)
            .height(24)
            .onClick(() => {
              if (this.isPause) {
                this.startPlay()
              } else {
                this.controller.pause()
                this.cancelProgressTimer()
              }
            }).margin({right:16})

          Row() {
            Text(DateTimeUtils.getFormattedDuration(this.currentTime * 1000))
              .fontSize(12)
              .fontColor(Color.White)
              .fontWeight(600)

            Slider({
              value: this.currentTime,
              min: 0,
              max: this.durationTime
            })
              .width("50%")
              .selectedColor('#ED2800')
              .trackColor("#1AFFFFFF")
              .trackThickness(2)
              .margin({ left: 4, right: 4 })
              .blockStyle({
                type: SliderBlockType.IMAGE,
                image: $r('app.media.slider_block')
              })
              .blockSize({ width: 14, height: 10 })
              .onChange((value: number, mode: SliderChangeMode) => {
                this.controller.setCurrentTime(value);
                if (mode == SliderChangeMode.End) {
                  if (this.isPause) {
                    this.progressTimerNumber = 0
                    this.startPlay()
                  } else {
                    this.startProgressTimer()
                  }
                }
              })
            Text(DateTimeUtils.getFormattedDuration(this.durationTime * 1000))
              .fontSize(12)
              .fontColor(Color.White)
              .fontWeight(600)
          }.alignItems(VerticalAlign.Center)
          .height(48)

          // Image($r('app.media.icon_full_screen'))
          //   .width(24)
          //   .height(24)
          //   .onClick(() => {
          //     this.controller.requestFullscreen(true)
          //   })
        }
        .opacity(this.progressOpacity)
        .linearGradient({
          direction: GradientDirection.Top, // 渐变方向
          colors: [[0x20000000, 0.0], [Color.Transparent, 1.0]] // [0x80000000, 0.5],
        })
        .width("100%")
        .height(48)
        .padding({left:16})
        .alignItems(VerticalAlign.Center)
      }
      .justifyContent(FlexAlign.End)
      .width(this.positionWidth)
      .height(this.positionHeight)
    }
  }

}