在Vue中自制视频播放器(上)

  • 前言
  • 初始化组件
  • 开始/暂停按钮
  • 停止按钮
  • 静音按钮
  • 视频播放时间
  • 全屏按钮
  • 源代码

前言

平时大家在浏览视频网站时,会发现各大视频网站都有自己的视频控制组件,虽然浏览器有原生的视频控制组件,但是不同浏览器的视频控制组件外观差异较大,功能也有限制,所以为了保证用户体验,大型视频门户网站都会使用自定义的视频播放器。

这篇文章介绍如何在Vue.js中自制一个视频播放器组件,我会用几个简单的功能抛砖引玉,带领大家初步了解视频播放器的实现方法。

注意:本文视频播放器中的图标都使用了Font Awesome图标,在Vue中使用Font Awesome可能与在原生JavaScript或在其他前端框架中使用Font Awesome有所不同,如果你还不了解如何在Vue中使用,可以参考我的另外一篇文章:在Vue中使用Font Awesome。

本文的Vue项目是用Vue CLI创建的,如果你想知道如何用Vue CLI创建项目,可以参考我的另外一篇文章:Vue CLI 3 快速搭建项目。

本文是系列文章的第一篇,主要介绍组件的基本布局,以及几个按钮如开始/暂停按钮、停止按钮、音量按钮、全屏按钮的制作。

如果你想读下篇文章,请移步:在Vue中自制视频播放器(下)。

初始化组件

既然我们要在Vue.js中开发一个自定义的视频播放器,最好的办法就是把这样一个视频播放器做成一个Vue组件的形式,我们首先新建一个Vue组件,命名为MyVideo.vue,因为HTML5中已经有<video>标签了,所以这个组件的名称不能与现有的标签混淆,让我们先来看看最初的代码骨架的样子:

<template><div class="video"><video class="video__player" ref="v"><source :src="videoSrc"/></video><div class="controller"></div></div>
</template><script>export default {name: "MyVideo",props: ['videoSrc',],data() {return {video: null,}},mounted() {this.video = this.$refs.v;},};
</script><style scoped>.video {position: relative;}.video__player {width: 100%;height: 100%;display: flex;}.controller {flex-direction: column;height: 50px;position: absolute;bottom: 0;left: 0;right: 0;background: rgba(0, 0, 0, 0.5);}
</style>

这个组件有一个属性,我们这里命名为videoSrc,表示视频的地址。

在视图内容上,这个组件应该被一个总的<div>包裹,其中包含一个<video>元素,表示视频本身,另外应该包含一个<div>元素,表示控制条,控制条应该是相对于父组件绝对定位,定位在下方。

注意这里我将视频的ref属性设置成了v,这是Vue的一个语法,用于注册一个元素,以便我们在Vue中直接访问,我通过这样的方式注册后,就可以通过this.$refs.v来访问这个元素了,因为视频控制条的各种行为都要通过使用<video>元素的API来实现,所以注册这个元素很有必要,关于Vue这方面的语法,请参考官方文档。

我在组件生命周期函数mounted中把this.video设置成了this.$refs.v,相当于将这个视频元素的引用保存在了data中,在访问的时候更加方便,这里必须是mounted函数而不能是created函数,因为只有DOM元素已经全部加载完毕时我们才能访问到this.$refs.v,关于Vue组建生命周期函数的内容,可以参考官方文档。

这里我把<video>元素的display属性设置成flex,只有这样<video>元素的大小才能与父元素<div>的大小保持一致,否则会出现视频元素的大小略微大于容器元素的大小而导致控制条位置出错的情况。

我们接下来将围绕着<video>元素的API进行控制条的开发,关于视频元素的API,可以参考W3Schools,这里介绍了<video>元素的属性,方法和事件。

开始/暂停按钮

示例代码:

