从0到1搭建一款Vue可配置视频播放器组件

时间:2022-01-26 16:22:46

从0到1搭建一款Vue可配置视频播放器组件

 前言

话不多说,这篇文章主要讲述如何从0到1搭建一款适用于Vue.js的自定义配置视频播放器。我们平时在PC端网站上观看视频时,会看到有很多丰富样式的视频播放器,而我们自己写的video标签样式却是那么丑。其实像那些网站都是基于原生video标签进行开发的,只不过还得适当加工一下,才会有我们所看到的漂亮的视频播放器。

开发

在具体开发之前,我们需要明确我们需要做什么?

  • 封装一个可配置的视频播放器;
  • 适用于Vue.js;
  • 应用于PC端网站;
  • 视频播放器常用的功能必须要有;
  • 发布到Npm;

好,明确了以上几点之后,我们就开始敲代码了。

一、搭建一个基础的UI组件

这里的UI组件你可以理解成我们搭建一个静态页面,就是把视频播放器简单地搭建起来,有一个基础的模型。

<template> 

  <div 

    class="video-box" 

  > 

    <video 

      class="video-player" 

    ></video> 

    <div class="bottom-tool"

      <div class="pv-bar"

        <div class="pv-played"></div> 

        <div class="pv-dot"></div> 

      </div> 

      <div class="pv-controls"

        <div class="pc-con-l"

          <div class="play-btn"

            <i class="iconfont icon-bofang"></i> 

            <i class="iconfont icon-zanting hide"></i> 

          </div> 

          <div class="pv-time"

            <span class="pv-currentTime">00:00:00</span> 

            <span>/</span> 

            <span class="pv-duration">00:00:00</span> 

          </div> 

        </div> 

        <div class="pc-con-r"

          <div class="pv-listen ml"

            <div class="pv-yl"

              <p class="pv-ol"></p> 

              <p class="pv-bg"></p> 

            </div> 

            <div class="pv-iconyl"

              <i class="iconfont icon-yinliang"></i> 

              <i class="iconfont icon-jingyin hide"></i> 

            </div> 

          </div> 

          <div class="pv-speed ml"

            <p class="pv-spnum">1x</p> 

            <ul class="selectList"

              <li>0.5x</li> 

              <li>1x</li> 

              <li>1.25x</li> 

              <li>1.5x</li> 

              <li>2x</li> 

            </ul> 

          </div> 

          <div class="pv-screen ml"

            <i class="iconfont icon-quanping"></i> 

            <i class="iconfont icon-huanyuan hide"></i> 

          </div> 

          <div class="pv-screens ml"

            <i class="iconfont icon-shipinquanping"></i> 

            <i class="iconfont icon-tuichuquanping hide"></i> 

          </div> 

        </div> 

      </div> 

    </div> 

  </div> 

</template> 

 

<script> 

export default { 

  name"VamVideo" 

}; 

</script> 

 

<style scoped> 

@import "./css/iconfont/iconfont.css"

@import "./css/index.css"

</style> 

样式文件我这里就不展示了,我会在文末给出源码地址。

从0到1搭建一款Vue可配置视频播放器组件

二、开发逻辑执行文件

最最关键的部分莫过于逻辑文件了,我这里使用构造函数的方式。

// eslint-disable-next-line no-unused-vars 

