需求

一个进度条组件控制多个视频的播放、进度调整。视频可点击全屏观看,唯一的进度条是某个指定视频的视频信息。

图示

一个进度条控制多个视频的播放

实现

html

//视频
 <div
        v-for="(item, index) in urls"
        :key="index"
      >
        <video :ref="item.type"  preload="auto" :src="item.url" />
        <div>
        // svg-icon是icon图标组件 
          <svg-icon v-if="!isFullScreen" :style="{ fill: '#dbdbdb', 'font-size': 20 + 'px' }" icon-class="quanping_o" @click="handleFullScreen(item)" />
          <svg-icon v-else :style="{ fill: '#dbdbdb', 'font-size': 20 + 'px' }" icon-class="quxiaoquanping_o" @click="handleFullScreen(item)" />
        </div>
      </div>
//进度条
 <div v-show="fileUrl" class="progress-wrap">
        <div class="operate">
          <div class="speed">
          //倍速列表
            <el-menu class="list" mode="horizontal">
              <el-menu-item v-for="(speed, i) in speedList" :key="i" class="list-item" @click="handleChangeSpeed(speed)">{{ speed }}</el-menu-item>
            </el-menu>
            //显示倍速
            {{ playSpeed }}x
          </div>
          <div>
            <div class="play-wrap" @click="play">
              <svg-icon v-if="!paused" icon-class="zanting" :style="{ 'font-size': 14 + 'px' }" />
              <svg-icon v-else-if="paused" icon-class="bofang" :style="{ 'font-size': 14 + 'px' }" />
            </div>
            // 显示已播放时长和总时长
            <div class="timer">{{ currentTime }} / {{ totalTime }}</div>
          </div>
        </div>
//进度条容器
        <div ref="control" class="control" @click="adjustProgress($event)">
        // 进度条本条
          <div class="progress" :style="{ width: progressWidth }">
          //滑块
            <div class="slider_circle" @mousedown="handleSliderMouseDown" />
            <div class="slider_circle_large" />
          </div>
        </div>
      </div>

vue.js

全屏

 handleFullScreen(item) {
      this.isFullScreen = !this.isFullScreen //isFullScreen 定义为布尔值,控制样式用的,给视频容器高度设置为100vh,宽度100%实现全屏显示
      this.fullVideoType = item.type  //控制样式用的,不必要
    },

点击进度条跳转

在这里插入图片描述

 adjustProgress(e) {
      e.preventDefault()
      // 这里的this.controlRef = this.$refs.control,加载完视频后定义即可
      const { left, width } = this.controlRef.getBoundingClientRect()
      // left: 进度条容器control到最左侧的距离,width:容器的宽度
        // e.clientX:鼠标点击的位置到最左侧的距离
      const progressWidth = e.clientX - left
      this.progressWidth = progressWidth + 'px'
      this.updadteCurrentTime(progressWidth, width)
    },

拖动滑块

在菜鸟教程上有以下几个参数的详细解说,这张图忘记哪里看的了,如有认领可评论我贴链接
在这里插入图片描述

  handleSliderMouseDown(event) {
  //如果不添加以下两处的阻止默认事件,会出现以下情况: 鼠标点击滑块向前拉动,移出进度条范围时,会自动选择文字等元素,出现禁用图标。松开鼠标,再次进入进度条,即使没有按住滑块,滑块也会跟随鼠标移动。这不是我们想要看到的效果。
      event.preventDefault()
      // 滑块点击坐标
      const offsetX = event.offsetX
      document.onmousemove = (e) => {
        e.preventDefault()
        // 滑动距离可视区域左侧的距离
        const X = e.clientX
        // 减去滑块偏移量
        const cl = X - offsetX
        const { left, width } = this.controlRef.getBoundingClientRect()
        // 除去滑块按钮长度的进度条长度
        const ml = cl - left
        let progressWidth
        if (ml <= 0) {
        //进度条长度最小和最大值的界定
          progressWidth = 0
        } else if (ml >= width) {
          progressWidth = width
        } else {
          progressWidth = ml
        }
        this.progressWidth = progressWidth + 'px'
        // 更新当前时间
        this.updadteCurrentTime(progressWidth, width)
      }
//抬起鼠标,结束移动事件
      document.onmouseup = () => {
        document.onmousemove = null
        document.onmouseup = null
      }
    },

