video.js 是一款优秀的视频播放器组件, 官网的文档是直接以 JS 标签引入的方式使用的, 对 ES6 模块风格的方式完全空白, 本文记录一下我在 vue 项目中是如何使用 video.js 的(踩过的坑)
安装与使用
输入命令行, 下载video.js
cnpm i video.js
main.js中引入
import Video from 'video.js'
import 'video.js/dist/video-js.css'
Vue.prototype.$video = Video;
为了复用, 可以新建一个Player.vue组件, 然后在template中加入video标签:
<template>
<video ref="video"
controls
class="video-js vjs-default-skin vjs-big-play-centered">
</video>
</template>
覆盖全局样式:
<style>
.vjs-big-play-button {
height: 3em !important;
width: 3em !important;
line-height: 3em !important;
border-radius: 50% !important;
}
.vjs-button > .vjs-icon-placeholder:before {
display: flex;
justify-content: center;
align-items: center;
font-size: 2em;
}
.vjs-slider-horizontal .vjs-volume-level:before {
top: -0.333333333333333em;
right: -0.5em;
}
.vjs-playback-rate .vjs-playback-rate-value {
font-size: 1.8em;
display: flex;
align-items: center;
justify-content: center;
}
.vjs-control-bar {
background: rgba(0,0,0,.8) !important;
}
.video-js .vjs-play-progress, .video-js .vjs-volume-level {
background: linear-gradient(to right, #FF7B02, #FFA604) !important;
}
.video-js:hover .vjs-big-play-button, .vjs-custom-skin > .video-js .vjs-big-play-button:focus, .vjs-custom-skin > .video-js .vjs-big-play-button:active {
background: linear-gradient(to right, #FF7B02, #FFA604) !important;
opacity: .7;
}
.vjs-big-play-button .vjs-icon-placeholder {
font-size: 70px !important;
}
.vjs-error .vjs-error-display:before {
color: #fff !important;
content: 'X' !important;
font-family: Arial, Helvetica, sans-serif !important;
font-size: 4.5em !important;
left: 0 !important;
line-height: 1.8 !important;
margin-top: -0.5em !important;
position: absolute !important;
text-shadow: 0.05em 0.05em 0.1em #000 !important;
text-align: center !important;
top: 50% !important;
vertical-align: middle !important;
width: 100% !important;
}
</style>
因为我的项目即 唯舞 , 使用的是黑色皮肤, 所以这套样式也是黑色的
Vue挂载完成后初始化video标签:
mounted() {
const vm = this;
this.player = this.$video(this.$refs.video, this.options, function () {
this.on('play', () => {// 播放事件发生时, 立刻调整音量至用户已设置过的音量
this.volume(vm.video.volume);
});
this.on('volumechange', () => {// 音量调整事件
vm.video.volume = this.volume();
window.localStorage.volume = this.volume();
});
this.on('error', () => {// 因为网络加载失败时 重新尝试
let err = this.error();
if (err.code === 2) {// 判断是否是网络加载问题
let buffTime = this.bufferedEnd() - 6;
this.load(vm.src);
this.currentTime(buffTime);
this.play();
} else {
vm.alert.err(err.message);
}
})
});
},
因为 video 很多页面都会使用, 为了用户体验, 用户调整一次音量后是肯定是希望记住该音量大小, 下次在刷新页面后就不需要再次调整音量
这个需求也很简单,如上监听 volumechange
事件, 然后将用户调整的音量存进 Vuex,全局通用,再储存一份至 localStorage
里, 页面刷新的时候从 localStorage
里加载音量值, 同步音量大小
在App.vue中如下设置即可:
beforeCreate() {
let volume = window.localStorage.volume;
if (volume) {
this.$store.state.video.volume = volume;
}
}
当视频路径发生变化时, 重新加载视频资源:
watch: {
src() {
this.player.src(this.src);
}
},
computed: {
src() {
return this.options.sources[0].src;
},
},
先在计算属性中返回视频的路径, 然后用watcher侦听该属性, 这样每次更改视频路径, 就可以进行重新加载视频资源
注意这里有一个Vue的BUG, 如果watch里监听options属性, 同时在渲染列表中同时渲染多个视频, 每次操作DOM都会触发watch,
明明options里的数据没有发生更改, 也会触发该watch事件, 例如这样写:
watch: {
options: {
handler() {
console.log('观测到options已发生变化:');
this.player.init();// 错误做法, 假设该操作是初始化视频, 那么每次数据未发生变化却触发watch事件时, 视频也会被初始化
},
deep: true
},
},
上面的代码将造成如下图这种BUG
目前npm上有一个1000多赞的vue-video-player组件, 是基于video.js的, 该组件截止目前仍然存在这种BUG
当操作DOM的时候, 页面中的所有视频都会被重新渲染, 就是因为该组件侦听了options属性, 那么解决这个BUG的方案也很简单, 就是不要侦听options, 只侦听src即可, 有其他需求, 思路也是相同的.
最后, 在业务组件中使用该Player组件:
<template>
<div @contextmenu.prevent>
<player :options="options"></player>
</div>
</template>
<script>
import player from '../base/Player';
export default {
components: {
player
},
data: () => ({
options : {
playbackRates: [0.1, 0.2, 0.5, 1.0], //播放速度
autoplay: true, //如果true,浏览器准备好时开始播放。
muted: false, // 默认情况下将会消除任何音频。
loop: false, // 导致视频一结束就重新开始。
preload: 'auto', // auto浏览器选择最佳行为,立即开始加载视频(如果浏览器支持)
//language: 'zh-CN',
aspectRatio: '16:9', // 播放器的比例。用冒号分隔的两个数字(例如"16:9"或"4:3")
fluid: true, // 当true时,播放器将拥有流体大小。换句话说,它将按比例缩放以适应其容器。
sources: [{
type: '',
src: '' //url地址
}],
//poster: '', //你的封面地址
// width: document.documentElement.clientWidth,
notSupportedMessage: '此视频暂无法播放,请稍后再试', //无法播放媒体源时显示的默认信息。
controlBar: {
timeDivider: true,
durationDisplay: true,
remainingTimeDisplay: false,
fullscreenToggle: true //全屏按钮
},
errorDisplay: false,
posterImage: false,
bigPlayButton : false,
textTrackDisplay : false,
},
}),
async beforeMount() {
const src = await this.api.get('getVideoSrc');
this.$set(this.options.sources, 0, {
type: 'video/mp4',
src
});
}
}
</script>
Player.vue组件全部代码 :
<template>
<video ref="video"
controls
class="video-js vjs-default-skin vjs-big-play-centered">
</video>
</template>
<script>
export default {
data: () => ({
player: null,
}),
props: {
options: {
type: Object,
required: true
},
},
watch: {
src() {
this.player.src(this.src);
}
},
computed: {
src() {
return this.options.sources[0].src;
},
video() {
return this.$store.state.video;
}
},
mounted() {
const vm = this;
this.player = this.$video(this.$refs.video, this.options, function () {
this.on('play', () => {
this.volume(vm.video.volume);
});
this.on('volumechange', () => {
vm.video.volume = this.volume();
window.localStorage.volume = this.volume();
});
this.on('error', () => {
let err = this.error();
if (err.code === 2) {// 判断是否是网络加载问题
let buffTime = this.bufferedEnd() - 6;
this.load(vm.src);
this.currentTime(buffTime);
this.play();
} else {
vm.alert.err(err.message);
}
})
});
},
}
</script>