function VamVideo(vp, attrObj, styleObj) { 

  // 初始化 

  this.timer = null

  this.disX = 0; 

  this.disL = 0; 

  this.isPageFullScreen = false

  // 处理视频属性 

  for (const key in attrObj) { 

    if (Object.hasOwnProperty.call(attrObj, key) && key !== "controls") { 

      $(".video-player").setAttribute(key, attrObj[key]); 

    } 

  } 

  // 处理视频样式 

  for (const key in styleObj) { 

    if (Object.hasOwnProperty.call(styleObj, key)) { 

      $(".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); 

    } 

  } 

  // 封装获取元素节点 

  function $(el) { 

    return document.querySelector(el); 

  } 

  // 处理当前时间 

  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"

  } 

  // 处理时分秒 

  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; 

    } 

  } 

  // 元素显示 

  this.showEl = function (el) { 

    $(el).style.display = "block"

  }; 

  // 元素隐藏 

  this.hideEl = function (el) { 

    $(el).style.display = "none"

  }; 

  // 动态设置视频宽高 

  this.setVp = function (w, h) { 

    const _w = String(w).indexOf("px") != -1 ? w : w + "px"

    const _h = String(h).indexOf("px") != -1 ? h : h + "px"

    $(".video-player").style.width = _w; 

    $(".video-player").style.height = _h; 

    $(".video-box").style.width = _w; 

    $(".video-box").style.height = _h; 

    $(".pv-bar").style.width = _w; 

  }; 

  // 底部控制栏(显示/隐藏) 

  this.bottomTup = function () { 

    $(".bottom-tool").style.bottom = "0px"

  }; 

  this.bottomTdow = function () { 

    $(".bottom-tool").style.bottom = "-45px"

  }; 

  // 播放/暂停 

  this.usePlay = function () { 

    if ($(".video-player").paused) { 

      $(".video-player").play(); 

      this.hideEl(".icon-bofang"); 

      this.showEl(".icon-zanting"); 

      nowTime(); 

      this.timer = setInterval(nowTime, 1000); 

    } else { 

      $(".video-player").pause(); 

      this.showEl(".icon-bofang"); 

      this.hideEl(".icon-zanting"); 

      clearInterval(this.timer); 

    } 

  }; 

  this.isplay = function () { 

    this.usePlay(); 

  }; 

  // 总时长 

  this.useOnplay = function () { 

    $(".pv-duration").innerHTML = changeTime($(".video-player").duration); 

  }; 

  // 播放结束 

  this.useEnd = function () { 

    this.showEl(".icon-bofang"); 

    this.hideEl(".icon-zanting"); 

  }; 

  // 静音 

  this.useVolume = function () { 

    if ($(".video-player").muted) { 

      $(".video-player").volume = 1; 

      this.hideEl(".icon-jingyin"); 

      this.showEl(".icon-yinliang"); 

      $(".video-player").muted = false

    } else { 

      $(".video-player").volume = 0; 

      this.showEl(".icon-jingyin"); 

      this.hideEl(".icon-yinliang"); 

      $(".video-player").muted = true

    } 

  }; 

  // 页面全屏 

  this.pageFullScreen = function () { 

    const w = document.documentElement.clientWidth || document.body.clientWidth; 

    const h = 

      document.documentElement.clientHeight || document.body.clientHeight; 

    this.isPageFullScreen = !this.isPageFullScreen; 

    if (this.isPageFullScreen) { 

      this.setVp(w, h); 

      this.hideEl(".icon-quanping"); 

      this.showEl(".icon-huanyuan"); 

      this.hideEl(".pv-screens"); 

    } else { 

      this.setVp(this.vbw, this.vbh); 

      this.showEl(".icon-quanping"); 

      this.hideEl(".icon-huanyuan"); 

      this.showEl(".pv-screens"); 

    } 

  }; 

  // 窗口全屏 

  this.fullScreen = function () { 

    const el = $(".video-box"); 

    const isFullscreen = 

      document.fullScreen || 

      document.mozFullScreen || 

      document.webkitIsFullScreen; 

    if (!isFullscreen) { 

      this.showEl(".icon-tuichuquanping"); 

      this.hideEl(".icon-shipinquanping"); 

      this.hideEl(".pv-screen"); 

      (el.requestFullscreen && el.requestFullscreen()) || 

        (el.mozRequestFullScreen && el.mozRequestFullScreen()) || 

        (el.webkitRequestFullscreen && el.webkitRequestFullscreen()) || 

        (el.msRequestFullscreen && el.msRequestFullscreen()); 

    } else { 

      this.showEl(".icon-shipinquanping"); 

      this.hideEl(".icon-tuichuquanping"); 

      this.showEl(".pv-screen"); 

      document.exitFullscreen 

        ? document.exitFullscreen() 

        : document.mozCancelFullScreen 

        ? document.mozCancelFullScreen() 

        : document.webkitExitFullscreen 

        ? document.webkitExitFullscreen() 

        : ""

    } 

  }; 

  // 播放进度条 

  this.useTime = function (ev) { 

    let ev1 = ev || window.event; 

    this.disX = ev1.clientX - $(".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 > $(".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

  }; 

  // 音量控制 

  this.useListen = function (ev) { 

    let ev1 = ev || window.event; 

    this.disL = ev1.clientX - $(".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 > $(".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) { 

        this.showEl(".icon-yinliang"); 

        this.hideEl(".icon-jingyin"); 

      } else { 

        this.showEl(".icon-jingyin"); 

        this.hideEl(".icon-yinliang"); 

      } 

      $(".video-player").volume = scale; 

    }; 

    document.onmouseup = function () { 

      document.onmousemove = null

    }; 

    return false

  }; 

  // 播放速度 

  this.useSpnum = function (e) { 

    let ev = e || window.event; 

    $(".pv-spnum").innerText = ev.target.innerText; 

    const value = ev.target.innerText.replace("x"""); 

    $(".video-player").playbackRate = value; 

  }; 