倍速

// 倍速
handleChangeSpeed(item) {
this.playSpeed = item
},

播放暂停

 play() {
 //是否暂停
      this.paused = !this.paused
      // 若此刻状态是重新播放,那么点击播放时,进度条需从头开始前进
      // 进度条控制的是中间视频,this.middleRef
      if (this.middleRef.duration === this.middleRef.currentTime) {
        this.middleRef.currentTime = 0
        this.currentTime = this.formatSeconds(this.middleRef.currentTime)
        this.progressWidth = 0 + 'px !important'
      }
      if (!this.paused) {
      // 定时器实时刷新
        this.timer = setInterval(this.updateVideoProgress, 50)
        this.updateVideoProgress()
      }
      this.videoRefArr.forEach((v) => {
        v.currentTime = this.middleRef.currentTime
        if (this.paused) {
          v.pause()
        } else {
          v.play()
          // 按倍速播放
          v.playbackRate = this.playSpeed
        }
      })
    },
    clearTimer() {
      if (this.timer) {
        clearInterval(this.timer)
        this.timer = null
      }
    },
      // 更新播放时进度条长度
    updateVideoProgress() {
    //按照已播放时间和总时长的比例更新进度条长度
      this.progressWidth = (this.middleRef.currentTime / this.middleRef.duration) * 100 + '%'
      this.currentTime = this.formatSeconds(this.middleRef.currentTime)
      this.totalTime = this.formatSeconds(this.middleRef.duration)
    
      // 放完、暂停这两种情况,遍历所有视频,使他们的状态一致,并清除定时器
      if (this.middleRef.currentTime === this.middleRef.duration || this.paused) {
        this.videoRefArr.forEach((v) => {
          v.pause()
        })
        this.paused = true
        this.clearTimer()
      }
    },
    // 格式化时间 
    formatSeconds(value) {
      const result = parseInt(value)
      const h = Math.floor(result / 3600) < 10 ? '0' + Math.floor(result / 3600) : Math.floor(result / 3600)
      const m = Math.floor((result / 60) % 60) < 10 ? '0' + Math.floor((result / 60) % 60) : Math.floor((result / 60) % 60)
      const s = Math.floor(result % 60) < 10 ? '0' + Math.floor(result % 60) : Math.floor(result % 60)
      //把视频时长格式化到毫秒
      let ms
      const msValue = value.toFixed(3).toString().split('.')[1]
      if (Math.floor(msValue % 1000) < 10) {
        ms = '00' + Math.floor(msValue % 1000)
      } else if (Math.floor(msValue % 1000) > 10 && Math.floor(msValue % 1000) < 100) {
        ms = '0' + Math.floor(msValue % 1000)
      } else if (Math.floor(msValue % 1000) < 1000) {
        ms = Math.floor(msValue % 1000)
      }
      let res = ''
      res += `${h}:`
      res += `${m}:`
      res += `${s}.`
      res += `${ms}`
      return res
    }

跳转、拖动后更新进度条长度是通过更新视频的currentTime

 // 更新当前时间、帧号
    updadteCurrentTime(progressWidth, width) {
      this.currentTime = this.formatSeconds((progressWidth / width) * this.middleRef.duration)
      this.totalTime = this.formatSeconds(this.middleRef.duration)
      this.videoRefArr.forEach((v) => {
        v.currentTime = (progressWidth / width) * this.middleRef.duration
      })
    },

总结:公司内部使用需要写这样一个组件。虽然比较不通用,但是比较灵活。html结构为了看的直观点,省略了相关样式。

有感兴趣的可以一起讨论。

原创不易,转载请贴出处

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