Vue中使用video.js

未标题2.png

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

load.gif

目前npm上有一个1000多赞的vue-video-player组件, 是基于video.js的, 该组件截止目前仍然存在这种BUG

相关issue

当操作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>

发布评论
还没有评论,快来抢沙发吧!