如何快速开发一个自定义的视频播放器(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在运行的过程中也会遇到很多的问题,下一篇我们会写一下实际运行的问题和改正的方法------项目调试

相关推荐
咖啡の猫30 分钟前
Shell脚本-for循环应用案例
前端·chrome
百万蹄蹄向前冲3 小时前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
朝阳5813 小时前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路4 小时前
GeoTools 读取影像元数据
前端
ssshooter4 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
你的人类朋友4 小时前
【Node.js】什么是Node.js
javascript·后端·node.js
Jerry5 小时前
Jetpack Compose 中的状态
前端
dae bal6 小时前
关于RSA和AES加密
前端·vue.js
柳杉6 小时前
使用three.js搭建3d隧道监测-2
前端·javascript·数据可视化
lynn8570_blog6 小时前
低端设备加载webp ANR
前端·算法