方法实现
本篇主要介绍上篇定义的所有方法的实现,写方法实际是逻辑分解的过程,当我们把我们的逻辑分解的足够小的时候,其实所有的方法都很容易实现。这里推荐一本书《福格行为模型》,当人们遇到复杂的事情的时候,一开始是抗拒不愿意去做的,但是当我们把事情分解到足够简单的多件事情时,每完成一个简单事情都能够正向鼓励自己,这才是实现复杂目标和事物的正确方法。事情的道理都是一样的,编码也一样,逻辑分解到足够小的时候,任何复杂的算法逻辑都很简单。
- 初始化。根据我们需求,初始化我们的播放去需要对视频的几个事件进行监听:
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
}
- 播放事件,这个比较简单,当视频暂停时播放,当视频播放时暂停
javascript
PlayVideo() {
// this.video.target.paused 可以获取到视频的暂停状态
if (this.video.target.paused) {
this.video.target.play()
} else {
this.video.target.pause()
}
}
- 全屏事件,全屏事件不同浏览器提供的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()
}
}
- 控制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)
}
},
- 点击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
Ï
})
}
- indicator指示器拖动,由于我们仅期望本demo运行在移动端,因此我们实现touchstart、touchmove、touchend即可,基本的逻辑就是:
- 按下indicator时刻暂停视频播放同时纪录拖动开始位置,需要注意的是我们的indicator是一个圆,我们需要精确计算到实际的滚动范围,因此我们在按下手指的时候,同时纪录一下indicator和手指距离的实际偏移
- 拖动过程种不断计算我们的滚动条宽度和indicator的偏移,实际上简单的做法是我们只需要计算时间的变化即可(取巧方法,不用进行复杂思考),宽度和indicator的偏移我们可以根据时间也就是currentTime转换得到,这个已经在ontimeupdate里面实现。
- 拖动结束,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在运行的过程中也会遇到很多的问题,下一篇我们会写一下实际运行的问题和改正的方法------项目调试