<!-- eslint-disable -->
<template>
  <view class="player-wrapper" :id="videoWrapperId" :parentId="id" :randomNum="randomNum"
    :change:randomNum="domVideoPlayer.randomNumChange" :viewportProps="viewportProps"
    :change:viewportProps="domVideoPlayer.viewportChange" :videoSrc="videoSrc"
    :change:videoSrc="domVideoPlayer.initVideoPlayer" :command="eventCommand"
    :change:command="domVideoPlayer.triggerCommand" :func="renderFunc" :change:func="domVideoPlayer.triggerFunc" />
</template>

<script>
export default {
  props: {
    src: {
      type: String,
      default: ''
    },
    autoplay: {
      type: Boolean,
      default: false
    },
    loop: {
      type: Boolean,
      default: false
    },
    controls: {
      type: Boolean,
      default: false
    },
    objectFit: {
      type: String,
      default: 'contain'
    },
    muted: {
      type: Boolean,
      default: false
    },
    playbackRate: {
      type: Number,
      default: 1
    },
    isLoading: {
      type: Boolean,
      default: false
    },
    poster: {
      type: String,
      default: ''
    },
    id: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
      randomNum: Math.floor(Math.random() * 100000000),
      videoSrc: '',
      // 父组件向子组件传递的事件指令（video的原生事件）
      eventCommand: null,
      // 父组件传递过来的，对 renderjs 层的函数执行（对视频控制的自定义事件）
      renderFunc: {
        name: null,
        params: null
      },
      // 提供给父组件进行获取的视频属性
      currentTime: 0,
      duration: 0,
      playing: false
    }
  },
  watch: {
    // 监听视频资源地址更新
    src: {
      handler(val) {
        if (!val) return
        setTimeout(() => {
          this.videoSrc = val
        }, 0)
      },
      immediate: true
    }
  },
  computed: {
    videoWrapperId() {
      return `video-wrapper-${this.randomNum}`
    },
    // 聚合视图层的所有数据变化，传给renderjs的渲染层
    viewportProps() {
      return {
        autoplay: this.autoplay,
        muted: this.muted,
        controls: this.controls,
        loop: this.loop,
        objectFit: this.objectFit,
        poster: this.poster,
        isLoading: this.isLoading,
        playbackRate: this.playbackRate
      }
    }
  },
  // 方法
  methods: {
    // 传递事件指令给父组件
    eventEmit({ event, data }) {
      this.$emit(event, data)
    },
    // 修改view视图层的data数据
    setViewData({ key, value }) {
      key && this.$set(this, key, value)
    },
    // 重置事件指令
    resetEventCommand() {
      this.eventCommand = null
    },
    // 播放指令
    play() {
      this.eventCommand = 'play'
    },
    // 暂停指令
    pause() {
      this.eventCommand = 'pause'
    },
    // 重置自定义函数指令
    resetFunc() {
      this.renderFunc = {
        name: null,
        params: null
      }
    },
    // 自定义函数 - 移除视频
    remove(params) {
      this.renderFunc = {
        name: 'removeHandler',
        params
      }
    },
    // 自定义函数 - 全屏播放
    fullScreen(params) {
      this.renderFunc = {
        name: 'fullScreenHandler',
        params
      }
    },
    // 自定义函数 - 跳转到指定时间点
    toSeek(sec, isDelay = false) {
      this.renderFunc = {
        name: 'toSeekHandler',
        params: { sec, isDelay }
      }
    }
  },
  // 组件销毁前自动清理 renderjs 层的原生 video 元素，
  // 防止 v-if 移除组件后视频仍在后台播放声音（"僵尸视频"问题）
  beforeUnmount() {
    this.renderFunc = {
      name: 'removeHandler',
      params: null
    }
  }
}
</script>