// 导出 

export default VamVideo; 

三、整合组件逻辑

开发完UI组件以及逻辑组件了,那我们接下来就是将两者结合起来。

<template> 

  <div 

    class="video-box" 

    @mouseenter="vp.bottomTup()" 

    @mouseleave="vp.bottomTdow()" 

  > 

    <video 

      class="video-player" 

      @canplay="vp.useOnplay()" 

      @ended="vp.useEnd()" 

      @click="vp.isplay()" 

    ></video> 

    <div class="bottom-tool"

      <div class="pv-bar"

        <div class="pv-played"></div> 

        <div class="pv-dot" @mousedown="vp.useTime()"></div> 

      </div> 

      <div class="pv-controls"

        <div class="pc-con-l"

          <div class="play-btn" @click="vp.usePlay()"

            <i class="iconfont icon-bofang"></i> 

            <i class="iconfont icon-zanting hide"></i> 

          </div> 

          <div class="pv-time"

            <span class="pv-currentTime">00:00:00</span> 

            <span>/</span> 

            <span class="pv-duration">00:00:00</span> 

          </div> 

        </div> 

        <div class="pc-con-r"

          <div class="pv-listen ml"

            <div class="pv-yl"

              <p class="pv-ol" @mousedown="vp.useListen()"></p> 

              <p class="pv-bg"></p> 

            </div> 

            <div class="pv-iconyl" @click="vp.useVolume()"

              <i class="iconfont icon-yinliang"></i> 

              <i class="iconfont icon-jingyin hide"></i> 

            </div> 

          </div> 

          <div class="pv-speed ml"

            <p class="pv-spnum">1x</p> 

            <ul class="selectList" @click="vp.useSpnum()"

              <li>0.5x</li> 

              <li>1x</li> 

              <li>1.25x</li> 

              <li>1.5x</li> 

              <li>2x</li> 

            </ul> 

          </div> 

          <div class="pv-screen ml" @click="vp.pageFullScreen()"

            <i class="iconfont icon-quanping"></i> 

            <i class="iconfont icon-huanyuan hide"></i> 

          </div> 

          <div class="pv-screens ml" @click="vp.fullScreen()"

            <i class="iconfont icon-shipinquanping"></i> 

            <i class="iconfont icon-tuichuquanping hide"></i> 

          </div> 

        </div> 

      </div> 

    </div> 

  </div> 

</template> 

 

<script> 

import VamVideo from "./vp.js"

export default { 

  name"VamVideo"

  data: () => ({ 

    vp: null

    defaultStyle: { 

      width: "1200px"

      height: "600px"

    }, 

  }), 

  props: { 

    properties: { 

      type: Object, 

    }, 

    videoStyle: { 

      type: Object, 

    }, 

  }, 

  mounted() { 

    this.vp = new VamVideo( 

      document.querySelector(".video-box"), 

      this.properties, 

      Object.keys(this.videoStyle).length === 0 

        ? this.defaultStyle 

        : this.videoStyle 

    ); 

  }, 

}; 

</script> 

 

<style scoped> 

@import "./css/iconfont/iconfont.css"

@import "./css/index.css"

</style> 

首先我们引入了之前开发完成的逻辑文件vp.js,然后在mounted方法中对类VamVideo进行实例化,赋给this.vp。传给类的几个参数分别是最外层节点、视频属性、视屏样式。props属性中的properties为视频属性,videoStyle为视频样式。

四、发布组件

完成了以上几个步骤的开发,我们需要将我们完成的组件发布到Npm上。

1. 初始化

创建一个空文件夹,我们可以取名叫v-vamvideo。在此文件夹下键入命令:

npm init -y 

因为我们还需要修改,所以直接创建package.json文件。

  "name""vue-vam-video"

  "version""1.0.0"

  "description""Vue.js Custom video components"

  "main""index.js"

  "author""maomincoding"

  "keywords": ["video"], 

  "license""ISC"

  "private"false 

  • name:组件名
  • author:Npm用户名
  • main:入口文件
  • version:版本号,更新组件需要用到这个字段
  • description:描述
  • license的值按照以上即可
  • keywords为:搜索的关键词
  • private设为false, 开源因此需要将这个字段改为false

2. 引入组件

将我们之前封装好的组件复制到v-vamvide这个文件夹中,下图就是我们之前封装好的组件文件目录。

从0到1搭建一款Vue可配置视频播放器组件

3. 创建入口文件

我们要发布到Npm上需要一个入口文件,我们在v-vamvide根目录下创建一个入口文件,我们这里叫做index.js。

// 引入组件 

