index.vue 10.6 KB
<template>
  <div class="video-player">
    <video
      id="my-player"
      ref="videoPlayer"
      class="video-js vjs-big-play-centered"
      controlslist="nodownload"
      crossorigin="anonymous"
      oncontextmenu="return false;"
      controls
    ></video>
  </div>
</template>

<script>
import videojs from 'video.js'
import { throttle } from '@/utils/util'
const mirrerIcon =
  '<svg t="1594374364946" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2814" width="16" height="16"><path d="M424.2 230.4c0.6 0.2 1.2 0.4 1.6 0.5L160.6 148c-2.9-0.6-6-0.9-9.2-0.9-24.5 0-44.4 19.9-44.4 44.4v634.8c0 24.6 19.9 44.5 44.4 44.5 3.2 0 6.2-0.4 9.2-0.9L432.4 785c17.9-9.2 30.1-27.9 30.1-49.3V283.2c-0.1-24.6-16.1-45.6-38.3-52.8z m-28.4 485.9l-222.1 59.2V242.3l222.1 59.2v414.8z m466.4-569.2c-3.2 0-6.2 0.4-9.2 0.9l-262 82c0.4-0.1 0.7-0.1 0.9-0.2-23.5 6.5-40.8 28-40.8 53.5v452.3c0 21.5 12.2 40.1 30.1 49.4l271.6 84.9c2.9 0.6 6.1 0.9 9.3 0.9 24.5 0 44.4-19.9 44.4-44.5V191.5c0.2-24.5-19.7-44.4-44.3-44.4z" p-id="2815" fill="#ffffff"></path></svg>'
// 热键
const leftKey = 37
const rightKey = 39
const spaceKey = 32
const enterKey = 13

