如何快速开发一个自定义的视频播放器(3)——方法实现

方法实现

本篇主要介绍上篇定义的所有方法的实现,写方法实际是逻辑分解的过程,当我们把我们的逻辑分解的足够小的时候,其实所有的方法都很容易实现。这里推荐一本书《福格行为模型》,当人们遇到复杂的事情的时候,一开始是抗拒不愿意去做的,但是当我们把事情分解到足够简单的多件事情时,每完成一个简单事情都能够正向鼓励自己,这才是实现复杂目标和事物的正确方法。事情的道理都是一样的,编码也一样,逻辑分解到足够小的时候,任何复杂的算法逻辑都很简单。

  1. 初始化。根据我们需求,初始化我们的播放去需要对视频的几个事件进行监听:

1.onloadedmetadata 监听加载metadata信息 获取duration信息

2.ontimeupdate 监听播放进度的事件 主要用来进度条控制

3.onpause 视频暂停监听 进行部分视频状态控制

4.onplay 视频开启播放监听

同时初始化视频的相关信息,这里我们做出如下实现:

javascript 复制代码
 LoadVideo(video) {
      // 监听加载metadata信息 获取duration信息
      video.onloadedmetadata = () => {
        this.video.duration = video.duration
        this.video.durationString = this.GetTime(video.duration)
      }
      // 监听播放进度的事件 主要用来进度条控制
      video.ontimeupdate = () => {
        // 获取currentTime
        var ctime = +video.currentTime.toFixed(2)
        this.video.currentTime = ctime
        // 更新currentTime字符串
        this.video.currentTimeString = this.GetTime(ctime)
        // 更新当前进度百分比 currentTime/totalTime*100
        this.video.progress = (ctime * 100) / this.video.duration
        // 进度条宽度设置
        this.$refs.progressCurrentLine.style.width = `${this.video.progress}%`
        
        // indicator的偏移位置 由于指示器是一个圆,因此需要计算指示器圆心的位置 currentTime/totalTime*进度条总宽度,因为开始时圆心在起点,因此再减去indicator的半径即可得到偏移位置
        var transformx =
          (ctime * this.$refs.progress.offsetWidth) / this.video.duration -
          this.$refs.indicator.offsetWidth / 2
        // 这里使用transform而不是用left后面会解释
        this.$refs.indicator.style.transform = `translateY(-50%) translateX(${transformx}px)`
        // 判断视频结束,冷知识,video没有结束监听事件
        if (video.currentTime >= this.video.duration) {
          this.video.status = 'stop'
        }
      }
      // waiting监听 可能有用 有些环境没有用
      video.onwaiting = () => {
        // 播放状态下才会触发waiting事件
        this.video.status = 'wait'
      }
      // 视频资源加载到足够播放事件监听
      video.oncanplay = () => {
        // 如果是等待状态,说明加载好就要进行播放。
        if (this.video.status === 'wait') {
          this.video.status = 'play'
        }
      }
      // 视频暂停监听
      video.onpause = () => {
        this.video.status = 'pause'
        this.video.starting = false
        // 是否展示start play btn 正常视频结束才展示,拖动indicator时的视频暂停并不展示
        if (this.video.isSlider == false && this.video.status !== 'stop') {
          this.showStartPlay = true
        }
        // 暂停时展示control pannel,且不隐藏
        this.showPannel = true
      }
      // 视频开启播放监听
      video.onplay = () => {
        console.log('start play')
        this.video.starting = true
        this.ShowControls()
        this.video.status = 'play'
        this.showStartPlay = false
      }

      this.video.target = video
    }
  1. 播放事件,这个比较简单,当视频暂停时播放,当视频播放时暂停
javascript 复制代码
 PlayVideo() {
      // this.video.target.paused 可以获取到视频的暂停状态
      if (this.video.target.paused) {
        this.video.target.play()
      } else {
        this.video.target.pause()
      }
    }
  1. 全屏事件,全屏事件不同浏览器提供的api有所差别需要进行兼容性处理
javascript 复制代码
 SetFullScreen() {
      const video = this.video.target
      if (video.requestFullscreen) {
        video.requestFullscreen()
      } else if (video.mozRequestFullScreen) {
        /* Firefox */
        video.mozRequestFullScreen()
      } else if (video.webkitRequestFullscreen) {
        /* Chrome, Safari 和 Opera */
        video.webkitRequestFullscreen()
      } else if (video.msRequestFullscreen) {
        /* IE/Edge */
        video.msRequestFullscreen()
      }
    }
  1. 控制control pannel展示事件,这里有两个逻辑需求,一个是基本的展示后隐藏需求,一个是在播放过程种可以随时点击control mask使得control pannel即时展示和隐藏,对应两个方法
javascript 复制代码
    // 基本的control pannel控制 time为默认展示时间
    ShowControls(time = 2000) {
      this.video.showTime = time
      this.showPannel = true
      // 基本防抖
      if (this.video.showTimer) {
        clearTimeout(this.video.showTimer)
      }
      // 一段时间后隐藏
      this.video.showTimer = setTimeout(() => {
        this.showPannel = false
      }, this.video.showTime)
    },
    // 播放过程种随时点击control mask使得control pannel即时展示和隐藏
    ShowControl() {
      if (this.video.showPannel === true) {
        if (this.video.showTimer) {
          clearTimeout(this.video.showTimer)
        }
        // 如果control pannel正在展示,则立即隐藏
        this.video.showPannel = false
      } else {
        this.ShowControls(3000)
      }
    },
  1. 点击seek实现,点击控制调计算点击位置的currentTime,seek这个时刻即可,因为我们在ontimeupdate已经实现了进度条展示和indicataor的定位逻辑,因此这里我们只需计算出currentTime即可