<script module="domVideoPlayer" lang="renderjs">
const PLAYER_ID = 'DOM_VIDEO_PLAYER'
export default {
  data() {
    return {
      num: '',
      videoEl: null,
      loadingEl: null,
      // 延迟生效的函数
      delayFunc: null,
      renderProps: {},
      // 是否主动暂停（通过父组件调用 pause() 方法）
      intentionalPause: false,
      // 上次自动恢复的时间戳，用于冷却控制
      lastAutoRecoverTime: 0
    }
  },
  computed: {
    playerId() {
      return `${PLAYER_ID}_${this.num}`
    },
    wrapperId() {
      return `video-wrapper-${this.num}`
    }
  },
  methods: {
    isApple() {
      const ua = navigator.userAgent.toLowerCase()
      return ua.indexOf('iphone') !== -1 || ua.indexOf('ipad') !== -1
    },
    async initVideoPlayer(src) {
      this.delayFunc = null
      await this.$nextTick()
      if (!src) return
      if (this.videoEl) {
        // 切换视频源
        if (!this.isApple() && this.loadingEl) {
          this.loadingEl.style.display = 'block'
        }
        this.videoEl.src = src
        return
      }

      const videoEl = document.createElement('video')
      this.videoEl = videoEl
      // 开始监听视频相关事件
      this.listenVideoEvent()

      const { autoplay, muted, controls, loop, playbackRate, objectFit, poster } = this.renderProps
      videoEl.src = src
      videoEl.autoplay = autoplay
      videoEl.controls = controls
      videoEl.loop = loop
      videoEl.muted = muted
      videoEl.playbackRate = playbackRate
      videoEl.id = this.playerId
      // 开启 X5 内核的 H5 同层渲染，防止原生视频组件层级过高在拖拽时脱离或卡死
      videoEl.setAttribute('x5-video-player-type', 'h5')
      videoEl.setAttribute('x5-video-player-fullscreen', 'false')
      videoEl.setAttribute('preload', 'auto')
      videoEl.setAttribute('playsinline', true)
      videoEl.setAttribute('webkit-playsinline', true)
      videoEl.setAttribute('crossorigin', 'anonymous')
      videoEl.setAttribute('controlslist', 'nodownload')
      videoEl.setAttribute('disablePictureInPicture', true)
      videoEl.style.objectFit = objectFit
      poster && (videoEl.poster = poster)
      videoEl.style.width = '100%'
      videoEl.style.height = '100%'

      // 插入视频元素
      // document.getElementById(this.wrapperId).appendChild(videoEl)
      const playerWrapper = document.getElementById(this.wrapperId)
      playerWrapper.insertBefore(videoEl, playerWrapper.firstChild)

      // 插入loading 元素（遮挡安卓的默认加载过程中的黑色播放按钮）
      this.createLoading()
    },
    // 创建 loading
    createLoading() {
      const { isLoading } = this.renderProps
      if (!this.isApple() && isLoading) {
        const loadingEl = document.createElement('div')
        this.loadingEl = loadingEl
        loadingEl.className = 'loading-wrapper'
        loadingEl.style.position = 'absolute'
        loadingEl.style.top = '0'
        loadingEl.style.left = '0'
        loadingEl.style.zIndex = '1'
        loadingEl.style.width = '100%'
        loadingEl.style.height = '100%'
        loadingEl.style.backgroundColor = 'black'
        document.getElementById(this.wrapperId).appendChild(loadingEl)

        // 创建 loading 动画
        const animationEl = document.createElement('div')
        animationEl.className = 'loading'
        animationEl.style.zIndex = '2'
        animationEl.style.position = 'absolute'
        animationEl.style.top = '50%'
        animationEl.style.left = '50%'
        animationEl.style.marginTop = '-15px'
        animationEl.style.marginLeft = '-15px'
        animationEl.style.width = '30px'
        animationEl.style.height = '30px'
        animationEl.style.border = '2px solid #FFF'
        animationEl.style.borderTopColor = 'rgba(255, 255, 255, 0.2)'
        animationEl.style.borderRightColor = 'rgba(255, 255, 255, 0.2)'
        animationEl.style.borderBottomColor = 'rgba(255, 255, 255, 0.2)'
        animationEl.style.borderRadius = '100%'
        animationEl.style.animation = 'circle infinite 0.75s linear'
        loadingEl.appendChild(animationEl)

        // 创建 loading 动画所需的 keyframes
        const style = document.createElement('style')
        const keyframes = `
          @keyframes circle {
            0% {
              transform: rotate(0);
            }
            100% {
              transform: rotate(360deg);
            }
          }
        `
        style.type = 'text/css'
        if (style.styleSheet) {
          style.styleSheet.cssText = keyframes
        } else {
          style.appendChild(document.createTextNode(keyframes))
        }
        document.head.appendChild(style)
      }
    },
    // 监听视频相关事件
    listenVideoEvent() {
      // 播放事件监听
      const playHandler = () => {
        this.$ownerInstance.callMethod('eventEmit', { event: 'play' })
        this.$ownerInstance.callMethod('setViewData', {
          key: 'playing',
          value: true
        })

        if (this.loadingEl) {
          this.loadingEl.style.display = 'none'
        }
      }
      this.videoEl.removeEventListener('play', playHandler)
      this.videoEl.addEventListener('play', playHandler)

      // 暂停事件监听
      const pauseHandler = () => {
        this.$ownerInstance.callMethod('eventEmit', { event: 'pause' })
        this.$ownerInstance.callMethod('setViewData', {
          key: 'playing',
          value: false
        })

        // 自动恢复机制：仅在非主动暂停时触发，并带有 2 秒冷却期防止循环拖动
        const { loop, autoplay } = this.renderProps
        const now = Date.now()
        if (loop && autoplay && this.videoEl && !this.intentionalPause && (now - this.lastAutoRecoverTime > 2000)) {
          this.lastAutoRecoverTime = now
          setTimeout(() => {
            if (this.videoEl && this.videoEl.paused && !this.intentionalPause) {
              this.videoEl.play().catch(() => {})
            }
          }, 200)
        }
      }
      this.videoEl.removeEventListener('pause', pauseHandler)
      this.videoEl.addEventListener('pause', pauseHandler)

      // 结束事件监听
      const endedHandler = () => {
        this.$ownerInstance.callMethod('eventEmit', { event: 'ended' })
        this.$ownerInstance.callMethod('resetEventCommand')
      }
      this.videoEl.removeEventListener('ended', endedHandler)
      this.videoEl.addEventListener('ended', endedHandler)

      // 加载完成事件监听
      const canPlayHandler = () => {
        this.$ownerInstance.callMethod('eventEmit', { event: 'canplay' })
        this.execDelayFunc()
      }
      this.videoEl.removeEventListener('canplay', canPlayHandler)
      this.videoEl.addEventListener('canplay', canPlayHandler)

      // 加载失败事件监听
      const errorHandler = (e) => {
        if (this.loadingEl) {
          this.loadingEl.style.display = 'block'
        }
        this.$ownerInstance.callMethod('eventEmit', { event: 'error' })
      }
      this.videoEl.removeEventListener('error', errorHandler)
      this.videoEl.addEventListener('error', errorHandler)

      // loadedmetadata 事件监听
      const loadedMetadataHandler = () => {
        this.$ownerInstance.callMethod('eventEmit', { event: 'loadedmetadata' })
        // 获取视频的长度
        const duration = this.videoEl.duration
        this.$ownerInstance.callMethod('eventEmit', {
          event: 'durationchange',
          data: duration
        })

        this.$ownerInstance.callMethod('setViewData', {
          key: 'duration',
          value: duration
        })

        // 加载首帧视频 模拟出封面图
        this.loadFirstFrame()
      }
      this.videoEl.removeEventListener('loadedmetadata', loadedMetadataHandler)
      this.videoEl.addEventListener('loadedmetadata', loadedMetadataHandler)

      // 播放进度监听
      let lastTime = 0;
      const timeupdateHandler = (e) => {
        const now = Date.now();
        // 增加节流阀，每 500ms 触发一次通信，极大地减少 renderjs 和逻辑层的通讯开销
        if (now - lastTime < 500) return;
        lastTime = now;

        const currentTime = e.target.currentTime
        this.$ownerInstance.callMethod('eventEmit', {
          event: 'timeupdate',
          data: currentTime
        })

        this.$ownerInstance.callMethod('setViewData', {
          key: 'currentTime',
          value: currentTime
        })

      }
      this.videoEl.removeEventListener('timeupdate', timeupdateHandler)
      this.videoEl.addEventListener('timeupdate', timeupdateHandler)

      // 倍速播放监听
      const ratechangeHandler = (e) => {
        const playbackRate = e.target.playbackRate
        this.$ownerInstance.callMethod('eventEmit', {
          event: 'ratechange',
          data: playbackRate
        })
      }
      this.videoEl.removeEventListener('ratechange', ratechangeHandler)
      this.videoEl.addEventListener('ratechange', ratechangeHandler)

      // 全屏事件监听
      if (this.isApple()) {
        const webkitbeginfullscreenHandler = () => {
          const presentationMode = this.videoEl.webkitPresentationMode
          let isFullScreen = null
          if (presentationMode === 'fullscreen') {
            isFullScreen = true
          } else {
            isFullScreen = false
          }
          this.$ownerInstance.callMethod('eventEmit', {
            event: 'fullscreenchange',
            data: isFullScreen
          })
        }
        this.videoEl.removeEventListener('webkitpresentationmodechanged', webkitbeginfullscreenHandler)
        this.videoEl.addEventListener('webkitpresentationmodechanged', webkitbeginfullscreenHandler)
      } else {
        const fullscreenchangeHandler = () => {
          let isFullScreen = null
          if (document.fullscreenElement) {
            isFullScreen = true
          } else {
            isFullScreen = false
          }
          this.$ownerInstance.callMethod('eventEmit', {
            event: 'fullscreenchange',
            data: isFullScreen
          })
        }
        document.removeEventListener('fullscreenchange', fullscreenchangeHandler)
        document.addEventListener('fullscreenchange', fullscreenchangeHandler)
      }
    },
    // 加载首帧视频，模拟出封面图
    loadFirstFrame() {
      let { autoplay, muted } = this.renderProps
      if (this.isApple()) {
        this.videoEl.play()
        if (!autoplay) {
          this.videoEl.pause()
        }
      } else {
        // optimize: timeout 延迟调用是为了规避控制台的`https://goo.gl/LdLk22`这个报错
        /**
         * 原因：chromium 内核中，谷歌协议规定，视频不允许在非静音状态下进行自动播放
         * 解决：在自动播放时，先将视频静音，然后延迟调用 play 方法，播放视频
         * 说明：iOS 的 Safari 内核不会有这个，仅在 Android 设备出现，即使有这个报错也不影响的，所以不介意控制台报错的话是可以删掉这个 timeout 的
         */
        this.videoEl.muted = true
        setTimeout(() => {
          this.videoEl.play()
          this.videoEl.muted = muted
          if (!autoplay) {
            setTimeout(() => {
              this.videoEl.pause()
            }, 100)
          }
        }, 10)
      }
    },
    triggerCommand(eventType) {
      if (eventType) {
        // 标记主动暂停/播放状态，用于自动恢复机制的判断
        if (eventType === 'pause') {
          this.intentionalPause = true
        } else if (eventType === 'play') {
          this.intentionalPause = false
        }
        this.$ownerInstance.callMethod('resetEventCommand')
        this.videoEl && this.videoEl[eventType]()
      }
    },
    triggerFunc(func) {
      const { name, params } = func || {}
      if (name) {
        this[name](params)
        this.$ownerInstance.callMethod('resetFunc')
      }
    },
    removeHandler() {
      if (this.videoEl) {
        this.videoEl.pause()
        this.videoEl.src = ''
        this.$ownerInstance.callMethod('setViewData', {
          key: 'videoSrc',
          value: ''
        })
        this.videoEl.load()
      }
    },
    fullScreenHandler() {
      if (this.isApple()) {
        this.videoEl.webkitEnterFullscreen()
      } else {
        this.videoEl.requestFullscreen()
      }
    },
    toSeekHandler({ sec, isDelay }) {
      const func = () => {
        if (this.videoEl) {
          this.videoEl.currentTime = sec
        }
      }

      // 延迟执行
      if (isDelay) {
        this.delayFunc = func
      } else {
        func()
      }
    },
    // 执行延迟函数
    execDelayFunc() {
      this.delayFunc && this.delayFunc()
      this.delayFunc = null
    },
    viewportChange(props) {
      this.renderProps = props
      const { autoplay, muted, controls, loop, playbackRate } = props
      if (this.videoEl) {
        this.videoEl.autoplay = autoplay
        this.videoEl.controls = controls
        this.videoEl.loop = loop
        this.videoEl.muted = muted
        this.videoEl.playbackRate = playbackRate
      }
    },
    randomNumChange(val) {
      this.num = val
    }
  },
  // renderjs 层直接清理原生 video 元素，
  // 确保组件被 v-if 销毁时视频立即停止播放和声音输出
  beforeUnmount() {
    if (this.videoEl) {
      this.videoEl.pause()
      this.videoEl.src = ''
      this.videoEl.load()
      this.videoEl = null
    }
  }
}
</script>

<style scoped>
.player-wrapper {
  overflow: hidden;
  height: 100%;
  padding: 0;
  position: relative;
  /* 开启硬件加速，将视频提升为独立合成层 */
  transform: translateZ(0);
}
</style>
