自定义video标签实现快进倍速全屏,进度条拖拽,静音

410 阅读1分钟
    <template>
  <div class="video" ref="video">
    
    <video ref="myvideo"
           @canplay="getTotalTime"
           @timeupdate="timeUpdate"
           @ended="onended">
      <source :src="url" />
    </video>

    <div class="controls">
      <div class="progress">
        <span @click="togglePlay"
              :class="{'icon-play':isPaused,'icon-pause':!isPaused,'cursor':true}"></span>
        <span>{{currentTime}}</span>
        <div ref="progress"
             class="progress_bar_box cursor"
             @mousedown="getNowWh($event)"
             @mouseup="mouseup($event)">
          <div ref="onProgress"
               :class="[
              radiusLeft
                ? 'on_progress_bar_child border_Radius'
                : 'on_progress_bar_child',
            ]">
            <span v-show="!radiusLeft"
                  ref="mouseDom"
                  class="mouse_down"
                  @mousedown.stop="mousedown($event)"
                  @mouseup.stop="mouseup($event)"></span>
          </div>
        </div>
        <span>{{duration}}</span>
      </div>
      <div class='bottom-bar'>

        <div class="con_center">
          <div class="button-rate">
            <div class="rate-div-list">
              <div class="rate-div-item"
                   v-for="item in rate"
                   :key="item.id"
                   @click="bindButtonRate"
                   :data-rate='item'>{{item}} x</div>
            </div>

            <div class="rate-div">
              {{currentRate}} x
            </div>

          </div>
          <div class="button-rate">
            <div class="rate-div-list">
              <div class="rate-div-item"
                   v-for="item in fastRate"
                   :key="item.id"
                   @click="bindButtonRateFast"
                   :data-rate='item'>{{item}} s</div>
            </div>

            <div class="rate-div">
              快进
            </div>

          </div>
        </div>
        <div class="con_right">

          <span @click="toggleMuted"
                :class="{'icon-volume':!isMuted,'icon-volume-mute':isMuted,'cursor':true}"></span>
          <span @click="toggleFullScreen"
                :class="{'icon-enlarge':!isFullScreen ,'icon-shrink':isFullScreen,'cursor':true}"></span>
        </div>
      </div>
    </div>
  </div>
</template>
<script>


