WdWebLocalComponent.ets 9.34 KB
import router from '@ohos.router';
import { BridgeUtil, BridgeWebViewControl, Callback } from 'wdJsBridge';
import { Logger } from 'wdKit/Index';
import { performJSCallNative } from './JsBridgeBiz';
import { H5CallNativeType } from './H5CallNativeType';
import { Message } from 'wdJsBridge/src/main/ets/bean/Message';
import { DateTimeUtils } from 'wdKit'

const TAG = 'WdWebLocalComponent';

@Component
export struct WdWebLocalComponent {
  webviewControl: BridgeWebViewControl = new BridgeWebViewControl()
  onWebPrepared: () => void = () => {
  }
  @Prop backVisibility: boolean = false
  @Prop webResource: Resource = {} as Resource
  @Prop @Watch('onReloadStateChanged') reload: number = 0
  @State webHeight: string | number = '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 durationStringTime: string = '';
  @State isPause: boolean = true;
  controller: VideoController = new VideoController()
  @StorageProp('currentBreakpoint') @Watch("currentChanged")currentBreakpoint: string = 'sm';

  currentChanged(){
    this.webviewControl.refresh()
  }

  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() {
          Web({
            src: this.webResource,
            controller: this.webviewControl,
            renderMode: RenderMode.SYNC_RENDER
          })// Web({ src: this.webResource, controller: this.webviewControl })
            .backgroundColor(Color.White)
            .domStorageAccess(true)
            .databaseAccess(true)
            .javaScriptAccess(true)
            .imageAccess(true)
            .mixedMode(MixedMode.All)
            .onlineImageAccess(true)
            // .enableNativeEmbedMode(true)
            //   .layoutMode(WebLayoutMode.FIT_CONTENT)
            .nestedScroll({
              scrollForward: NestedScrollMode.SELF_FIRST,
              scrollBackward: NestedScrollMode.PARENT_FIRST
            })
            .width('100%')
            .height(this.webHeight)
            .onPageBegin((event) => {
              this.onPageBegin(event?.url);
            })
            .onPageEnd((event) => {
              this.onPageEnd(event?.url)
            })
            .onLoadIntercept((event) => {
              let url: string = event.data.getRequestUrl().toString()
              url = url.replace("%(?![0-9a-fA-F]{2})", "%25")
                .replace("\\+", "%2B");
              url = decodeURIComponent(url)
              if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) {
                this.webviewControl.handlerReturnData(url)
                return true
              }
              if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) {
                Logger.debug(TAG, 'flushMessageQueue');
                this.webviewControl.flushMessageQueue()
                return true
              }
              return this.onLoadIntercept(event.data.getRequestUrl().toString());
            })
            .id('web')
            .alignRules({
              top: { anchor: "__container__", align: VerticalAlign.Top },
            })

          if (this.videoUrl) {
            Stack({ alignContent: Alignment.Bottom }) {
              Video({
                src: this.videoUrl,
                currentProgressRate: this.curRate,
                controller: this.controller
              })
                .borderRadius(5)
                .controls(false)
                .autoPlay(true)
                .objectFit(ImageFit.Contain)
                .onStart(() => {
                  this.isPause = false
                })
                .onPause(() => {
                  this.isPause = true
                })
                .onPrepared((event) => {
                  if (event) {
                    this.durationTime = event.duration
                  }
                })
                .onUpdate((event) => {
                  if (event) {
                    this.currentTime = event.time
                  }
                })
              Row() {
                Image(this.isPause ? $r('app.media.icon_play') : $r('app.media.icon_pause'))
                  .width(24)
                  .height(24)
                  .onClick(() => {
                    if (this.isPause) {
                      this.controller.start()
                    } else {
                      this.controller.pause()
                    }
                  })
                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')
                    .margin({ left: 4, right: 4 })// .blockStyle({
                      //   type: SliderBlockType.IMAGE,
                      //   image: $r('app.media.slider_block')
                      // })
                      // .blockSize({ width: 18, height: 12 })
                    .onChange((value: number, mode: SliderChangeMode) => {
                      this.controller.setCurrentTime(value);
                    })
                  Text(DateTimeUtils.getFormattedDuration(this.durationTime * 1000))
                    .fontSize(12)
                    .fontColor(Color.White)
                    .fontWeight(600)
                }
                .justifyContent(FlexAlign.Center)

                Image($r('app.media.icon_full_screen'))
                  .width(24)
                  .height(24)
                  .onClick(() => {
                    this.controller.requestFullscreen(true)
                  })
              }
              .opacity(0.8)
              .width("100%")
              .justifyContent(FlexAlign.SpaceAround)
            }
            .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)
    }
  }

  private registerHandlers(): void {
    // 注册h5调用js相关
    for (let i = 0; i < H5CallNativeType.JsCallTypeList.length; i++) {
      let handleName = H5CallNativeType.JsCallTypeList[i];
      let handle = (data: Message, f: Callback) => {
        Logger.debug('registerHandlers handlerName: ' + JSON.stringify(data.data))
        this.setCurrentPageOperate8(data)
        this.setCurrentPageOperate9(data)
        this.defaultPerformJSCallNative(data, f)
      };
      this.webviewControl.registerHandler(handleName, { handle: handle });
    }
  }

  //webview 高度设置
  private setCurrentPageOperate8: (data: Message) => void = (data) => {
    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%'
      }
    }
  }
  //播放视频
  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'
    }
  }
  /**
   * 默认【CallNative】逻辑处理
   */
  private defaultPerformJSCallNative: (data: Message, f: Callback) => void = (data: Message, f: Callback) => {
    performJSCallNative(data, f)
  }
  onPageBegin: (url?: string) => void = () => {
    Logger.debug(TAG, 'onPageBegin');
    this.registerHandlers();
    //有时序问题 必须延时执行
    setTimeout(() => {
      BridgeUtil.webViewLoadLocalJs(getContext(this), this.webviewControl)
    }, 200)
  }
  onPageEnd: (url?: string) => void = () => {
    Logger.debug(TAG, 'onPageEnd');
    this.onWebPrepared()
    this.isPageEnd = true
  }
  onLoadIntercept: (url?: string) => boolean = () => {
    Logger.debug(TAG, 'onLoadIntercept return false');
    return false
  }

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