最近再尝试Vue播放视频,结果没想到有人让我保存进度,有点懵逼,不过觉得还是可以挑战一下的。
首先先用Vue项目去集成Vue-Video-Player(https://github.com/surmon-china/vue-video-player),具体的集成步骤不再赘述。
集成完之后先做出来一般基础的框架,实现视频的播放功能。
代码如下:
<video-player class="video-player vjs-custom-skin" style="margin-bottom: 20px;" ref="videoPlayer" :playsinline="true"
:options="playerOptions"
></video-player>
</div>
import { videoPlayer } from 'vue-video-player'
import 'video.js/dist/video-js.css'
export default {
data () {
return {
paused: true,
learningDuration: {
id: '',
userId: '',
type: '0',
examinationId: '',
finishFlag: '0',
durations: ''
},
playerOptions: {
playbackRates: [0.7, 1.0, 1.5, 2.0], // 播放速度
autoplay: false, // 如果true,浏览器准备好时开始回放。
muted: false, // 默认情况下将会消除任何音频。
loop: false, // 导致视频一结束就重新开始。
preload: 'auto', // 建议浏览器在<video>加载元素后是否应该开始下载视频数据。auto浏览器选择最佳行为,立即开始加载视频(如果浏览器支持)
language: 'zh-CN',
aspectRatio: '16:9', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3")
fluid: false, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。
sources: [{
// src: '../../static/video/test.mp4', // 路径
src: ''
// type: 'video/mp4' // 类型
}],
poster: '../../../static/images/home/cnooc.png', // 你的封面地址
// width: document.documentElement.clientWidth,
notSupportedMessage: '此视频暂无法播放,请稍后再试', // 允许覆盖Video.js无法播放媒体源时显示的默认信息。
controlBar: {
timeDivider: true,
durationDisplay: true,
remainingTimeDisplay: false,
fullscreenToggle: true // 全屏按钮
}
}
}
},
components: {
videoPlayer
},
上面的视频可以用src注释掉的那一段使用本地视频,也可以使用动态后台获取的,如果是后台网络获取的话再vue的created模块使用如下方式(我使用的FastDfs存储的文件,你们可以随意,后面只相当于一个网络访问地址):
created () {
this.playerOptions.sources[0].src = this.sysConfig.fdfsHttpHost + '/' + this.training.examAvatar
}
添加完成之后发现可以使用Vue进行视频播放了,但是要实现进度保存就有点麻烦了,首先要分析如果要实现进度保存,那么一共有两个步骤:
1. 播放进度保存
2. 播放进度恢复
所谓进度保存就是每次播放视频的时候需要把播放进度保存到后台数据库,这样下次用户再次进行视频播放的时候可以读取的到上次播放的位置,不然没有意义了。
进度恢复也比较好理解,就是当用户再次播放视频的时候,可以根据上次播放的进度进行进度恢复。
那么就要开始想办法实现了:
一、进度保存
要想保存进度,必须要理清楚怎么获取得到视频的当前播放位置,查阅文档,可以发现Vue-Video-Player 有事件监听机制,事件如下:
<video-player class="video-player vjs-custom-skin"
ref="videoPlayer"
:playsinline="true"
:options="playerOptions"
@play="onPlayerPlay($event)"
@pause="onPlayerPause($event)"
@ended="onPlayerEnded($event)"
@loadeddata="onPlayerLoadeddata($event)"
@waiting="onPlayerWaiting($event)"
@playing="onPlayerPlaying($event)"
@timeupdate="onPlayerTimeupdate($event)"
@canplay="onPlayerCanplay($event)"
@canplaythrough="onPlayerCanplaythrough($event)"
@ready="playerReadied"
@statechanged="playerStateChanged($event)">
</video-player>
// 对应的methods
methods: {
//监听播放
onPlayerPlay (player) {
console.log(player);
// this.$refs.videoPlayer.player.play();
},
//监听暂停
onPlayerPause (player) {
console.log(player);
// this.$refs.videoPlayer.player.pause();
},
//监听播放状态改变
playerStateChanged (player) {
// console.log(player);
},
//监听媒体是否已到达结尾,播放完
onPlayerEnded (player) {
// console.log(player);
},
//DOM元素上的readyState更改导致播放停止。
onPlayerWaiting (player) {
// console.log(player);
},
//媒体不再被禁止播放,并且已开始播放。
onPlayerPlaying (player) {
// console.log(player);
},
//当播放器在当前播放位置下载数据时触发
onPlayerLoadeddata (player) {
// console.log(player);
},
//当前播放位置发生变化时触发。
onPlayerTimeupdate (player) {
// console.log(player);
},
//媒体的readyState为HAVE_FUTURE_DATA或更高
onPlayerCanplay(player) {
// console.log('player Canplay!', player)
},
//媒体的readyState为HAVE_ENOUGH_DATA或更高。这意味着可以在不缓冲的情况下播放整个媒体文件。
onPlayerCanplaythrough(player) {
// console.log('player Canplaythrough!', player)
},
//将侦听器绑定到组件的就绪状态。与事件监听器的不同之处在于,如果ready事件已经发生,它将立即触发该函数。。
playerReadied(player) {
// seek to 10s
console.log('example player 1 readied', player);
// player.currentTime(0)
// console.log('example 01: the player is readied', player)
}
},
根据console.log发现,可以在onPlayerTimeupdate 中的player.cache_.currentTime获取到播放时间,所以可以保存下来:
onPlayerTimeupdate (player) {
this.durations = player.cache_.currentTime
// console.log(' onPlayerTimeupdate!', player)
},
然后要想办法保存下来,刚开始我是想直接在这个位置去请求后台保存数据,但是后来发现这个方法的触发时间太快了,会对后台造成很大的请求负载,所以打算使用定时器去处理:
mounted () {
this.putLearningObj()
setInterval(this.putLearningObj, 3000)
},
methods: {
putLearningObj () {
if (!this.paused) {
putObj(this.learningDuration)
console.log('putLearningObj ~~~~~~~~~')
}
},
onPlayerPlay (player) {
this.paused = false
// console.log('onPlayerPlay!', player)
},
onPlayerPause (player) {
this.paused = true
// console.log('onPlayerPause!', player)
},
onPlayerEnded (player) {
this.paused = false
console.log('player ended!', player)
}
}
意思就是,在mounted过程中设置一个定时器3s会触发一次,但是又考虑到用户暂停的时候不需要进行进度保存,所以设置了一个flag标识——paused。当用户暂停的时候就不需要去请求后端了。
(关于putObj 是我自己定义的一个请求后端的方式,你们可以改成你们自己的,去保存当前进度)
以上就实现了进度保存功能,关于后台处理就是一些Rest请求然后链接数据库的操作了 ,也不再赘述。
二、进度恢复
所谓进度恢复,那只能在视频播放之前就要确定好播放位置。
1.刚开始的思路是在vue的created()中进行后台数据请求,拿到播放进度之后在playerReadied 进行赋值,后来发现playerReadied的加载比created快,所以这个方式不行。
2. 换个思路打算在playerReadied中请求后台数据,然后进行赋值,修改playerReadied 方法如下:
playerReadied (player) {
getObj(this.learningDuration).then(response => {
console.log(response.data.data)
if (response.data !== null) {
this.learningDuration = response.data.data
player.currentTime(this.learningDuration.durations)
}
}).catch(() => {
notifyFail(this, '获取失败')
})
},
刚开始习惯在最后输入一个console.log()来打印一次player,后来发现log一直赋值失败,想半天才明白console.log不会等请求结束才打印,当时还想着用等待接口返回,后来一想也是傻,只要在这个方法进行数据逻辑处理,什么时候赋值也没有任何影响。
下面是坑!!!请注意!!!
我在开启定时器之后,不小心点了下别的页面,然后发现控制台还有定时器在一直在请求,所以一定要记得销毁!
修改代码如下:
mounted () {
this.timer = setInterval(this.putLearningObj, 3000)
},
destroyed () {
// 如果定时器在运行则关闭
if (this.timer) {
clearInterval(this.timer)
}
}
以上。
前端小白,如有大佬有更好的处理方案,欢迎讨论~