<template><div class="video"><video class="video__player" ref="v" @ended="handleEnd"><source :src="videoSrc"/></video><div class="controller"><div class="controller__btn-wrapper"><div class="controller__btn" @click="togglePlaying"><font-awesome-icon :icon="['fas', 'play']" v-if="isPaused"></font-awesome-icon><font-awesome-icon :icon="['fas', 'pause']" v-else></font-awesome-icon></div></div></div></div>
</template><script>export default {name: "MyVideo",props: ['videoSrc',],data() {return {video: null,isPaused: true,};},methods: {togglePlaying() {if (this.video.paused) {this.playVideo();} else {this.pauseVideo();}},handleEnd() {this.pauseVideo();},playVideo() {this.isPaused = false;this.video.play();},pauseVideo() {this.isPaused = true;this.video.pause();},},mounted() {this.video = this.$refs.v;},};
</script><style scoped>.video {position: relative;}.video__player {width: 100%;height: 100%;display: flex;}.controller {flex-direction: column;height: 50px;position: absolute;bottom: 0;left: 0;right: 0;background: rgba(0, 0, 0, 0.5);}.controller__btn-wrapper {position: relative;height: calc(100% - 5px);display: flex;align-items: center;color: #fff;padding: 0 18px;}.controller__btn {cursor: pointer;transition: 0.5s;margin: 0 20px;}.controller__btn:hover {color: #409eff;}
</style>

我这里用CSS小小地美化了一下,本文关于CSS的内容不再赘述,UI设计方面的内容见仁见智,我仅把自己的代码贴在这里,就当抛砖引玉了。

这里加了一个按钮,同时在data中加了isPaused属性,这个属性用来记录视频是否已经暂停,因为我们需要它来决定这个图标的外观应该是播放还是暂停。每次按按钮时,将isPaused取反,而我们判断视频是否在播放是通过原生的paused属性来判断,如果视频已暂停,则开始播放,反之暂停。

还有一个特殊情况会导致视频暂停,就是视频完成了播放,所以我们监听视频元素的ended事件,一旦事件被触发,我们也手动更新isPaused属性。

停止按钮

示例代码:

<template><div class="video"><video class="video__player" ref="v" @ended="handleEnd"><source :src="videoSrc"/></video><div class="controller"><div class="controller__btn-wrapper"><div class="controller__btn" @click="togglePlaying"><font-awesome-icon :icon="['fas', 'play']" v-if="isPaused"></font-awesome-icon><font-awesome-icon :icon="['fas', 'pause']" v-else></font-awesome-icon></div><div class="controller__btn" @click="stopPlaying"><font-awesome-icon :icon="['fas', 'stop']"></font-awesome-icon></div></div></div></div>
</template><script>export default {name: "MyVideo",props: ['videoSrc',],data() {return {video: null,isPaused: true,};},methods: {togglePlaying() {if (this.video.paused) {this.playVideo();} else {this.pauseVideo();}},stopPlaying() {this.video.currentTime = 0;this.pauseVideo();},handleEnd() {this.pauseVideo();},playVideo() {this.isPaused = false;this.video.play();},pauseVideo() {this.isPaused = true;this.video.pause();},},mounted() {this.video = this.$refs.v;},};
</script><style scoped>.video {position: relative;}.video__player {width: 100%;height: 100%;display: flex;}.controller {flex-direction: column;height: 50px;position: absolute;bottom: 0;left: 0;right: 0;background: rgba(0, 0, 0, 0.5);}.controller__btn-wrapper {position: relative;height: calc(100% - 5px);display: flex;align-items: center;color: #fff;padding: 0 18px;}.controller__btn {cursor: pointer;transition: 0.5s;margin: 0 20px;}.controller__btn:hover {color: #409eff;}
</style>

停止按钮跟上一部分按钮的基本原理相同,我们通过API将视频的时间点设置为0,同时将视频暂停,这样就相当于停止了视频的播放,关键就是这行代码this.video.currentTime = 0

静音按钮

示例代码:

<template><div class="video"><video class="video__player" ref="v" @ended="handleEnd"><source :src="videoSrc"/></video><div class="controller"><div class="controller__btn-wrapper"><div class="controller__btn" @click="togglePlaying"><font-awesome-icon :icon="['fas', 'play']" v-if="isPaused"></font-awesome-icon><font-awesome-icon :icon="['fas', 'pause']" v-else></font-awesome-icon></div><div class="controller__btn" @click="stopPlaying"><font-awesome-icon :icon="['fas', 'stop']"></font-awesome-icon></div><div class="controller__btn" @click="toggleMute"><font-awesome-icon :icon="['fas', 'volume-up']"v-if="isMuted"></font-awesome-icon><font-awesome-icon :icon="['fas', 'volume-mute']" v-else></font-awesome-icon></div></div></div></div>
</template><script>export default {name: "MyVideo",props: ['videoSrc',],data() {return {video: null,isPaused: true,isMuted: false,};},methods: {togglePlaying() {if (this.video.paused) {this.playVideo();} else {this.pauseVideo();}},stopPlaying() {this.video.currentTime = 0;this.pauseVideo();},toggleMute() {this.video.muted = !this.video.muted;this.isMuted = this.video.muted;},handleEnd() {this.pauseVideo();},playVideo() {this.isPaused = false;this.video.play();},pauseVideo() {this.isPaused = true;this.video.pause();},},mounted() {this.video = this.$refs.v;},};
</script><style scoped>.video {position: relative;}.video__player {width: 100%;height: 100%;display: flex;}.controller {flex-direction: column;height: 50px;position: absolute;bottom: 0;left: 0;right: 0;background: rgba(0, 0, 0, 0.5);}.controller__btn-wrapper {position: relative;height: calc(100% - 5px);display: flex;align-items: center;color: #fff;padding: 0 18px;}.controller__btn {cursor: pointer;transition: 0.5s;margin: 0 20px;}.controller__btn:hover {color: #409eff;}
</style>

静音按钮其实跟暂停按钮有点类似,也是用了一个isMuted属性来手动更新静音状态以便动态渲染不同的图标,不过静音比暂停更加方便,因为<video>组件本身就有muted属性,这个属性可读又可写,通过读取这个属性我们可以直接知道视频是否被静音了,通过改变这个属性我们可以将视频静音/解除静音,所以全部的操作都可以在这一个属性上完成。视频的暂停/播放则略微复杂,因为需要调用特别的方法才能暂停或者开始播放视频。

视频播放时间

示例代码:

<template><div class="video"><video class="video__player" ref="v" @timeupdate="handleTimeUpdate" @ended="handleEnd"><source :src="videoSrc"/></video><div class="controller"><div class="controller__btn-wrapper"><div class="controller__btn" @click="togglePlaying"><font-awesome-icon :icon="['fas', 'play']" v-if="isPaused"></font-awesome-icon><font-awesome-icon :icon="['fas', 'pause']" v-else></font-awesome-icon></div><div class="controller__btn" @click="stopPlaying"><font-awesome-icon :icon="['fas', 'stop']"></font-awesome-icon></div><div class="controller__btn" @click="toggleMute"><font-awesome-icon :icon="['fas', 'volume-up']"v-if="isMuted"></font-awesome-icon><font-awesome-icon :icon="['fas', 'volume-mute']" v-else></font-awesome-icon></div><div class="controller__timer">{{videoTime}}</div></div></div></div>
</template><script>function secToTimer(originalSec) {const min = Math.floor(originalSec / 60);const sec = Math.floor(originalSec % 60);const minStr = min < 10 ? `0${min}` : String(min);const secStr = sec < 10 ? `0${sec}` : String(sec);return `${minStr}:${secStr}`;}export default {name: "MyVideo",props: ['videoSrc',],data() {return {video: null,isPaused: true,isMuted: false,videoTime: '00:00 / 00:00',};},methods: {handleTimeUpdate() {this.videoTime = this.refreshTime();},refreshTime() {if (!this.video) {return `${secToTimer(0)} / ${secToTimer(0)}`;}const currTime = this.video.currentTime || 0;const duration = this.video.duration || 0;return `${secToTimer(currTime)} / ${secToTimer(duration)}`;},togglePlaying() {if (this.video.paused) {this.playVideo();} else {this.pauseVideo();}},stopPlaying() {this.video.currentTime = 0;this.pauseVideo();},toggleMute() {this.video.muted = !this.video.muted;this.isMuted = this.video.muted;},handleEnd() {this.pauseVideo();},playVideo() {this.isPaused = false;this.video.play();},pauseVideo() {this.isPaused = true;this.video.pause();},},mounted() {this.video = this.$refs.v;},};
</script><style scoped>.video {position: relative;}.video__player {width: 100%;height: 100%;display: flex;}.controller {flex-direction: column;height: 50px;position: absolute;bottom: 0;left: 0;right: 0;background: rgba(0, 0, 0, 0.5);}.controller__btn-wrapper {position: relative;height: calc(100% - 5px);display: flex;align-items: center;color: #fff;padding: 0 18px;}.controller__btn {cursor: pointer;transition: 0.5s;margin: 0 20px;}.controller__btn:hover {color: #409eff;}.controller__timer {margin-left: 15px;}
</style>

这里我们要用到currentTimeduration两个属性,一个表示当前的时间,一个表示视频总时间,我们用一个函数将这两个时间变为分分:秒秒这样的时间格式显示出来就可以了,但是这里有一个比较关键的问题,如何保持这个时间更新呢?

如果把上面的模板中的{{videoTime}}改成{{refreshTime()}}的话似乎也可以完成目标,但是这样做的话模板只会在最初的时候更新一次,随着视频的播放时间改变后,它仍然不会更新。所以我们需要做的是把这个计时器绑定到一个侦听数据上,同时通过timeupdate事件触发这个计时器的更新,一旦侦听数据发生变化,就会发生视图的更新,只有这样我们才能让这个计时器保持实时更新。这里的timeupdate还是一个<video>的事件,当播放时间发生变化时这个事件就会被触发,所以不管是视频正常播放,还是它被停止播放了,都可以触发计时器的更新。

全屏按钮

示例代码:

<template><div class="video" ref="vcontainer"><video class="video__player" ref="v" @timeupdate="handleTimeUpdate" @ended="handleEnd"><source :src="videoSrc"/></video><div class="controller"><div class="controller__btn-wrapper"><div class="controller__btn" @click="togglePlaying"><font-awesome-icon :icon="['fas', 'play']" v-if="isPaused"></font-awesome-icon><font-awesome-icon :icon="['fas', 'pause']" v-else></font-awesome-icon></div><div class="controller__btn" @click="stopPlaying"><font-awesome-icon :icon="['fas', 'stop']"></font-awesome-icon></div><div class="controller__btn" @click="toggleMute"><font-awesome-icon :icon="['fas', 'volume-up']"v-if="isMuted"></font-awesome-icon><font-awesome-icon :icon="['fas', 'volume-mute']" v-else></font-awesome-icon></div><div class="controller__timer">{{videoTime}}</div><div class="controller__btn controller__btn--fullscreen" @click="toggleFullscreen"><font-awesome-icon :icon="['fas', 'expand']"></font-awesome-icon></div></div></div></div>
</template><script>function secToTimer(originalSec) {const min = Math.floor(originalSec / 60);const sec = Math.floor(originalSec % 60);const minStr = min < 10 ? `0${min}` : String(min);const secStr = sec < 10 ? `0${sec}` : String(sec);return `${minStr}:${secStr}`;}export default {name: "MyVideo",props: ['videoSrc',],data() {return {video: null,isPaused: true,isMuted: false,videoTime: '00:00 / 00:00',};},methods: {toggleFullscreen() {const isFullscreen = document.webkitIsFullScreen || document.fullscreen;if (isFullscreen) {const exitFunc = document.exitFullscreen || document.webkitExitFullscreen;exitFunc.call(document);} else {const element = this.$refs.vcontainer;const fullscreenFunc = element.requestFullscreen || element.webkitRequestFullScreen;fullscreenFunc.call(element);}},handleTimeUpdate() {this.videoTime = this.refreshTime();},refreshTime() {if (!this.video) {return `${secToTimer(0)} / ${secToTimer(0)}`;}const currTime = this.video.currentTime || 0;const duration = this.video.duration || 0;return `${secToTimer(currTime)} / ${secToTimer(duration)}`;},togglePlaying() {if (this.video.paused) {this.playVideo();} else {this.pauseVideo();}},stopPlaying() {this.video.currentTime = 0;this.pauseVideo();},toggleMute() {this.video.muted = !this.video.muted;this.isMuted = this.video.muted;},handleEnd() {this.pauseVideo();},playVideo() {this.isPaused = false;this.video.play();},pauseVideo() {this.isPaused = true;this.video.pause();},},mounted() {this.video = this.$refs.v;},};
</script><style scoped>.video {position: relative;}.video__player {width: 100%;height: 100%;display: flex;}.controller {flex-direction: column;height: 50px;position: absolute;bottom: 0;left: 0;right: 0;background: rgba(0, 0, 0, 0.5);}.controller__btn-wrapper {position: relative;height: calc(100% - 5px);display: flex;align-items: center;color: #fff;padding: 0 18px;}.controller__btn {cursor: pointer;transition: 0.5s;margin: 0 20px;}.controller__btn:hover {color: #409eff;}.controller__timer {margin-left: 15px;}.controller__btn--fullscreen {position: absolute;right: 15px;}
</style>

这里我们加了一个全屏按钮,为了使得控制条在全屏状态下仍然可以使用,我们选择将整个容器<div>元素全屏,这是DOM元素的一个API,为了访问整个容器,我将整个容器也注册了。

这里我们要考虑到浏览器兼容性的状况,所以我们先通过||运算符来判断哪个全屏API是浏览器支持的,然后在通过function.call应用到元素上面去。我这里只考虑了通用的API和WebKit浏览器专用的API,在Chrome浏览器上实测是可以使用的,如果你有特别要支持的浏览器,请查阅相关浏览器的API。要注意的是,判断是否全屏,进入全屏,退出全屏这些函数在不同浏览器都可能不同,需要查阅资料。

运行效果:

上半部分的介绍就到此为止,下半部分将介绍如何制作可拖动的进度条,敬请关注。

源代码

文章中的例子的源代码已经放到GitHub,请点击这里查看或下载。

在Vue中自制视频播放器(上)相关推荐

  1. vue中DPlayer视频播放器使用方法

    vue中DPlayer视频播放器使用方法 1通过npm下载 npm install dplayer - s 2在需要使用的组件中导入 import Dplayer from 'Dplayer' 3页面 ...

  2. vue引入video视频播放器(视频调用代码范例)

    vue引入video视频播放器(视频调用代码范例) VUE视频调用代码范例1: <template><div><div id="player"> ...

  3. 在C#中实现视频播放器

    当我们需要在C#中实现视频播放器的时候,可以使用如下几种方法: 一.使用MediaPlayer ActiveX控件 在C#中支持视屏播放器最简单的方式就是插入MediaPlayer控件了,在WPF中还 ...

  4. Vue 中定义方法页面上使用

    vue中定义方法的格式 为什么要这样定义 最近在学习前段,特别是HBuildX 我看里面很多方式都是这样使用,所以想强加练习 如何引用 当然我们的editArticle也可以直接放到js中,如下: 都 ...

  5. Vue中路由管理器Vue Router使用介绍(三)

    2019独角兽企业重金招聘Python工程师标准>>> 一.路由定义添加动态参数定义 1.路由定义项,使用:xx 方式 定义动态参数 {path:'/user/:id/:name', ...

  6. 网页中嵌入视频播放器代码

    有时候我们会有这样的需求,在网页中嵌入视频播放器,以播放我们的服务器端的视频,下面我将代码贴出来: 嵌入快播播放器: <table width="100%" align=&q ...

  7. 在 Vue 中实现 sticky 鼠标上滑显示、下滑隐藏的效果

    在 Vue 中实现 sticky 鼠标上滑显示.下滑隐藏的效果 首先在需要实现该效果的组件中,创建一个数据属性,例如: isStickyVisible: true,并将其初始值设置为 true. 在组 ...

  8. 网页中嵌入视频播放器

    有时候我们会有这样的需求,在网页中嵌入视频播放器,以播放我们的服务器端的视频,下面我将代码贴出来: 嵌入快播播放器: <table width="100%" align=&q ...

  9. 【详解Vue中请求拦截器】

    文章目录 前言 一.安装依赖 二.定义拦截器 1.创建一个interceptors.js文件用于定义拦截器 2.注册插件 3.发送请 总结 前言 提示: Vue请求拦截器通常用于在发送请求之前对请求进 ...

最新文章

  1. mysql 40101
  2. 使用中文输入法时对键盘事件的处理
  3. php乱码解决方案,php中文乱码问题的4种解决方案
  4. 泛型 与 实体类的相互转换
  5. QuarkXPress 2020中文版
  6. linux系统硬盘数据恢复软件下载,Linux硬盘数据恢复软件
  7. boost::sort模块实现使用不区分大小写的字符串键对结构进行排序的示例
  8. cx是什么简称_80年的5角,在纸币收藏界简称为8005
  9. 前端性能优化(一)-- 文件的压缩与合并
  10. Vue2.0 $set()处理数据更新但视图不更新的问题(给对象添加属性必须用this.$set(this.data,”key”,value’))
  11. mavan自动化接管浏览器_人工智能与自动化:接管还是共生?
  12. NLPIR/ICTCLAS中文分词系统 java相关api文档总结
  13. vue3引入echarts
  14. python创建快捷方式_python创建桌面快捷方式的代码详解
  15. 最好看的Excel条形图 如何用Excel图表把它做出来
  16. JDBC MySQL 连接
  17. 【蓝桥杯经典数学题】杨辉三角形
  18. 读书分享:《数学之美》中的一些关键名词
  19. 华为手机NFC功能,教你一键复制各种卡
  20. 使用 Vanilla JavaScript 框架创建一个简单的天气应用

热门文章

  1. 【Procmon教程2】如何揪出篡改注册表的元凶?
  2. 论文阅读笔记---《TransferNet: An Effective and Transparent Framework for Multi-hop Question Answering over》
  3. 全链通“铭镜”农产品溯源平台介绍分析
  4. excel日期相减去除周末_仅在Excel中允许周末日期
  5. Markov Chain Monte Carlo 和 Gibbs Sampling算法
  6. 超越美国!中国AI初创企业融资额全球第一 人脸识别最受热捧【附报告全文】
  7. 财务自开发系统的一些想法(实现篇)
  8. ui界面颜色设计_界面设计ui的颜色基础
  9. 银行手机APP安全评估报告【转载】
  10. java生成word排版_java生成word(文字和图片)