export default {
  name: "VideoUser",
  props: {
    url: {
      type: String,
      default: "",
    },
  },

  components: {

  },
  data() {
    return {
      //  src:’//img.tukuppt.com/video_show/2269348/00/12/95/5ddd414994581.mp4‘
      // 用于存放<video>标签对象,方便调用原生API
      myvideo: null,
      video: null,
      // 是否暂停,用于控制播放与暂停
      isPaused: true,
      // 播放当前时间
      currentTime: "00:00",
      duration: "00:00",
      // 声音是否静音
      isMuted: false,
      // 是否全屏
      isFullScreen: false,
      rate: [1, 1.5, 2, 3],
      currentRate: 1,
      fastRate: [3, 5, 10],
      timer: null, //定时器
      radiusLeft: false,

    };
  }, computed: {

  }
  ,
  mounted() {



    // 加载完毕后,先获取<video>标签DOM对象
    this.myvideo = this.$refs.myvideo
    this.video = this.$refs.video


  },
  watch: {
    url(val){
      this.url = val 
      this.myvideo.load();
    }
  },
  methods: {
    onended() {

      this.isPaused = true;
      this.currentTime = "00:00"
    },
    // 播放与暂停,注意,这里isPause的变化,对应图标的变化
    togglePlay() {
      this.isPaused = !this.isPaused;
      this.isPlay()
    },
    isPlay() {
      if (this.isPaused) {
        this.myvideo.pause()
        clearInterval(this.timer);
        this.timer = null;
      } else {
        this.myvideo.play()
        this.timer = setInterval(this.setProgress, 60);
      }
    },
    bindButtonRate(e) { // 设置倍速播放
      let rate = e.currentTarget.dataset.rate
      this.currentRate = (Number(rate))
      this.myvideo.playbackRate = this.currentRate
      this.isPlay()
    },
    //快进播放
    bindButtonRateFast(e) {
      let rate = e.currentTarget.dataset.rate
      let currentTime = (Number(rate)) + this.myvideo.currentTime
      this.myvideo.currentTime = currentTime
      this.setProgress()
      this.isPlay()

    },

    // 定义一个时间处理函数,例如将100秒,处理成1:40
    timeFormat(time) {
      let minute = Math.floor((time % 3600) / 60);  // 取余
      let second = Math.floor(time % 60);
      minute = minute < 10 ? "0" + minute : minute;
      second = second < 10 ? "0" + second : second;
      return `${minute}:${second}`;
    },
    // 当事件oncanplay触发时,获取视频的总时间信息,然后通过timeFormat函数处理成00:00的格式,并渲染到template中
    getTotalTime() {
      this.currentTime = this.timeFormat(this.myvideo.currentTime);
      this.duration = this.timeFormat(this.myvideo.duration);
    },
    // 时间timeupdate触发时,更新当前播放时间
    timeUpdate() {
      this.currentTime = this.timeFormat(this.myvideo.currentTime)
    },
    // 声音是否静音
    toggleMuted() {
      this.isMuted = !this.isMuted
      this.myvideo.muted = !this.myvideo.muted
    },
    // 切换全屏,注意,这里进入全屏时,使用的元素是this.video,而不是this.myvideo。video是myvideo的父标签,这样控制条才能正确显示
    toggleFullScreen(event) {
      let fullscreen = document.webkitIsFullScreen || document.fullscreen;
      this.isFullScreen = fullscreen
      if (!this.isFullScreen) {
        this.isFullScreen = !this.isFullScreen
        const inFun = this.video.requestFullscreen || this.video.webkitRequestFullScreen;
        inFun.call(this.video);
      } else {
        this.isFullScreen = !this.isFullScreen
        const exitFun = document.exitFullscreen || this.document.webkitExitFullScreen;
        exitFun.call(document);
      }
    },
    //设置进度条
    setProgress() {
      let progress = this.$refs.progress; //获取进度条父元素
      let onProgress = this.$refs.onProgress; //获取变化进度条的元素
      let mouseDom = this.$refs.mouseDom;
      this.currentTime = this.timeFomat(this.myvideo.currentTime); //当前播放的时间,格式化时间格式后展示
      this.duration = this.timeFomat(this.myvideo.duration); //总时长,格式化时间格式后展示
      //换算,视频没有结束或者被暂停
      if (!this.myvideo.ended) {
        this.radiusLeft = false;
        let percent = this.myvideo.currentTime / this.myvideo.duration; //得出比例
        mouseDom &&
          (mouseDom.style.left = percent * progress.offsetWidth + "px");
        onProgress &&
          (onProgress.style.width = percent * progress.offsetWidth + "px");
      } else {
        //视频已结束
        onProgress && (onProgress.style.width = "100%");
        this.radiusLeft = true;
        clearInterval(this.timer);
        this.timer = null;

      }
    },
    //点击当前位置,设置进度条
    getNowWh(event) {
      let ev = event || window.event;
      this.videoSeek(ev.offsetX);
    },
    //传入当前点击的偏移量,换算当前视频时间,并播放
    videoSeek(startx) {

      if (this.isPaused || this.myvideo.ended) {


        this.enhanceVideoSeek(startx);
      } else {
        this.myvideo.play();
        this.enhanceVideoSeek(startx);
      }
    },
    //根据鼠标拖动的距离,换算进度条
    enhanceVideoSeek(moveWidth) {
      clearInterval(this.timer);
      let progress = this.$refs.progress; //获取进度条父元素
      let onProgress = this.$refs.onProgress; //获取变化进度条的元素
      let percent = moveWidth / progress.offsetWidth;
      onProgress &&
        (onProgress.style.width = percent * progress.offsetWidth + "px");
      this.myvideo.currentTime = percent * this.myvideo.duration;
      this.timer = setInterval(this.setProgress, 60);
    },
    //鼠标按下
    mousedown(event) {
      let that = this;
      let ev = event || window.event;
      let _target = ev.target;
      let startx = ev.clientX;
      let sb_bkx = startx - ev.target.offsetLeft;
      let allwh = this.$refs.progress.clientWidth;
      let ww = document.documentElement.clientWidth;
      let wh = window.innerHeight;
      //阻止事件冒泡
      if (ev.preventDefault) {
        ev.preventDefault();
      } else {
        ev.returnValue = false;
      }
      //移动
      document.onmousemove = function (ev) {
        let evt = ev || window.event;

        if (
          evt.clientY < 0 ||
          evt.clientX < 0 ||
          evt.clientY > wh ||
          evt.clientX > ww
        ) {
          return false;
        }


        let endx = evt.clientX - sb_bkx;
        //设置拖动有效区域
        if (endx > -1 && endx < allwh) {
          _target.style.left = endx + "px";
          that.enhanceVideoSeek(endx);
        } else {
          //超出区域,置空拖动事件
          document.onmousemove = null;
        }
      };
    },
    //鼠标放开
    mouseup(e) {
      document.onmousemove = null;
    },

    /* 时间格式化 */
    timeFomat(time) {
      let h = Math.floor(time / 3600);
      let m = Math.floor((time % 3600) / 60);
      let s = Math.floor(time % 60);
      m = m >= 10 ? m : "0" + m;
      s = s >= 10 ? s : "0" + s;
      if (h === 0) {
        return m + ":" + s;
      }
      return h + ":" + m + ":" + s;
    },
  }
}
</script>
 

