前言
沉寂了一周了,打算把这几天的结果呈现给大家。这几天抽空就一直在搞一个自定义视频播放器,为什么会有如此想法?是因为之前看一些学习视频网站时,看到它们做的视频播放器非常Nice!于是,就打算抽空开发一款属于自己的视频播放器。话不多说,一起来实战吧!
项目展示
(这只是一张图片哦~)
这张图就是我们的成品,还等什么?赶紧来实战吧!
实战
我会把完整源码放在github上,欢迎来star,地址在文末。
首先,我们会使用最原生的JavaScript来实现,老大哥肯定要打头阵啊!
一、JavaScript
iconfont.css:阿里字体图标文件,你可以在上面找到很多漂亮的图标。
index.css:项目样式文件。
index.js:项目逻辑文件。
-
- "en">
-
- "UTF-8" />
- name="viewport" content="width=device-width, initial-scale=1.0" />
-
VamVideo(原生js版) - "stylesheet" href="./css/iconfont/iconfont.css" />
- "stylesheet" href="./css/index.css" />
-
-
- "video-box">
- preload="auto" poster="./img/bg.png">
-
- src="https://mos-vod-drcn.dbankcdn.cn/P_VT/video_injection/A91343E9D/v3/9AB0A7921049102362779584128/MP4Mix_H.264_1920x1080_6000_HEAAC1_PVC_NoCut.mp4"
- />
-
-
- "bottom-tool">
- "pv-bar">
- "pv-played">
- "pv-dot">
-
- "pv-controls">
- "pc-con-l">
- "play-btn">
- "iconfont icon-bofang">
- "iconfont icon-zanting hide">
-
- "pv-time">
- "pv-currentTime">00:00:00
- /
- "pv-duration">00:00:00
-
-
- "pc-con-r">
- "pv-listen ml">
- "pv-yl">
-
"pv-ol"
> -
"pv-bg"
> -
- "pv-iconyl">
- "iconfont icon-yinliang">
- "iconfont icon-jingyin hide">
-
-
- "pv-speed ml">
-
"pv-spnum"
>1x -
"selectList"
> -
- 0.5x
-
- 1x
-
- 1.25x
-
- 1.5x
-
- 2x
-
-
- "pv-screen ml">
- "iconfont icon-quanping">
- "iconfont icon-huanyuan hide">
-
-
-
-
-
-
-
-
我们主要看下逻辑文件index.js。
- let timer = null;
- let disX = 0;
- let disL = 0;
- function $(el) {
- return document.querySelector(el);
- }
- function showEl(el) {
- $(el).style.display = "block";
- }
- function hideEl(el) {
- $(el).style.display = "none";
- }
- function setVp(w, h) {
- $(".video-player").style.width = w + "px";
- $(".video-player").style.height = h + "px";
- $(".video-box").style.width = w + "px";
- $(".video-box").style.height = h + "px";
- $(".pv-bar").style.width = w + "px";
- }
- // 时间格式化
- function changeTime(iNum) {
- let iN = parseInt(iNum);
- const iH = toZero(Math.floor(iN / 3600));
- const iM = toZero(Math.floor((iN % 3600) / 60));
- const iS = toZero(Math.floor(iN % 60));
- return iH + ":" + iM + ":" + iS;
- }
- // 整0处理
- function toZero(num) {
- if (num <= 9) {
- return "0" + num;
- } else {
- return "" + num;
- }
- }
- // 底部控制栏
- $(".video-box").onmouseenter = function () {
- $(".bottom-tool").style.bottom = "0px";
- };
- $(".video-box").onmouseleave = function () {
- $(".bottom-tool").style.bottom = "-45px";
- };
-
- // 倍速播放栏(显示/隐藏)
- $(".pv-spnum").onmouseover = function () {
- showEl(".selectList");
- };
- $(".pv-controls").onmouseleave = function () {
- hideEl(".selectList");
- };
-
- // 播放/暂停
- $(".play-btn").onclick = function () {
- if ($(".video-player").paused) {
- $(".video-player").play();
- hideEl(".icon-bofang");
- showEl(".icon-zanting");
- nowTime();
- timer = setInterval(nowTime, 1000);
- } else {
- $(".video-player").pause();
- showEl(".icon-bofang");
- hideEl(".icon-zanting");
- clearInterval(timer);
- }
- };
-
- // 总时长
- $(".video-player").oncanplay = function () {
- $(".pv-duration").innerHTML = changeTime($(".video-player").duration);
- };
-
- // 播放结束
- $(".video-player").onended = function (params) {
- showEl(".icon-bofang");
- hideEl(".icon-zanting");
- };
-
- // 播放时长
- function nowTime() {
- $(".pv-currentTime").innerHTML = changeTime($(".video-player").currentTime);
- let scale = $(".video-player").currentTime / $(".video-player").duration;
- let w = $(".pv-bar").offsetWidth - $(".pv-dot").offsetWidth;
- $(".pv-dot").style.left = scale * w + "px";
- $(".pv-played").style.width = scale * w + "px";
- }
-
- // 静音/取消静音
- $(".pv-iconyl").onclick = function () {
- if ($(".video-player").muted) {
- $(".video-player").volume = 1;
- hideEl(".icon-jingyin");
- showEl(".icon-yinliang");
- $(".video-player").muted = false;
- } else {
- $(".video-player").volume = 0;
- showEl(".icon-jingyin");
- hideEl(".icon-yinliang");
- $(".video-player").muted = true;
- }
- };
- let isfullScreen = false;
- // 全屏
- $(".pv-screen").onclick = function () {
- const w = document.documentElement.clientWidth || document.body.clientWidth;
- const h = document.documentElement.clientHeight || document.body.clientHeight;
- isfullScreen = !isfullScreen;
- if (isfullScreen) {
- setVp(w, h);
- hideEl(".icon-quanping");
- showEl(".icon-huanyuan");
- } else {
- setVp(900, 480);
- showEl(".icon-quanping");
- hideEl(".icon-huanyuan");
- }
- };
- // 播放进度条
- $(".pv-dot").onmousedown = function (ev) {
- let ev1 = ev || window.event;
- disX = ev1.clientX - $(".pv-dot").offsetLeft;
- document.onmousemove = function (ev) {
- let ev2 = ev || window.event;
- let L = ev2.clientX - disX;
- if (L < 0) {
- L = 0;
- } else if (L > $(".pv-bar").offsetWidth - $(".pv-dot").offsetWidth) {
- L = $(".pv-bar").offsetWidth - $(".pv-dot").offsetWidth;
- }
- $(".pv-dot").style.left = L + "px";
-
- let scale = L / ($(".pv-bar").offsetWidth - $(".pv-dot").offsetWidth);
- $(".video-player").currentTime = scale * $(".video-player").duration;
- nowTime();
- };
-
- document.onmouseup = function () {
- document.onmousemove = null;
- };
-
- return false;
- };
- // 音量控制
- $(".pv-ol").onmousedown = function (ev) {
- let ev1 = ev || window.event;
- disL = ev1.clientX - $(".pv-ol").offsetLeft;
- document.onmousemove = function (ev) {
- let ev2 = ev || window.event;
- let L = ev2.clientX - disL;
- if (L < 0) {
- L = 0;
- } else if (L > $(".pv-yl").offsetWidth - $(".pv-ol").offsetWidth) {
- L = $(".pv-yl").offsetWidth - $(".pv-ol").offsetWidth;
- }
- $(".pv-ol").style.left = L + "px";
- let scale = L / ($(".pv-yl").offsetWidth - $(".pv-ol").offsetWidth);
- $(".pv-bg").style.width = $(".pv-ol").offsetLeft + "px";
- if ($(".pv-ol").offsetLeft !== 0) {
- showEl(".icon-yinliang");
- hideEl(".icon-jingyin");
- } else {
- showEl(".icon-jingyin");
- hideEl(".icon-yinliang");
- }
- $(".video-player").volume = scale;
- };
-
- document.onmouseup = function () {
- document.onmousemove = null;
- };
-
- return false;
- };
- // 播放速度
- $(".selectList").onclick = function (e) {
- let ev = e || window.event;
- hideEl(".selectList");
- $(".pv-spnum").innerText = ev.target.innerText;
- const value = ev.target.innerText.replace("x", "");
- $(".video-player").playbackRate = value;
- };
这样写是可以实现一个视频播放器,你可以通过改样式文件还有部分逻辑文件来实现一个自定义配置视频播放器,但是这种效果不太好,所以我们将通过使用Es6中的Class类来重写这个自定义配置视频播放器。
二、Class类
vp.js:class类逻辑文件。
- class VamVideo {
- constructor(vp, attrObj, styleObj) {
- this.timer = null;
- this.disX = 0;
- this.disL = 0;
- this.isfullScreen = false;
- for (const key in attrObj) {
- if (Object.hasOwnProperty.call(attrObj, key)) {
- this.$(".video-player").setAttribute(key, attrObj[key]);
- }
- }
- for (const key in styleObj) {
- if (Object.hasOwnProperty.call(styleObj, key)) {
- this.$(".video-box").style[`${key}`] = styleObj[key];
- key === "width"
- ? (this.vbw = styleObj.width)
- : (this.vbw = vp.offsetWidth);
- key === "height"
- ? (this.vbh = styleObj.height)
- : (this.vbh = vp.offsetHeight);
- }
- }
- }
- $ = (el) => document.querySelector(el);
- showEl = (el) => {
- this.$(el).style.display = "block";
- };
- hideEl = (el) => {
- this.$(el).style.display = "none";
- };
- setVp = (w, h) => {
- const _w = String(w).indexOf("px") != -1 ? w : w + "px";
- const _h = String(h).indexOf("px") != -1 ? h : h + "px";
- this.$(".video-player").style.width = _w;
- this.$(".video-player").style.height = _h;
- this.$(".video-box").style.width = _w;
- this.$(".video-box").style.height = _h;
- this.$(".pv-bar").style.width = _w;
- };
- nowTime = () => {
- this.$(".pv-currentTime").innerHTML = this.changeTime(
- this.$(".video-player").currentTime
- );
- let scale =
- this.$(".video-player").currentTime / this.$(".video-player").duration;
- let w = this.$(".pv-bar").offsetWidth - this.$(".pv-dot").offsetWidth;
- this.$(".pv-dot").style.left = scale * w + "px";
- this.$(".pv-played").style.width = scale * w + "px";
- };
- changeTime = (iNum) => {
- let iN = parseInt(iNum);
- const iH = this.toZero(Math.floor(iN / 3600));
- const iM = this.toZero(Math.floor((iN % 3600) / 60));
- const iS = this.toZero(Math.floor(iN % 60));
- return iH + ":" + iM + ":" + iS;
- };
- toZero = (num) => {
- if (num <= 9) {
- return "0" + num;
- } else {
- return "" + num;
- }
- };
- // 底部控制栏(显示/隐藏)
- bottomTup = () => {
- this.$(".bottom-tool").style.bottom = "0px";
- };
- bottomTdow = () => {
- this.$(".bottom-tool").style.bottom = "-45px";
- };
- // 倍速播放栏(显示/隐藏)
- selectListShow = () => {
- this.showEl(".selectList");
- };
- selectListHide = () => {
- this.hideEl(".selectList");
- };
- // 播放/暂停
- usePlay = () => {
- if (this.$(".video-player").paused) {
- this.$(".video-player").play();
- this.hideEl(".icon-bofang");
- this.showEl(".icon-zanting");
- this.nowTime();
- this.timer = setInterval(this.nowTime, 1000);
- } else {
- this.$(".video-player").pause();
- this.showEl(".icon-bofang");
- this.hideEl(".icon-zanting");
- clearInterval(this.timer);
- }
- };
- // 总时长
- useOnplay = () => {
- this.$(".pv-duration").innerHTML = this.changeTime(
- this.$(".video-player").duration
- );
- };
- // 播放结束
- useEnd = () => {
- this.showEl(".icon-bofang");
- this.hideEl(".icon-zanting");
- };
- // 静音
- useVolume = () => {
- if (this.$(".video-player").muted) {
- this.$(".video-player").volume = 1;
- this.hideEl(".icon-jingyin");
- this.showEl(".icon-yinliang");
- this.$(".video-player").muted = false;
- } else {
- this.$(".video-player").volume = 0;
- this.showEl(".icon-jingyin");
- this.hideEl(".icon-yinliang");
- this.$(".video-player").muted = true;
- }
- };
- // 全屏
- fullScreen = () => {
- const w = document.documentElement.clientWidth || document.body.clientWidth;
- const h =
- document.documentElement.clientHeight || document.body.clientHeight;
- this.isfullScreen = !this.isfullScreen;
- if (this.isfullScreen) {
- this.setVp(w, h);
- this.hideEl(".icon-quanping");
- this.showEl(".icon-huanyuan");
- } else {
- console.log("w" + this.vbw, "h" + this.vbh);
- this.setVp(this.vbw, this.vbh);
- this.showEl(".icon-quanping");
- this.hideEl(".icon-huanyuan");
- }
- };
- // 播放进度条
- useTime = (ev) => {
- let ev1 = ev || window.event;
- this.disX = ev1.clientX - this.$(".pv-dot").offsetLeft;
- document.onmousemove = (ev) => {
- let ev2 = ev || window.event;
- let L = ev2.clientX - this.disX;
- if (L < 0) {
- L = 0;
- } else if (
- L >
- this.$(".pv-bar").offsetWidth - this.$(".pv-dot").offsetWidth
- ) {
- L = this.$(".pv-bar").offsetWidth - this.$(".pv-dot").offsetWidth;
- }
- this.$(".pv-dot").style.left = L + "px";
- let scale =
- L / (this.$(".pv-bar").offsetWidth - this.$(".pv-dot").offsetWidth);
- this.$(".video-player").currentTime =
- scale * this.$(".video-player").duration;
- this.nowTime();
- };
- document.onmouseup = function () {
- document.onmousemove = null;
- };
- return false;
- };
- // 音量控制
- useListen = (ev) => {
- let ev1 = ev || window.event;
- this.disL = ev1.clientX - this.$(".pv-ol").offsetLeft;
- document.onmousemove = (ev) => {
- let ev2 = ev || window.event;
- let L = ev2.clientX - this.disL;
- if (L < 0) {
- L = 0;
- } else if (
- L >
- this.$(".pv-yl").offsetWidth - this.$(".pv-ol").offsetWidth
- ) {
- L = this.$(".pv-yl").offsetWidth - this.$(".pv-ol").offsetWidth;
- }
- this.$(".pv-ol").style.left = L + "px";
- let scale =
- L / (this.$(".pv-yl").offsetWidth - this.$(".pv-ol").offsetWidth);
- this.$(".pv-bg").style.width = this.$(".pv-ol").offsetLeft + "px";
- if (this.$(".pv-ol").offsetLeft !== 0) {
- this.showEl(".icon-yinliang");
- this.hideEl(".icon-jingyin");
- } else {
- this.showEl(".icon-jingyin");
- this.hideEl(".icon-yinliang");
- }
- this.$(".video-player").volume = scale;
- };
- document.onmouseup = function () {
- document.onmousemove = null;
- };
- return false;
- };
- // 播放速度
- useSpnum = (e) => {
- let ev = e || window.event;
- this.hideEl(".selectList");
- this.$(".pv-spnum").innerText = ev.target.innerText;
- const value = ev.target.innerText.replace("x", "");
- this.$(".video-player").playbackRate = value;
- };
- }
这样不仅可以自定义配置一个视频播放器,逻辑文件中的每一个方法函数还非常的简单明了,可以说是达到我们要求的目的了。但是我们可以更简洁。
三、模板字符串
strvp.js:把标签语句放在了模板字符串中。
-
- "en">
-
- "UTF-8" />
- name="viewport" content="width=device-width, initial-scale=1.0" />
-
VamVideo(模板字符版) - "stylesheet" href="./css/iconfont/iconfont.css" />
- "stylesheet" href="./css/index.css" />
-
-
- -- 挂载点 -->
- "app">
-
-
-
-
-
-
可以看到上面的代码,我直接把标签语句转换为字符串直接挂载到父节点上,这样就更加简洁了。下面的代码就是一堆标签语句。
- const strHtml = `
- "video-box" onmouseenter="vp.bottomTup()" onmouseleave="vp.bottomTdow()">
- oncanplay="vp.useOnplay()" onended="vp.useEnd()">
- "bottom-tool">
- "pv-bar">
- "pv-played">
- "pv-dot" onmousedown="vp.useTime()">
-
- "pv-controls" onmouseleave="vp.selectListHide()">
- "pc-con-l">
- "play-btn" onclick="vp.usePlay()">
- "iconfont icon-bofang">
- "iconfont icon-zanting hide">
-
- "pv-time">
- "pv-currentTime">00:00:00
- /
- "pv-duration">00:00:00
-
-
- "pc-con-r">
- "pv-listen ml">
- "pv-yl">
-
"pv-ol"
onmousedown="vp.useListen()"> -
"pv-bg"
> -
- "pv-iconyl" onclick="vp.useVolume()">
- "iconfont icon-yinliang">
- "iconfont icon-jingyin hide">
-
-
- "pv-speed ml">
-
"pv-spnum"
onmouseover="vp.selectListShow()">1x -
"selectList"
onclick="vp.useSpnum()"> -
- 0.5x
-
- 1x
-
- 1.25x
-
- 1.5x
-
- 2x
-
-
- "pv-screen ml" onclick="vp.fullScreen()">
- "iconfont icon-quanping">
- "iconfont icon-huanyuan hide">
-
-
-
-
-
- `;
我们再进一步,使用Vue.js、React.js分别实现一波。
四、Vue.js
vue@2.6.12:引入Vue.js,这里我们使用@2.6.12。
-
- "en">
-
- "UTF-8" />
- name="viewport" content="width=device-width, initial-scale=1.0" />
-
VamVideo(Vue.js版) - "stylesheet" href="./css/iconfont/iconfont.css" />
- "stylesheet" href="./css/index.css" />
-
-
- "app">
-
-
-
-
-
-
-
从上面的代码中可以看到,可以直接在全局实例化一个对象,可以根据自己的需要进行配置。
五、React.js
react.development.js - React 的核心库。
react-dom.development - 提供与 DOM 相关的功能。
babel.min.js - Babel 可以将 ES6 代码转为 ES5 代码,这样我们就能在目前不支持 ES6 浏览器上执行 React 代码。Babel 内嵌了对 JSX 的支持。通过将 Babel 和 babel-sublime 包(package)一同使用可以让源码的语法渲染上升到一个全新的水平。
-
- "en">
-
- "UTF-8" />
- name="viewport" content="width=device-width, initial-scale=1.0" />
-
VamVideo(React.js版) - "stylesheet" href="./css/iconfont/iconfont.css" />
- "stylesheet" href="./css/index.css" />
-
-
-
-
-
- "app">
-
-
-
-
结语
到这里,我们使用五种方法来实践一个自定义配置视频播放器。梦想就这么简单地实现了!你可以查看完整源码到我的github上,地址在这https://github.com/maomincoding/vamPlayer。
项目中主要难点在于拖拽那块,大家可以先自己尝试着去理解,我将会在下一篇主要讲述本项目所遇到的一些问题以及解决方法。