import VamVideo from "./VamVideo/vamvideo.vue"

// 组件需要添加name属性,代表注册的组件名称 

VamVideo.install = (Vue) => Vue.component(VamVideo.name, VamVideo); //注册组件 

 

export default VamVideo; 

4. 创建一个说明文档

发布到Npm上,用户需要知道这个组件干什么的?怎么用?我们在v-vamvide根目录下创建一个说明文档,取名为README.md

# vue-vamvideo 

> Vue.js Custom video components 

 

## Using documents 

1. Introducing components 

2. configuration parameter 

 

- `properties`: Video properties. 

 

- `videoStyle`: Video style. 

 

These two parameters need to be set separately. 

*** 

<template> 

  <div id="app"

    <vam-video :properties="videoOption.properties" :videoStyle="videoOption.videoStyle"></vam-video> 

  </div> 

</template> 

 

<script> 

export default { 

  name"Index"

  data: () => ({ 

    videoOption: { 

      properties: { 

        poster: require("./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"

        preload: "auto"

        loop: "loop"

        // autoplay:"autoplay"

        // muted:true

        // controls:"controls" 

      }, 

      videoStyle: { 

        // width: "1200px"

        // height: "600px"

      }, 

    }, 

  }) 

}; 

</script> 

*** 

我们离成功很近了,所以谢谢你可以阅读到这。源码地址:https://github.com/maomincoding/vue-vam-video

5. 发布

开始操作以下步骤之前,你需要把命令行切换到项目根目录下(也就是这里的v-vamvide这个文件夹)。

1.查看登录源是否是http://registry.npmjs.org

npm config get registry 

如果不是,切换登录源。

npm config set registry=http://registry.npmjs.org 

2.登录Npm

npm login 

相继输入用户名、密码、邮箱。回车出现Logged in as maomincoding on http://registry.npmjs.org,那么就登录成功了。

3.上传发布到Npm

npm publish 

 从0到1搭建一款Vue可配置视频播放器组件

五、安装组件

既然我们已经发布到Npm上,我们可以去Npm网站上看下效果。

https://www.npmjs.com/package/vue-vam-video

从0到1搭建一款Vue可配置视频播放器组件

发布组件成功了,那么我们放在Vue工程上测试一下。

1.安装组件

npm i vue-vam-video 

2.注册组件

全局注册

import Vue from 'vue' 

import App from './App.vue' 

// 全局注册 

import VamVideo from "vue-vam-video"

Vue.use(VamVideo); 

 

Vue.config.productionTip = false 

 

new Vue({ 

  render: h => h(App), 

}).$mount('#app'

<template> 

  <div id="app"

    <vam-video :properties="videoOption.properties" :videoStyle="videoOption.videoStyle"></vam-video> 

  </div> 

</template> 

 

<script> 

export default { 

  name"App"

  data: () => ({ 

    videoOption: { 

      properties: { 

        poster: require("./assets/logo.png"), 

        src: 

          "https://mos-vod-drcn.dbankcdn.cn/P_VT/video_injection/A91343E9D/v3/9AB0A7921049102362779584128/MP4Mix_H.264_1920x1080_6000_HEAAC1_PVC_NoCut.mp4"

        preload: "auto"

        loop: "loop"

        // autoplay:"autoplay"

        // muted:true

        // controls:"controls" 

      }, 

      videoStyle: { 

        // width: "1200px"

        // height: "600px"

      }, 

    }, 

  }) 

}; 

</script> 

局部注册

<template> 

  <div id="app"

    <vam-video :properties="videoOption.properties" :videoStyle="videoOption.videoStyle"></vam-video> 

  </div> 

</template> 

 

<script> 

import VamVideo from 'vue-vam-video' 

export default { 

  name"App"

  data: () => ({ 

    videoOption: { 

      properties: { 

        poster: require("./assets/logo.png"), 

        src: 

          "https://mos-vod-drcn.dbankcdn.cn/P_VT/video_injection/A91343E9D/v3/9AB0A7921049102362779584128/MP4Mix_H.264_1920x1080_6000_HEAAC1_PVC_NoCut.mp4"

        preload: "auto"

        loop: "loop"

        // autoplay:"autoplay"

        // muted:true

        // controls:"controls" 

      }, 

      videoStyle: { 

        // width: "1200px"

        // height: "600px"

      }, 

    }, 

  }), 

  components: { 

    VamVideo 

  }, 

}; 

</script> 

3.效果 大功告成!

从0到1搭建一款Vue可配置视频播放器组件

原文地址:https://mp.weixin.qq.com/s/vrHGiurc0gIJXG7n9oUDGQ