<style lang='scss'   scoped>
.video {
  position: relative;
}
.video video {
  width: 100%;
  height:  100%;
}
.controls {
  width: 100%;
    height: 50px;
  position: absolute;
  bottom: 0;
  left: 0;
  background-color: #22222261;
  display: flex;
 padding: 5px;
 
  flex-direction: column;
}
.controls span {
  padding: 0 5px;
  color: #fff;
}
.button-rate {
  width: 40px;
  text-align: center;
  height: 20px;
  position: relative;
  cursor: pointer;
 margin: 0 5px;
  left: -11px;
}
.rate-div {
  width: 100%;
  color: #fff;
  /* border: 1px solid #fff; */
  position: absolute;
  left: 0;

  top: 0;
}
.rate-div-list {
 color: #595959;
    text-align: center;
    visibility: hidden;
    position: absolute;
    width: 100%;
    bottom: 20px;
    background: #fff;
    z-index: 99999999;
    width: 150px;
    box-shadow: 2px 2px 2px #888888;
}
.rate-div-item:hover {
 background: #ebebeb;
}
.rate-div-item{
  padding: 10px 5px;
}
.button-rate:hover .rate-div-list {
  visibility: initial;
}
.con_center {
  display: flex;
}
.bottom-bar{
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.progress{
      margin-bottom: 5px;
    display: flex;
    justify-content: space-between;padding: 5px 0 0 0;align-items: center;
}
 
  .audio_progress {
    width: 100%;
    position: relative;
    height: 2px;
    display: flex;
    align-items: center;
    cursor:pointer;
    user-select: none;
    outline: none;background:#c1c2c3
  }
  .audio_progress_line {
    position: absolute;
    height: 2px;
    width: 100%;
    background-color: #000;
    left: 0;
    top: 50%;
    margin-top: -1px;
  }

  .audio_progress_dot {
    position: absolute;
    top: 50%;
    width: 12px;
    height: 12px;
    background-color: #000000;
    color: #c1c2c3;
    border-radius: 50%;
    transform: translate(-50%, -50%);
    cursor: pointer;
  }
   
      .progress_bar_box {
        flex: 1;
        margin:0 16px 0 16px;
        height: 8px;
        background: rgba(205, 206, 209, 0.2);
        border-radius: 5px;
        box-sizing: border-box;
        position: relative;
        .on_progress_bar_child {
          width: 0;
          height: 100%;
          background: #fff;
          border-radius:4px;
          // overflow: hidden;
          position: absolute;
          top: 0;
          left: 0;
          z-index: 1001;
          // .mouse_down {
          //   position: absolute;
          //   width: 5px;
          //   height:10px;
           
           
          //   left: 100%;
          //   background: #fff;
          //   border-radius: 50%;
          //   z-index: 1002;
          //   cursor: pointer;
          // }
        }
        .on_progress_bar_child.border_Radius {
          border-radius: 4px;
        }
      }
     .cursor{
       cursor: pointer;
     }
</style>