export default {
  props: {
    singHoldingTime: {
      type: Object,
      default: () => {
        return {
          // signing: {
          //   beginTime: 3,
          //   endTime: 5
          // },
          // holding: [
          //   {
          //     beginTime: 20,
          //     endTime: 25
          //   }
          // ]
        }
      }
    },
    pointList: {
      type: Array,
      default: () => {
        return [
          // {
          //   text: '沙和尚',
          //   time: 3
          // },
          // {
          //   text: '沙和尚',
          //   time: 5
          // },
          // {
          //   text: '沙和尚',
          //   time: 7
          // }
        ]
      }
    },
    url: {
      type: String,
      default: ''
    },
    // 快进快退的s
    fastSeconds: {
      type: Number,
      default: 5
    },
    previewImage: {
      type: Array,
      default: () => {
        return [
          // {
          //   img: 'https://tupian.sioe.cn/b/bing-home-image/pic/20140801.jpg',
          //   time: 3
          // },
        ]
      }
    }
  },
  data() {
    return {
      player: null,
      duration: 0,
      signingColor: '#e11920',
      holdingColor: '#ffd800'
    }
  },
  computed: {
    videoEle() {
      return this.$refs.videoPlayer
    }
  },
  watch: {
    url: {
      handler(val) {
        // 重新设置video的视频来源
        if (this.player) {
          this.player.pause()
          this.player.src(val)
          this.player.load()
        }
      }
    }
  },
  mounted() {
    this.init()
  },
  beforeDestroy() {
    if (this.player) {
      this.player.dispose()
    }
  },
  // TODO 确认是否删除
  // beforeRouteLeave(to, from, next) {
  //   this.stopVideo()
  //   next()
  // },
  methods: {
    init() {
      this.player = videojs(
        'my-player',
        {
          playbackRates: [1, 1.5, 2],
          userActions: {
            // doubleClick: this.myDoubleClickHandler,
            hotkeys: this.hotkeys
          },
          controlBar: {
            fullscreenToggle: true,
            volumePanel: {
              inline: false
            }
          },
          notSupportedMessage: '视频加载失败'
        },
        () => {
          this.player.on('loadedmetadata', () => {
            this.duration = this.player.duration() // 获取视频长度
            this.createProgress()
          })
          this.player.on('timeupdate', throttle(this.timeupdate, 1000, true))
          // this.addMirrorComponents()
        }
      )
      // this.player.on('pause', () => {
      //   // Modals are temporary by default. They dispose themselves when they are
      //   // closed; so, we can create a new one each time the player is paused and
      //   // not worry about leaving extra nodes hanging around.
      //   var modal = this.player.createModal('This is a modal!')

      //   // When the modal closes, resume playback.
      //   modal.on('modalclose', () => {
      //     this.player.play()
      //   })
      // })
      this.player.src(this.url)
    },
    timeupdate() {
      // 获取视频当前播放时间
      this.$emit('timeupdate', this.videoEle.currentTime)
    },
    myDoubleClickHandler() {
      // console.log('doubleclick')
    },
    hotkeys(e) {
      if (e.which === spaceKey || e.which === enterKey) {
        this.playVideo()
      }
      if (e.which === leftKey) {
        this.fastForward(-this.fastSeconds)
      }
      if (e.which === rightKey) {
        this.fastForward(this.fastSeconds)
      }
    },
    /**
     * 暂停或播放视频
     */
    playVideo() {
      if (this.videoEle) {
        if (this.videoEle.paused) {
          this.videoEle.play()
        } else {
          this.videoEle.pause()
        }
      }
    },
    /**
     * 强制暂停
     */
    stopVideo() {
      this.videoEle.pause()
    },
    /**
     * 快进视频
     */
    fastForward(time) {
      this.videoEle.play()
      if (this.videoEle.currentTime !== 0) this.videoEle.currentTime += time
    },
    // 设置视频时间
    setVideoCurrentTime(time) {
      this.videoEle.currentTime = time - 0
    },
    // 添加Mirror镜像控件
    addMirrorComponents() {
      const domTemp = `<button class="vjs-picture-in-picture-control vjs-control vjs-button" type="button" title="Picture-in-Picture" aria-disabled="false">
      ${mirrerIcon}
      <span class="vjs-control-text" aria-live="polite">Picture-in-Picture</span>
      </button>`
      const control = document.getElementsByClassName('vjs-control-bar')[0]
      const span = document.createElement('span')
      span.innerHTML = domTemp
      control.appendChild(span)
      let bool = true
      span.addEventListener('click', () => {
        const video = document.getElementsByTagName('video')[0]
        bool = !bool
        bool
          ? (video.style.transform = 'rotateY(0)')
          : (video.style.transform = 'rotateY(180deg)')
      })
    },
    createProgress() {
      // 视频进度条做标记
      const progress = document.getElementsByClassName('vjs-progress-holder')[0]
      const delDotDiv = document.getElementsByClassName(
        'video-face-pair-list'
      )[0]
      delDotDiv && progress.removeChild(delDotDiv)
      const dotDiv = document.createElement('div')
      dotDiv.setAttribute('class', 'video-face-pair-list')
      // 视频人脸对比标记
      Object.keys(this.singHoldingTime).forEach(a => {
        const item = this.singHoldingTime[a]
        const colorDiv = document.createElement('div')
        colorDiv.setAttribute('class', 'face-pair')
        if (Array.isArray(item)) {
          item.forEach(sonItem => {
            if (sonItem.beginTime < 0) return
            colorDiv.style.width =
              ((sonItem.endTime - sonItem.beginTime) / this.duration) * 100 +
              '%'
            colorDiv.style.left =
              (sonItem.beginTime / this.duration) * 100 + '%'
            colorDiv.style.background = this.holdingColor
          })
        } else {
          if (item.beginTime < 0) return
          colorDiv.style.width =
            ((item.endTime - item.beginTime) / this.duration) * 100 + '%'
          colorDiv.style.left = (item.beginTime / this.duration) * 100 + '%'
          colorDiv.style.background = this.signingColor
        }
        dotDiv.appendChild(colorDiv)
      })
      // 视频预览图
      this.previewImage.forEach(item => {
        const spanDom = document.createElement('span')
        spanDom.classList.add('preview-image-dot')
        spanDom.style.left = `${(item.time / this.duration) * 100}%`
        spanDom.innerHTML = `<div class="preview-wrapper">
            <img
              src="${item.img}"
              alt=""
            />
          </div>`
        dotDiv.appendChild(spanDom)
      })
      // 视频关键打点
      this.pointList.forEach(item => {
        const spanDom = document.createElement('span')
        spanDom.classList.add('video-dot')
        spanDom.style.left = `${(item.time / this.duration) * 100}%`
        spanDom.innerHTML = `<div class="video-dot-tip">
           ${item.text}
          </div>`
        dotDiv.appendChild(spanDom)
      })

      progress.appendChild(dotDiv)
    }
  }
}
</script>
<style lang="scss" scope>
.video-player {
  width: 100%;
  height: 100%;
  position: relative;
  .video-js {
    height: 100%;
    width: 100%;
  }
  #my-player.video-js {
    .video-face-pair-list {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      .face-pair {
        position: absolute;
        height: inherit;
      }
      .video-dot {
        position: absolute;
        top: -2px;
        width: 8px;
        height: 8px;
        border-radius: 50%;
        background-color: #fff;
        overflow: visible;
        z-index: 1000;
        box-shadow: 0 0 2px 2px #999;
      }
      .video-dot:hover {
        cursor: pointer;
      }
      .video-dot-tip {
        position: absolute;
        display: none;
        top: -76px;
        left: -75px;
        max-width: 250px;
        min-width: 150px;
        max-height: 60px;
        min-height: 25px;
        line-height: 25px;
        padding: 5px;
        background-color: #fff;
        box-shadow: 0 0 -2px 3px #adadad;
        overflow: auto;
        letter-spacing: 1px;
        color: #3d3d3d;
        border-radius: 5px;
        z-index: 10;
        font-size: 12px;
        font-family: 'Microsoft YaHei';
      }
      .video-dot:hover {
        .video-dot-tip {
          display: block;
        }
      }
      .preview-image-dot {
        display: inline-block;
        position: absolute;
        left: 0;
        top: -11px;
        width: 0;
        height: 0;
        border-right: 5px solid #1a1f26;
        border-left: 5px solid #1a1f26;
        // border-top: 4px solid #3248a8;
        border-top: 5px solid #fff;
        .preview-wrapper {
          display: none;
          position: absolute;
          bottom: 18px;
          left: -50px;
          width: 130px;
          height: 80px;
          object-fit: contain;
          img {
            width: 100%;
            height: 100%;
          }
        }
      }
      .preview-image-dot:hover {
        background-color: lightblue;
        .preview-wrapper {
          display: block;
        }
      }
    }
  }
}
</style>
<style lang="scss">
.vjs-paused .vjs-big-play-button,
.vjs-paused.vjs-has-started .vjs-big-play-button {
  display: block;
}
.vjs-slider-vertical .vjs-volume-level:before {
  left: -0.4em;
}
.video-player .vjs-picture-in-picture-control {
  display: none;
}
</style>