当前位置:   article > 正文

使用videojs+vue2+elementui自定义播放器控件_vue videojs

vue videojs

一、安装项目所需依赖

videojs依赖:

npm install --save-dev video.js

elementui依赖(这个图方便就不按需引入了):

npm i element-ui -S

二、main.js修改

增加以下几行:

  1. import videojs from 'video.js'
  2. import elemenui from 'element-ui'
  3. import 'element-ui/lib/theme-chalk/index.css';
  4. Vue.prototype.$videoJS = videojs;
  5. Vue.use(elemenui)

三、准备html结构

1、准备两个组件

在components文件夹下创建两个组件videoComponent和videoPlayer——

videoComponent挂载到App组件上

videoPlayer挂载到videoComponent上

2、各组件中的html结构

先把两个组件最基本的结构搭好

videoComponent:

  1. <template>
  2. <div class="container">
  3. <video-player :options="videoOptions" class="video-css"></video-player>
  4. </div>
  5. </template>
  6. <script>
  7. import videoPlayer from './videoPlayer.vue'
  8. export default {
  9. name: 'videoComponent',
  10. components:{videoPlayer},
  11. data(){
  12. return{
  13. videoOptions:{
  14. //一些视频的配置项,见下面补充...
  15. }
  16. }
  17. }
  18. </script>
  19. <style scoped>
  20. .container{
  21. width: 100%;
  22. height: 100vh;
  23. display: flex;
  24. justify-content: center;
  25. align-items: center;
  26. }
  27. /* 视频宽高将由此样式调整 */
  28. .video-css{
  29. width: 800px;
  30. height: auto;
  31. }
  32. </style>

videoPlayer:

  1. <template>
  2. <div class="videoBox">
  3. <video ref="videoPlayer" class="video-js"></video>
  4. </div>
  5. </template>
  6. <script>
  7. import 'video.js/dist/video-js.css';
  8. export default {
  9. name:'videoPlayer',
  10. }
  11. </script>
  12. <style scoped>
  13. .videoBox{
  14. box-sizing: border-box;
  15. position: relative;
  16. width: 800px;
  17. height:500px;
  18. }
  19. </style>

App:

  1. <template>
  2. <div id="app">
  3. <video-component></video-component>
  4. </div>
  5. </template>
  6. <script>
  7. import videoComponent from './components/videoComponent.vue'
  8. export default {
  9. name: 'App',
  10. components: {
  11. videoComponent
  12. }
  13. }
  14. </script>
  15. <style>
  16. *{
  17. padding: 0;
  18. margin: 0;
  19. }
  20. </style>

3、在videoPlayer组件里实例化播放器

  1. <script>
  2. import 'video.js/dist/video-js.css';
  3. export default {
  4. name:'videoPlayer',
  5. //接收外部组件来的参数,可实现videoPlayer组件的复用
  6. props:{
  7. options: {
  8. type: Object
  9. }
  10. },
  11. data(){
  12. return{
  13. player:null
  14. }
  15. },
  16. methods:{
  17. //定义好一个实例化播放器的方法
  18. createVideoPlayer(){
  19. this.player = this.$videoJS(this.$refs.videoPlayer,this.options)
  20. }
  21. },
  22. //在组件挂载时调用播放器实例化方法
  23. mounted(){
  24. this.createVideoPlayer()
  25. },
  26. //组件销毁前销毁播放器
  27. beforeDestroy(){
  28. if(this.player){
  29. this.player.dispose()
  30. }
  31. }
  32. }
  33. </script>

4、videoComponent传递视频参数

还有很多配置项,有需要自己查

  1. <script>
  2. import videoPlayer from './videoPlayer.vue'
  3. export default {
  4. name: 'videoComponent',
  5. components:{videoPlayer},
  6. data(){
  7. return{
  8. videoOptions:{
  9. //controls配置项决定是否显示默认控件
  10. controls:true,
  11. //fluid配置项根据外层css样式大小,自动填充宽高
  12. fluid:true,
  13. //sources配置项配置视频播放源
  14. sources:[
  15. {
  16. //播放源
  17. src:require('@/assets/kp.mp4'),
  18. //视频类型
  19. type:"video/mp4"
  20. }
  21. ]
  22. }
  23. }
  24. },
  25. }
  26. </script>

在assets文件夹下事先放好一个视频,在配置项sources里面改一下视频路径。

走到这一步,运行项目一般可以正常播放视频了:

 接下来就可以实现播放器自定义控件

5、自定义控件的html结构

确定视频可以正常播放后就可以着手自定义控件了,控件写在videoPlayer这个组件里。先上结构,这里图标用的都是elementui的icon,音量没找到合适的就凑合着用了其他图标:

  1. <template>
  2. <div class="videoBox">
  3. <video
  4. ref="videoPlayer"
  5. class="video-js"
  6. >
  7. </video>
  8. <!-- 自定义控件 -->
  9. <div class="controlBar">
  10. <div class="progressBar" @mousedown="isDraging = true" @mouseup="isDraging = false">
  11. <el-slider
  12. v-model="currentTimeVal"
  13. :max="totalTimeVal"
  14. :format-tooltip="timeFormat"
  15. @change="progressUpdate"
  16. >
  17. </el-slider>
  18. </div>
  19. <div class="controlBtnBox">
  20. <div class="left">
  21. <i class="el-icon-video-play icon-size"></i>
  22. <span>00:00:00 / 00:00:00</span>
  23. </div>
  24. <div class="right">
  25. <i class="el-icon-d-arrow-left icon-size" @click="back(player.currentTime())"></i>
  26. <i class="el-icon-d-arrow-right icon-size" @click="forward(player.currentTime())"></i>
  27. <i class="el-icon-bell icon-size" @click="toShowVolumeBar"></i>
  28. <div id="volumeBar">
  29. <el-slider
  30. v-model="volume"
  31. vertical
  32. height="100px">
  33. </el-slider>
  34. </div>
  35. </div>
  36. <div class="rateBox">
  37. <span @click="toShowOptions">{{ratedisplay}}</span>
  38. <div class="rateOptions" v-show="isShowRateOptions">
  39. <span
  40. v-for="r,index in rateOptions"
  41. :key="index"
  42. @click="setPlayRate(r)"
  43. >
  44. {{r}}x
  45. </span>
  46. </div>
  47. </div>
  48. </div>
  49. </div>
  50. </div>
  51. </template>

结构效果图最终长这样,这里样式不多废话,跟完整代码放在文章最后面。

 6、准备数据

 结构搞定之后,在data里准备好后续需要用到的数据

  1. data(){
  2. return{
  3. player:null,
  4. //当前播放速率
  5. rate:1.0,
  6. //播放速率
  7. rateOptions:[2.0,1.75,1.5,1.0,0.75,0.5],
  8. //显示速率选项
  9. isShowRateOptions:false,
  10. //音量
  11. volume:30,
  12. //是否暂停
  13. isPaused:true,
  14. //当前播放时间点和视频总时长
  15. currentTime:'00:00:00',
  16. totalTime:'00:00:00',
  17. //进度条的当前值,必须为number类型
  18. currentTimeVal:0,
  19. //进度条最大值,必须为number类型
  20. totalTimeVal:0,
  21. //是否在拖到进度条
  22. isDraging:false
  23. }
  24. },

rate和rateOptions用在哪上面的代码已经写了,isShowRateOptions后面用来隐藏倍速选项那个框的这里不重要先不去理。主要是volume,它绑定在el-slider上是该滑块的默认值,后面改音量会用到;currentTime和 totalTime用于动态显示当前视频播放的具体时间,以及视频的总时长; currentTimeVal和totalTimeVal是改变进度条、实现时长跳转的主要数据;isPaused用来决定自定义控件的图标是暂停还是播放。

7、播放暂停、进度条实时跟进,拉到进度条实现跳转

首先从简单的暂停和播放做起。在html结构中,我们应该根据isPaused来决定显示哪个图标,同时将前面显示时长的假数据换成data里的currentTime和totalTime:

  1. <i :class="[isPaused ? 'el-icon-video-play' : 'el-icon-video-pause']" class=" icon-size" v-show="isPaused"></i>
  2. <span>{{currentTime}} / {{totalTime}}</span>

然后给图标绑定上一个togglePlay的函数,来响应点击后实现播放或暂停:

  1. //控制视频的播放与暂停
  2. togglePlay(){
  3. this.isPaused = !this.isPaused
  4. if(!this.isPaused){
  5. this.player.play()
  6. }else{
  7. this.player.pause()
  8. }
  9. },

时间格式化:

  1. //视频时长格式化
  2. timeFormat(time){
  3. let hour = Math.floor(time / 3600),
  4. minute = Math.floor((time % 3600) / 60),
  5. second = Math.floor(time % 60);
  6. hour = hour < 10 ? "0" + hour : hour;
  7. minute = minute < 10 ? "0" + minute : minute;
  8. second = second < 10 ? "0" + second : second;
  9. return `${hour}:${minute}:${second}`;
  10. },

获取视频总时长:

  1. //获取视频的总时长和进度条最大值
  2. getTotalTime(){
  3. this.totalTime = this.timeFormat(this.player.duration())
  4. this.totalTimeVal = Math.floor(this.player.duration())
  5. },

更新视频当前播放时间、进度条实时跟进:

进度条的实现原理其实就是video的timeUpdate事件可以监测到视频的播放进度,在这个事件中可以一直获取到视频当前的播放时间,然后将这个值赋给滑块绑定的currentTimeVal,这样就能在播放过程中跟着改变滑块的位置了。

  1. //更新视频当前播放时间
  2. timeUpdate(){
  3. //如果当前正在拉到进度条,先停止更新当前播放时间,直接return结束这个函数
  4. //没有这一句会出现拉动进度条跳转失败的bug
  5. if(this.isDraging) return
  6. this.currentTime = this.timeFormat(this.player.currentTime())
  7. this.currentTimeVal = this.player.currentTime()
  8. //当前时间更新到等于总时长时,要改变视频的播放状态按钮
  9. if(this.currentTime === this.totalTime){
  10. this.isPaused = true
  11. }
  12. },

拉到进度条跳转到指定位置播放:

这一功能的实现就是el-slider有一个change事件,在拖拽滑块松开鼠标后触发,这时只要在鼠标松开后,改变播放器的currentTime属性的值,

这里要稍微注意一下:因为我们在拉动进度条的时候,视频还处于播放状态,那么意味着上一步我们更新进度条时长的那个函数获取到的currentTime值也会改变el-slider的值,所以在上一步的函数中,我们需要监测进度条是否在拉动,如果是,我们应该停止执行那个函数。监听只需要在进度条外层的div上绑定一个mouseon和mousedown事件,鼠标按住时让isDragging等于false,然后在timeUpdate函数中通过isDragging来判断进度条是否处于拖拽的状态。

  1. //进度条拉动时更新进度条值并从拉到的位置播放
  2. progressUpdate(val){
  3. this.player.currentTime(val)
  4. // 虽然mouseup已经可以改变isDraging的值,但下面这句不能少,不然视频播放结束再点击播放时,进度条不会回到最开始位置
  5. this.isDraging = false
  6. },

8、更新速率、改变音量

  1. //改变速率
  2. setPlayRate(rate){
  3. this.rate = rate;
  4. this.player.playbackRate(rate);
  5. this.isShowRateOptions = false;
  6. },
  7. //改变音量
  8. changeVolume(val){
  9. this.volume = val
  10. //由于h5规定volum的值在0-1之间,所以这里要对获取到的val做一个处理(滑块的val是从0-100)
  11. this.player.volume(val / 100)
  12. },
  13. //快进
  14. forward(ct){
  15. this.progressUpdate(ct + 10)
  16. },
  17. //后退
  18. back(ct){
  19. this.progressUpdate(ct - 10)
  20. }

9、完整代码

VideoComponent:

  1. <template>
  2. <div class="container">
  3. <video-player
  4. :options="videoOptions"
  5. class="video-css"
  6. >
  7. </video-player>
  8. </div>
  9. </template>
  10. <script>
  11. import videoPlayer from './videoPlayer.vue'
  12. export default {
  13. name: 'videoComponent',
  14. components:{videoPlayer},
  15. data(){
  16. return{
  17. videoOptions:{
  18. //controls配置项决定是否显示默认控件,因为这里要做自定义的控件,就不显示了
  19. controls:false,
  20. //fluid配置项根据外层css样式大小,自动填充宽高
  21. fluid:true,
  22. //sources配置项配置视频播放源
  23. sources:[
  24. {
  25. //播放源
  26. src:require('@/assets/kp.mp4'),
  27. //视频类型
  28. type:"video/mp4"
  29. }
  30. ],
  31. }
  32. }
  33. },
  34. }
  35. </script>
  36. <style scoped>
  37. .container{
  38. width: 800px;
  39. height: 100vh;
  40. display: flex;
  41. justify-content: center;
  42. align-items: center;
  43. }
  44. /* 视频宽高由此样式调整 */
  45. .video-css{
  46. width: 800px;
  47. height:auto;
  48. }
  49. </style>

VideoPlayer:

  1. <template>
  2. <div class="videoBox">
  3. <video
  4. ref="videoPlayer"
  5. class="video-js"
  6. @canplay="getTotalTime"
  7. @timeupdate="timeUpdate"
  8. >
  9. </video>
  10. <!-- 自定义控件 -->
  11. <div class="controlBar">
  12. <div class="progressBar" @mousedown="isDraging = true" @mouseup="isDraging = false">
  13. <el-slider
  14. v-model="currentTimeVal"
  15. :max="totalTimeVal"
  16. :format-tooltip="timeFormat"
  17. @change="progressUpdate"
  18. >
  19. </el-slider>
  20. </div>
  21. <div class="controlBtnBox">
  22. <div class="left">
  23. <i
  24. :class="[isPaused ? 'el-icon-video-play' : 'el-icon-video-pause']"
  25. class=" icon-size"
  26. @click="togglePlay()"
  27. >
  28. </i>
  29. <span>{{currentTime}}/{{totalTime}}</span>
  30. </div>
  31. <div class="right">
  32. <i class="el-icon-d-arrow-left icon-size" @click="back(player.currentTime())></i>
  33. <i class="el-icon-d-arrow-right icon-size" @click="forward(player.currentTime())></i>
  34. <i class="el-icon-bell icon-size" @click="toShowVolumeBar"></i>
  35. <div id="volumeBar" v-show="isShowVolumeBar">
  36. <el-slider
  37. v-model="volume"
  38. vertical
  39. height="100px"
  40. @input="changeVolume"
  41. >
  42. </el-slider>
  43. </div>
  44. </div>
  45. <div class="rateBox">
  46. <span @click="toShowOptions">{{ratedisplay}}</span>
  47. <div class="rateOptions" v-show="isShowRateOptions">
  48. <span
  49. v-for="r,index in rateOptions"
  50. :key="index"
  51. @click="setPlayRate(r)"
  52. >
  53. {{r}}x
  54. </span>
  55. </div>
  56. </div>
  57. </div>
  58. </div>
  59. </div>
  60. </template>
  61. <script>
  62. import 'video.js/dist/video-js.css';
  63. export default {
  64. name:'videoPlayer',
  65. //接收来自父组件videoComponent的video的具体配置信息,这样可以实现对VideoPlayer组件的复用
  66. props:{
  67. options: {
  68. type: Object
  69. }
  70. },
  71. //用计算属性来实现当速率为1时,显示“倍速”
  72. computed:{
  73. ratedisplay(){
  74. if(this.rate == 1){
  75. return '倍速'
  76. }else{
  77. return this.rate + 'x'
  78. }
  79. }
  80. },
  81. data(){
  82. return{
  83. player:null,
  84. //当前播放速率
  85. rate:1.0,
  86. //播放速率
  87. rateOptions:[2.0,1.75,1.5,1.0,0.75,0.5],
  88. //显示速率选项和音量选项
  89. isShowRateOptions:false,
  90. isShowVolumeBar:false,
  91. //音量
  92. volume:30,
  93. //是否暂停
  94. isPaused:true,
  95. //当前播放时间点和视频总时长
  96. currentTime:'00:00:00',
  97. totalTime:'00:00:00',
  98. //进度条的当前值,必须为number类型
  99. currentTimeVal:0,
  100. //进度条最大值,必须为number类型
  101. totalTimeVal:0,
  102. //是否在拖到进度条
  103. isDraging:false
  104. }
  105. },
  106. methods:{
  107. createVideoPlayer(){
  108. this.player = this.$videoJS(this.$refs.videoPlayer,this.options)
  109. },
  110. //显示速率选项
  111. toShowOptions(){
  112. this.isShowRateOptions = !this.isShowRateOptions
  113. },
  114. toShowVolumeBar(){
  115. this.isShowVolumeBar = !this.isShowVolumeBar
  116. },
  117. //视频时长格式化
  118. timeFormat(time){
  119. let hour = Math.floor(time / 3600),
  120. minute = Math.floor((time % 3600) / 60),
  121. second = Math.floor(time % 60);
  122. hour = hour < 10 ? "0" + hour : hour;
  123. minute = minute < 10 ? "0" + minute : minute;
  124. second = second < 10 ? "0" + second : second;
  125. return `${hour}:${minute}:${second}`;
  126. },
  127. //获取视频的总时长和进度条最大值
  128. getTotalTime(){
  129. this.totalTime = this.timeFormat(this.player.duration())
  130. this.totalTimeVal = Math.floor(this.player.duration())
  131. },
  132. //改变速率
  133. setPlayRate(rate){
  134. this.rate = rate;
  135. this.player.playbackRate(rate);
  136. this.isShowRateOptions = false;
  137. },
  138. //控制视频的播放与暂停
  139. togglePlay(){
  140. console.log()
  141. this.isPaused = !this.isPaused
  142. if(!this.isPaused){
  143. this.player.play()
  144. }else{
  145. this.player.pause()
  146. }
  147. },
  148. //更新视频当前播放时间
  149. timeUpdate(){
  150. //如果当前正在拉到进度条,先停止更新当前播放时间,直接return结束这个函数
  151. //没有这一句会出现拉动进度条跳转失败的bug
  152. if(this.isDraging) return
  153. this.currentTime = this.timeFormat(this.player.currentTime())
  154. this.currentTimeVal = this.player.currentTime()
  155. //当前时间更新到等于总时长时,要改变视频的播放状态按钮
  156. if(this.currentTime === this.totalTime){
  157. this.isPaused = true
  158. }
  159. },
  160. //进度条拉动时更新进度条值并从拉到的位置播放
  161. progressUpdate(val){
  162. this.player.currentTime(val)
  163. // 虽然mouseup已经可以改变isDraging的值,但下面这句不能少,不然视频播放结束再点击播放时,进度条不会回到最开始位置
  164. this.isDraging = false
  165. },
  166. //改变音量
  167. changeVolume(val){
  168. this.volume = val
  169. //由于h5规定volum的值在0-1之间,所以这里要对获取到的val做一个处理(滑块的val是从0-100)
  170. this.player.volume(val / 100)
  171. },
  172. //快进
  173. forward(ct){
  174. this.progressUpdate(ct + 10)
  175. },
  176. //后退
  177. back(ct){
  178. this.progressUpdate(ct - 10)
  179. }
  180. },
  181. mounted(){
  182. this.createVideoPlayer()
  183. },
  184. beforeDestroy(){
  185. if(this.player){
  186. this.player.dispose()
  187. }
  188. }
  189. }
  190. </script>
  191. <style scoped>
  192. .videoBox{
  193. box-sizing: border-box;
  194. position: relative;
  195. width: 800px;
  196. height:500px;
  197. background-color: rgb(73, 156, 128);
  198. }
  199. .controlBar{
  200. width: 90%;
  201. height: 55px;
  202. position:absolute;
  203. bottom: 20px;
  204. left: 5%;
  205. background-color:#817f7f5a;
  206. box-sizing: border-box;
  207. color: rgb(233, 231, 231);
  208. }
  209. .progressBar{
  210. box-sizing: border-box;
  211. position: relative;
  212. width: 100%;
  213. padding: 10px;
  214. height: 10%;
  215. /* background-color: aliceblue; */
  216. }
  217. .controlBtnBox{
  218. box-sizing: border-box;
  219. width: 100%;
  220. height:60%;
  221. display: flex;
  222. justify-content: space-between;
  223. align-items: center;
  224. }
  225. /* 以下强行修改了el-slider样式 */
  226. .progressBar /deep/ .el-slider__bar{
  227. height: 3px;
  228. background-color: #409EFF;
  229. border-top-left-radius: 3px;
  230. border-bottom-left-radius: 3px;
  231. position: absolute;
  232. }
  233. .progressBar /deep/ .el-slider__button{
  234. height: 8px;
  235. width: 8px;
  236. }
  237. .progressBar /deep/ .el-slider__runway{
  238. margin-top:1px;
  239. margin-bottom: 1px;
  240. height: 3px;
  241. }
  242. .progressBar /deep/.el-slider__button-wrapper{
  243. width: 28px;
  244. height: 33px;
  245. }
  246. .icon-size{
  247. font-size: 25px;
  248. cursor: pointer;
  249. }
  250. .left{
  251. padding-left:10px ;
  252. width: 50%;
  253. display: flex;
  254. align-items: center;
  255. }
  256. .left span{
  257. margin-left: 20px;
  258. }
  259. .right{
  260. width: 15%;
  261. display: flex;
  262. justify-content: space-around;
  263. position: relative;
  264. }
  265. .right i{
  266. display: block;
  267. }
  268. #volumeBar{
  269. width: 30px;
  270. height: 120px;
  271. background-color: #817f7f5a;
  272. position: absolute;
  273. top:-150px;
  274. right: 4px;
  275. display: flex;
  276. justify-content: center;
  277. align-items: center;
  278. }
  279. .rateBox{
  280. width: 15%;
  281. cursor: pointer;
  282. }
  283. .rateOptions{
  284. width: 80px;
  285. height: 180px;
  286. background-color: #817f7f5a;
  287. position: absolute;
  288. top:-185px;
  289. right: 50px;
  290. display: flex;
  291. flex-wrap: wrap;
  292. align-content: center;
  293. }
  294. .rateOptions span{
  295. display: block;
  296. width: 100%;
  297. height: 30px;
  298. text-align: center;
  299. line-height: 30px;
  300. }
  301. .rateOptions span:hover{
  302. background-color: #cec9c95a;
  303. color: #409EFF;
  304. }
  305. </style>

App:

  1. <template>
  2. <div id="app">
  3. <video-component></video-component>
  4. </div>
  5. </template>
  6. <script>
  7. import videoComponent from './components/videoComponent.vue'
  8. export default {
  9. name: 'App',
  10. components: {
  11. videoComponent
  12. }
  13. }
  14. </script>
  15. <style>
  16. *{
  17. padding: 0;
  18. margin: 0;
  19. }
  20. #app{
  21. display: flex;
  22. justify-content: center;
  23. width: 100%;
  24. }
  25. </style>

结束,暂时还没发现什么bug。

但测试用的是的视频是放在本地的,没有涉及到资源加载,如果视频资源是走网络请求的话还得再改。。

参考链接:

以下是实现的时候参考的几篇文章

vue中自定义视频:

vue 中 自定义视频video_粥粥_的博客-CSDN博客

video.js使用教程:

Video.js使用教程一(详解)_lucky-peach的博客-CSDN博客

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/79547
推荐阅读
相关标签
  

闽ICP备14008679号