自定义video

需求

  1. 播放暂停切换
  2. 播放进度条反馈和控制 (拖拽 点击) 播放时间 00:00/05:30
  3. 音量调控 反馈 一键静音/取消静音
  4. 全屏播放 循环模式
  5. 播放状态反馈 未播放 已播放 播放完毕
  6. 本地存储播放时间 自动续播

准备

        [
	        {
	          方法:[ play(),pause()],
	          属性: [paused],
	          element:[#contr-play]
	        },{
	          方法:[ mousedown,pause,mousemove,mouseup,play],
	          属性: [currentTime],
	          element:[player-progress,progress-line,progress-unit,player-time,player-current]
	        },{
	          方法:[ mousedown,mousemove,mouseup],
	          属性: [volume,muted],
	          element:[]
	        },{
	
	        }

        ] 
        
        * video设置currentTime的时候 会触发 canplay事件

        * 拖拽进度条之后 播放视频

        点击进度条某个位置 直接开始跳转播放 

        click {
          mousedown 
          mouseup
        }

效果

在这里插入图片描述

实现

<!DOCTYPE html>
<html lang="zh-cn">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>video 播放器</title>
  <style>
    @font-face {
      font-family: "iconfont";
      src: url('fonts/iconfont.eot?t=1600426546624');
      /* IE9 */
      src: url('fonts/iconfont.eot?t=1600426546624#iefix') format('embedded-opentype'),
        /* IE6-IE8 */
        url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAS4AAsAAAAACZwAAARrAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDSAqGEIUYATYCJAMYCw4ABCAFhG0HWBtOCFGULspqsq+wKWO+GKJwXogXwkCjQQ+Evn05o/Tmdn/CWtgG7CKF5wDBE+3X3uzO7ldJh0qDrg1PXronIomYrETRBCXeT3b2b3lEpbkvLFIehM2H9pLZzSaenZwqqfxQaVQqGrOyuvDwtnv/7k9So5BJKoGZTRUuRKGkK0x2QTiRPI44CCLOEosr0+0G0l/4RJO7B8EAIYJA47YGzrlqkEjLIJhA8XigAzpJpmAVUP1xr+qf5x8s61myQaI496BcKFwuUIHGXmQtSLmCWgewTbzfuh4CYqPWYKb61l6Ip/TdBMhpmTrEJ+y0ghKhyRccm8gHgrDcszcAePB+Xn5wJB4MV9FvdLjVuaGyp3ePG401m+oojA7nBdiTwIJlgELeQv8FLsgv48TyX+BtA1IhQ/tqFrOS69292J+EWg+L1PiXB46lDIEMQJ9S5K7OHaDCkkeGYlixMFTCisJwDGwY/N9TXpbZD9gzAPwBiGwk6QcOfgcvpWOqmuni+nDbJCffPri99vX5c4JDgObuNOtT9Qobn1udt1pvXyqbSOFmnLnrUp1xT05in3ORNC6+nT8KbDu2cILh7o5rl+7K1J35e9b7WiHOI4LWDRwQhtIW7srqIIzWBM4jQrddyno3JkG8WNmroq/UENHQrkXGZmh3yKq0M7oO1tmbuCt311QH7T+SszNH3zmavW3H1gfaO3fpCqqAIVBbk3Rl6+lFYfQpjxD/1LCiaKujjpQD+89yHz9y0JxlpFBTnnAd1a2FWEvuByU7/O7KqNeXCjfdL1cSSuJRmNo0Bpn8r4CxZCUHHcjbCN6cUle4Ls4hLHIutpit6Y6v6lp58hMru1btw3RqwOWJcnTx2zmdm0nVEhLUh2y1sY2ZRsiMJ91hePo8SkrtC2z+X28ZH25J8x/lc11WCMukL2WvGeIKj5MKnvzeVZWYhp/oVg54db9lpL7krXtXOz+MYaIn3rxmf0cKGU2aLN3uOQqu3RZ9sPmavnmV/5xqhowU6qgjOVx+IowrAqhXhNR4GWQA/BR+4datAgD7sL8Y/reDi/+X/Q6P6ylpaaV/QlEAPuYvOLy+4WiCbQB/FQONFf8pRcM5VVS6w6c02xpCgLf/sbIGxMYS/JPCtVdDBTsQeUXIKQBGYASs0DSq5DI4iVXwQmsQW9JwcqJBcCQjYNEUAaJ0BozcM1ilm6iSL8HpfQGvDAexo8g6Z2I2VmINwTrFbtQ/jCxXKGiYLprot3ZjM+rXSVzl0TRMbCuFVElJlxpwEJMhDrAHTAelBjJIKIDqiZthvz+EwiTkxS4qeSgNF8uyUfRFkisUgERbCExHYW5Iv2GIxSUkyHBWphIz398NM0X56UjFTFlUgxE2a+2ISqI0QBv0wUYzl/IS2wCTA0UZEAMREoDUE17Mb+RDkHDxVl6YCyXxdMiGFZPJRUZTvTS9MXCXVwEx/aN8GLGi4sRLgLv1qI3jhqJBT1QPxg9bQb+lB834SGLzsBU0xU6/PowJAAA=') format('woff2'),
        url('fonts/iconfont.woff?t=1600426546624') format('woff'),
        url('fonts/iconfont.ttf?t=1600426546624') format('truetype'),
        /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
        url('fonts/iconfont.svg?t=1600426546624#iconfont') format('svg');
      /* iOS 4.1- */
    }

    .iconfont {
      font-family: "iconfont" !important;
      font-size: 32px;
      color: #ccc;
      font-style: normal;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      cursor: pointer;
    }

    .icon-pause:before {
      content: "\e7d2";
    }

    .icon-loop:before {
      content: "\e6a9";
    }

    .icon-volume:before {
      content: "\e63a";
    }

    .icon-fullscreen:before {
      content: "\e640";
    }

    .icon-player:before {
      content: "\e62b";
    }

    div {
      box-sizing: border-box;
    }

    video {
      display: block;
      width: 100%;
      max-height: 100%;
    }

    .player {
      position: relative;
      display: flex;
      flex-direction: column;
      justify-content: center;
      width: 638px;
      height: 493px;
      margin: 100px auto;
      background-color: #000;
    }

    .player .hint {
      position: absolute;
      top: 0;
      width: 100%;
      height: 30px;
      color: #fff;
      text-align: center;
      line-height: 30px;
      font-size: 20px;
      letter-spacing: .2em;
    }

    .player-screen {
      position: absolute;
      width: 100%;
      height: 100%;
      top: 0;
      left: 0;
      display: flex;
      justify-content: center;
    }


    .player-controls {

      display: flex;
      flex-direction: column;
      justify-content: space-between;
      align-items: center;
      position: absolute;
      bottom: 0;
      width: 100%;
      height: 60px;
    }


    .player-progress {
      position: relative;
      width: 100%;
      height: 4px;
      background-color: #ccc;
    }

    .player-progress:hover .progress-unit {
      display: block;
    }

    .player-progress .progress-line {
      position: relative;
      width: 0;
      height: 4px;
      background-color: rgb(18, 177, 226);
    }

    .player-progress .progress-unit {
      display: none;
      position: absolute;
      right: -18px;
      top: 0;
      bottom: 0;
      margin: auto;
      width: 18px;
      height: 18px;
      border-radius: 50%;
      background-color: rgb(18, 177, 226);
      cursor: pointer;
    }

    .player-time {
      position: absolute;
      right: 0;
      top: -35px;
      color: #fff;
    }

    .player-controls .player-operator {
      display: flex;
      justify-content: space-between;
      align-items: center;
      width: 100%;
      padding: 4px 20px;
    }

    .operator-right {
      display: flex;
      align-items: center;
    }

    .operator-right i {
      padding: 0 6px;
    }


    .is-loop {
      color: pink;
    }

    .is-muted {
      color: red;
    }

    .volume-range {
      width: 100px;
      height: 5px;
    }
  </style>
</head>

<body>
  <div id="my-player" class="player">
    <p class="hint">暂未播放影片</p>
    <div class="player-screen">
      <video src="video/mp4.mp4">
        <source src="video/mp4.mp4" type="video/mp4">
      </video>
    </div>

    <div class="player-controls">
      <div class="player-progress">
        <p class="player-time"><span class="player-current">00:00:00</span> / <span class="player-count">00:00:00</span>
        </p>
        <div class="progress-line">
          <div class="progress-unit"></div>
        </div>
      </div>
      <div class="player-operator">
        <i id="contr-play" class="iconfont icon-player icon-pause"></i>

        <div class="operator-right">
          <input type="range" value="0.1" id="volume-range" class="volume-range">
          <i id="contr-volume" class=" iconfont icon-volume"></i>
          <i id="contr-loop" class=" iconfont icon-loop"></i>
          <i id="contr-full" class=" iconfont icon-fullscreen"></i>

        </div>
      </div>
    </div>
  </div>
  <script src="js/common.js"></script>
  <script src="js/Storage.js"></script>
  <script>
    class MyVideo {
      static progressMaxWidth = $('.player-progress').offsetWidth;

      constructor(player = $('video')) {
        this.player = player;
        this.canPlay = false; //视频可播放状态 视频加载到可以播放状态的时候 canPlay 为true 默认false
        this.storage = new Storage('local');
        this.$ele = {
          $playBtn: $('#contr-play'), //播放|暂停按钮
          $progress: $('.progress-line'), //进度条
          $progressUnit: $('.progress-unit'), //进度条按钮
          $currentTime: $('.player-current'), //当前播放时长
          $countTime: $('.player-count'), //总时长
          $fullScreen: $('#contr-full'),//全屏按钮
          $loop: $('#contr-loop'), //切换循环loop状态
          $volumeBtn: $('#contr-volume'),
          $volumeRange: $('#volume-range'),
        }
        //进度条数据集
        this.progress = {
          isDown: false, //是否按下鼠标
          startX: 0, //起始鼠标x方向位置
          width: 0, //起始进度条宽度
        };
        //事件总控制
        this.eventListener();
      }

      //设置触发 左查询 写入
      set isPaused(val) {
        this._paused = val;
        if (val) {
          this.myPlay();
          return false;
        }
        this.myPause();
      }


      //获取触发 右查询 读取
      get isPaused() {
        return this._paused; //return的值会作为 isPaused属性的值
      }


      set volume(val) {
        this._volume = val;
        this.player.volume = val;
        this.$ele.$volumeRange.value = val * 100;
      }


      get volume() {
        return this._volume;
      }


      //初始化
      initPlayer() {

        if (this.canPlay) {
          return false;
        }
        this.volume = 0.1;
        this.player.volume = this.volume; //初始化音量
        this.canPlay = true; //初始化可播放状态

        this.checkPlayHistory();//检查本地存储该视频是否有播放时间戳 如果存在设置播放时间戳跳转
        this.setCountTimeText()//初始化总播放时长
        this.setCurrentText(); //初始化当前播放时长
      }

      checkPlayHistory() {
        let current = ~~this.storage.getStorage(this.player.src);
        if (!current) {
          return false;
        }
        this.setCurrentTime(null, current);
      }

      eventListener() {
        //事件分发映射表
        const dragMap = {
          'mousedown': (e) => {
            const target = e.target;

            //按下鼠标的时候 判断触发元素是否是progress-unit
            if (e.target.className === 'progress-unit') {
              //拖拽流程
              if (this.progress.isDown) {
                return false;
              }
              this.progress.startX = e.clientX;
              this.progress.isDown = true;
              this.progress.width = this.$ele.$progress.offsetWidth;
              this.isPaused = false;

              return false; //终止后续执行
            }

            //类名中是否包含progress 判断是否点击在 进度条上 => 进度跳转
            if (/progress/g.test(e.target.className)) {

              let _x = e.clientX - getPosition($('.player-progress')).left;
              let ratio = _x / MyVideo.progressMaxWidth;
              let time = ratio * this.player.duration;
              this.setCurrentTime(null, time);
              this.isPaused = true;
            }
          },
          'mousemove': (e) => {
            if (!this.progress.isDown) {
              //如果没有开启拖拽 
              return false;
            }
            //_x 鼠标拖拽x方向位移增量
            let _x = e.clientX - this.progress.startX;
            //width 当前进度条宽度 = 增量+初始宽度
            let width = _x + this.progress.width;

            //ratio 比例 = 当前宽度/总宽度
            let ratio = width / MyVideo.progressMaxWidth;
            //time 当前播放市场 = 比例 * 总时长 
            let time = ratio * this.player.duration;
            this.setCurrentTime(null, time);
          },
          'mouseup': (e) => {
            if (!this.progress.isDown) {
              //如果没有开启拖拽 
              return false;
            }
            this.isPaused = true; //set isPaused
            this.progress.isDown = false;
          }
        }

        //事件控制器 监听
        this.$ele.$playBtn.addEventListener('click', () => {
          //获取暂停当前状态 isPaused true暂停 false播放
          this.isPaused = this.player.paused;
          //监听播放/暂停按钮的点击
          if (!this.canPlay) {
            //判断是否可以播放 默认true
            return false;
          }

        }, false);

        //监听可播放状态 调用 initPlayer
        this.player.addEventListener('canplay', this.initPlayer.bind(this));

        //播放时间更新触发
        this.player.addEventListener('timeupdate', this.setCurrentTime.bind(this));

        //监听全屏切换按钮
        this.$ele.$fullScreen.addEventListener('click', this.changeFullScreen.bind(this));

        this.$ele.$loop.addEventListener('click', this.changeLoop.bind(this));

        this.$ele.$volumeRange.addEventListener('change', this.changeVolume.bind(this), false);

        this.$ele.$volumeBtn.addEventListener('click', this.chageMuted.bind(this), false);

        //当页面完全不可见的时候  存储视频播放信息
        document.addEventListener('visibilitychange', () => {
          if (document.hidden) {

            this.isPaused = false; //暂停视频
            this.setVideoInfo();
          }
        });


        //事件分发 
        const drag = (e) => {
          let type = e.type;
          if (dragMap.hasOwnProperty(type)) {
            dragMap[type](e);
          }
        }

        //监听进度条的拖拽组
        document.addEventListener('mousedown', drag, false);
        document.addEventListener('mousemove', drag, false);
        document.addEventListener('mouseup', drag, false);


      }

      //设置进度条
      setProgress(done) {
        //进度条比例 .progress-line 的宽度  = 当前播放(秒)/视频总时长(秒) * 100 + '%'

        let ratio = this.player.currentTime / this.player.duration * 100;
        if (done) {
          this.progress.width = ratio * MyVideo.progressMaxWidth;
          return false;
        }
        //设置进度条DOM的宽度样式
        this.$ele.$progress.style.width = ratio + '%';

      }

      //设置当前播放时间反馈
      setCurrentTime(e, time) {
        if (time) {
          this.player.currentTime = time;
        }
        //设置进度条
        this.setProgress();
        //设置当前播放时间text
        this.setCurrentText();
      }


      //设置当前播放时间文本
      setCurrentText() {
        this.$ele.$currentTime.innerText = this.getCurrentTime();
      }

      //设置总时长文本
      setCountTimeText() {
        this.$ele.$countTime.innerText = this.getCountTime();
      }

      //获取总时间 s=> h:m:s
      getCountTime() {
        //duration 视频总时长
        return MyVideo.fromatTime(this.player.duration);
      }

      //获取当前播放时长 s=> h:m:s
      getCurrentTime() {
        return MyVideo.fromatTime(this.player.currentTime);
      }


      //静态方法 内部的工具方法
      static fromatTime(time) {
        let [s, m, h] = [~~(time % 60), ~~(time / 60), ~~(time / 3600)];
        [s, m, h] = [s, m, h].map(padLeft);
        return `${h}:${m}:${s}`;
      }


      //播放动作
      myPlay() {
        this.$ele.$playBtn.classList.remove('icon-player');
        this.player.play();
      }


      //暂停动作
      myPause() {
        this.$ele.$playBtn.classList.add('icon-player');
        this.player.pause();
      }

      //全屏切换
      changeFullScreen() {
        if (!isFullScreen()) {
          openFullScreen(this.player);
          return false;
        }
        closeFullScreen();
      }


      //切换循环状态
      changeLoop(e) {
        this.player.loop = !this.player.loop;
        e.target.classList.toggle('is-loop');
      }


      //音量调整
      changeVolume(e) {
        let ratio = Number(e.target.value) / 100;
        this.volume = ratio;
      }

      chageMuted(e) {
        this.player.muted = !this.player.muted;
        e.target.classList.toggle('is-muted');
      }

      //存储观看信息 到本地存储
      setVideoInfo() {
        //视频src=>ID 播放时间
        if (!this.canPlay) {
          //视频准备好了播放的情况下才存储
          return false;
        }
        this.storage.setStorage({
          [this.player.src]: this.player.currentTime
        })

      }
    }

    function padLeft(num) {
      return String(num)[1] && String(num) || '0' + num;
    }

    const myPlay = new MyVideo();
  </script>
</body>

</html>
  • 12
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值