javascript 复制代码
 Seek(e) {   
      this.$nextTick(() => { 
        // 计算方法比较简单根据offsetX和进度调的偏移来计算
        this.video.target.currentTime =
          (e.offsetX / this.$refs.progress.offsetWidth) * this.video.duration
         Ï
      })
    }
  1. indicator指示器拖动,由于我们仅期望本demo运行在移动端,因此我们实现touchstart、touchmove、touchend即可,基本的逻辑就是:
  1. 按下indicator时刻暂停视频播放同时纪录拖动开始位置,需要注意的是我们的indicator是一个圆,我们需要精确计算到实际的滚动范围,因此我们在按下手指的时候,同时纪录一下indicator和手指距离的实际偏移
  2. 拖动过程种不断计算我们的滚动条宽度和indicator的偏移,实际上简单的做法是我们只需要计算时间的变化即可(取巧方法,不用进行复杂思考),宽度和indicator的偏移我们可以根据时间也就是currentTime转换得到,这个已经在ontimeupdate里面实现。
  3. 拖动结束,seek并播放视频

具体代码

javascript 复制代码
 TouchStart(e) {
      // 纪录拖动状态
      this.video.isSlider = true
      this.video.target.pause()
      var pageX = e.touches[0].pageX
      this.video.istartX = pageX
      // 这里通过则匹配获取indicator的偏移,因为不是left而是translateX
      var transformX = this.$refs.indicator.style.transform.match(
        /translateX\((.*?)px\)/
      )[1]
      // 计算实际偏移
      this.video.istartOffsetX =
        pageX - (+transformX + this.$refs.progress.offsetLeft)
    },
    TouchMove(e) {
      this.video.target.pause()
      var pageX = e.touches[0].pageX
      // 计算手指x轴偏移距离
      var idistance = pageX - this.video.istartX
      // 判断偏移距离对应的偏移时间
      var moveTime =
        (idistance / this.$refs.progress.offsetWidth) * this.video.duration
      // set left 计算实际的left距离
      var left = this.video.istartX + idistance - this.video.istartOffsetX
      // indicator的x轴的最小偏移和最大偏移,拖动不可超出时间轴
      // 最大偏移=轴左偏移+轴宽度+指示器半径
      const maxx =
        this.$refs.progress.offsetLeft +
        this.$refs.progress.offsetWidth -
        this.$refs.indicator.offsetWidth / 2
      // 最小偏移=轴左偏移-指示器半径
      const minx =
        this.$refs.progress.offsetLeft - this.$refs.indicator.offsetWidth / 2
      // 只有手指在合理范围内才更新进度条和指示器
      if (left <= maxx && left >= minx) {
        // 计算移动时的实际时间
        var currentTime = moveTime + this.video.currentTime
        // 根据时间计算进度条宽度和指示器偏移
        this.video.offsetCurrentTime = currentTime
        this.video.progress = (currentTime * 100) / this.video.duration
        this.$refs.progressCurrentLine.style.width = `${this.video.progress}%`
        var transformx =
          (currentTime * this.$refs.progress.offsetWidth) /
            this.video.duration -
          this.$refs.indicator.offsetWidth / 2
        this.$refs.indicator.style.transform = `translateY(-50%) translateX(${transformx}px)`
      }
    },
    TouchEnd(e) {
      // 取消拖动状态
      this.video.isSlider = false
      // 直接seek时间即可
      this.video.target.currentTime = +this.video.offsetCurrentTime.toFixed(2)
      // 兼容一些浏览器seek后不播放的问题(正常浏览器seek后自动播放)
      this.video.target.play()
      this.ShowControls()
    },

到这里我们的方法基本都实现了,我们可以运行一下看看手机上的各个端都有什么问题。这里推荐一下本人自研的局域网移动调试方法------移动开发快速预览调试的实现,本方法已成功申请专利。

总结

写代码和读书一样都容易产生心流,这也是我们热爱coding的一大主要原因,但是写是写出来了,效果会怎样呢?似乎没有第一次运行就满意的程序,我们这个demo在运行的过程中也会遇到很多的问题,下一篇我们会写一下实际运行的问题和改正的方法------项目调试

相关推荐
abc80021170342 小时前
前端Bug 修复手册
前端·bug
Best_Liu~2 小时前
el-table实现固定列,及解决固定列导致部分滚动条无法拖动的问题
前端·javascript·vue.js
_斯洛伐克3 小时前
下降npm版本
前端·vue.js
苏十八4 小时前
前端进阶:Vue.js
前端·javascript·vue.js·前端框架·npm·node.js·ecmascript
st紫月4 小时前
用MySQL+node+vue做一个学生信息管理系统(四):制作增加、删除、修改的组件和对应的路由
前端·vue.js·mysql
乐容5 小时前
vue3使用pinia中的actions,需要调用接口的话
前端·javascript·vue.js
似水明俊德5 小时前
ASP.NET Core Blazor 5:Blazor表单和数据
java·前端·javascript·html·asp.net
至天6 小时前
UniApp 中 Web/H5 正确使用反向代理解决跨域问题
前端·uni-app·vue3·vue2·vite·反向代理
与墨学长6 小时前
Rust破界:前端革新与Vite重构的深度透视(中)
开发语言·前端·rust·前端框架·wasm
H-J-L7 小时前
Web基础与HTTP协议
前端·http·php