如何快速开发一个自定义的视频播放器(2)——模板定义

vue Demo实现

本篇主要介绍基于vue2的模板定义,vue2模板其实也比较偏面相对象。实际开发过程中,当前后端分离时,我们在接到需求的时候,往往可以先于服务端进行开发,提前实现模板页面的代码,以及提前定义我们自己需要的属性和方法。等到我们进行前后端联调的时候,无非就是请求接口然后把接口里面的数据赋值给我们提前定义好的属性即可。 本篇文章主要是demo的html+css实现,以及基本的数据结构和方法定义。

模板及属性方法定义

模板:

html 复制代码
 <div class="video-wrapper">
      <video
        :src="videoSrc"
        id="video"
        class="player"
        style="object-fit:fill"
        webkit-playsinline="true"
        playsinline="true"
        x5-playsinline
        preload="auto"
        :poster="poster"
        x-webkit-airplay="true"
        ref="video"
      ></video>
      <div
        class="fz-player-controls-panel-wrapper"
        id="playerPanel"
        @click="ShowControl"
      >
        <a
          href="javascript:;"
          class="play-btn-wrapper"
          id="playBtn"
          v-if="showStartPlay"
          @click.stop="PlayVideo"
        >
          <img src="https://cdn.cn/common/play-icon.png">
        </a>
        <div
          class="fz-player-controls-panel"
          :style="{opacity:showPannel?'1':'0'}"
          id="panelControls"
        >
          <div class="progress-wrapper">
            <a
              href="javascript:;"
              class="mini-play-btn-wrapper"
              id="miniPlayBtn"
              @click="PlayVideo"
            >
              <img
                src="https://cdn.cn/common/play-mini.png?a=1"
                id='playBtnIcon'
                v-if="video.starting==false"
              />
              <img
                src="https://cdn.cn/common/stop-mini.png?a=1"
                id='playBtnIcon'
                v-else
              />
            </a>
            <div
              class="current-time"
              id="currentTime"
            >{{video.currentTimeString}}</div>
            <div
              class="progress"
              id="progress"
              ref="progress"
              @click="Seek($event)"
            >
              <div class="progress-line"></div>
              <div
                class="progress-current-line"
                id="progressCurrentLine"
                ref="progressCurrentLine"
              ></div>
              <div
                class="current-indicator-wrapper"
                id="indicator"
                ref="indicator"
                @touchstart.stop="TouchStart($event)"
                @touchmove.stop="TouchMove($event)"
                @touchend.stop="TouchEnd($event)"
              >
                <div class="current-indicator"></div>
                <div class="current-indicator-inner"></div>
              </div>
            </div>
            <div
              class="duration-time"
              id="durationTime"
            >{{video.durationString}}</div>
            <a
              href="javascript:;"
              class="full-btn-wrapper"
              id="fullBtn"
              @click="SetFullScreen"
            >
              <img src="https://cdn.cn/common/full-icon.png" />
            </a>
          </div>
        </div>
      </div>
    </div>Ï

注意

  1. 正常的video在手机端播放的时候都是默认全屏播放,因此我们需要控制视频初始内联播放,因此需要设置一下属性:

webkit-playsinline="true"

playsinline="true"

x5-playsinline

这里同时设置了3个playsinline主要是兼容不同的设备

webkit-playsinline="true":这是一个非标准的Apple属性,用于在iOS Safari浏览器上控制视频的内联播放。当该属性设置为true时,视频将在页面内播放,而不是全屏播放。

playsinline="true":这是HTML5的标准属性,与webkit-playsinline的功能相同。它也是用来控制视频是否在页面内播放,而不是全屏播放。这个属性在非Safari浏览器(例如Chrome和Firefox)上更有用。

x5-playsinline:这是由腾讯X5内核浏览器(例如QQ浏览器,微信内置浏览器)提供的属性,用于控制视频的内联播放。

2.#playerPanel 节点为control-mask节点是父元素,绑定了click方法,因此其内部的子元素的一些绑定的可能出现冲突的事件都需要阻止冒泡,故而我们绑定时候这样写就可以了: @click.stop

3.我们知道控制一个元素隐藏有很多种方式,有display设置为none和block,有visibility设置为visible和hidden,还有opacity设置为0和1。这里采用opacity主要是为了实现淡入淡出的效果,同时能够在初始化的时候拿到子元素渲染后的宽高等信息(当初始display为none的时候实际上是拿不到offsetLeft和offsetWidth等信息的)

样式:

less 复制代码
.video-wrapper {
    width: 100%;
    position: relative;
    width: 7.5rem;
    height: 4.2168rem;
    .player {
      width: 100%;
      height: 100%;
    }
    .poster {
      width: 100%;
      height: 100%;
      position: absolute;
      z-index: 1;
      top: 0;
      left: 0;
      img {
        width: 100%;
        height: 100%;
      }
    }
    .loading-icon {
      width: 0.72rem;
      height: 0.72rem;
      position: absolute;
      z-index: 1;
      top: 50%;
      left: 0;
      right: 0;
      transform: translateY(-50%);
      margin: 0 auto;
    }
    .fz-player-controls-panel-wrapper {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      z-index: 2;
      .play-btn-wrapper {
        width: 1.2rem;
        position: absolute;
        top: 50%;
        transform: translateY(-50%);
        left: 0;
        right: 0;
        margin: 0 auto;
        img {
          width: 100%;
        }
      }
      .fz-player-controls-panel {
        width: 100%;
        height: 0.68rem;
        background: rgba(23, 23, 26, 0.6);
        position: absolute;
        bottom: 0;
        transition: opacity linear 0.2s;
        opacity: 0;
        .progress-wrapper {
          width: 100%;
          height: 100%;
          display: flex;
          align-items: center;
          .mini-play-btn-wrapper {
            width: 0.36rem;
            height: 0.36rem;
            margin: 0 0.24rem;
            img {
              width: 100%;
            }
          }
          .full-btn-wrapper {
            width: 0.36rem;
            height: 0.36rem;
            margin: 0 0.24rem;
            img {
              width: 100%;
            }
          }
          .current-time {
            width: 0.59rem;
            height: 100%;
            margin-right: 0.24rem;
            font-size: 0.22rem;
            color: #fff;
            display: flex;
            align-items: center;
            flex-flow: row-reverse;
          }
          .duration-time {
            width: 0.59rem;
            height: 100%;
            margin-left: 0.24rem;
            font-size: 0.22rem;
            color: #fff;
            display: flex;
            align-items: center;
          }
          .progress {
            width: 4.39rem;
            height: 100%;
            position: relative;
            .progress-line {
              width: 100%;
              height: 0.04rem;
              background: #ffffff;
              border-radius: 0.02rem;
              opacity: 0.4;
              top: 50%;
              transform: translateY(-50%);
              left: 0;
              right: 0;
              position: absolute;
              margin: 0 auto;
              z-index: 1;
            }
            .progress-current-line {
              height: 0.04rem;
              background: linear-gradient(90deg, #2cd3d7 0%, #2acf6f 100%);
              border-radius: 0.02rem;
              width: 0;
              top: 50%;
              transform: translateY(-50%);
              left: 0;
              right: 0;
              position: absolute;
              z-index: 2;
            }
            .current-indicator-wrapper {
              top: 50%;
              transform: translateY(-50%) translateX(-0.16rem);
              position: absolute;
              z-index: 3;
              width: 0.32rem;
              height: 0.32rem;
              transition: left linear 0.03s;
              .current-indicator {
                width: 100%;
                height: 100%;
                background: linear-gradient(90deg, #2cd3d7 0%, #2acf6f 100%);
                opacity: 0.4;
                border-radius: 50%;
              }
              .current-indicator-inner {
                width: 0.2rem;
                height: 0.2rem;
                position: absolute;
                top: 50%;
                transform: translateY(-50%);
                left: 0;
                right: 0;
                margin: 0 auto;
                border-radius: 50%;
                background: linear-gradient(90deg, #2cd3d7 0%, #2acf6f 100%);
              }
            }
          }
        }
      }
    }
  }

说明 样式采用了less,单位使用rem,写样式用这些loader真的很方便。

js:

javascript 复制代码
 export default {
  name: 'videoTest',
  data() {
    return {
      // 是否展示control-pannel
      showPannel: false,
      // 是否展示start play btn
      showStartPlay: true,
      // 视频资源地址
      videoSrc:
        'https://cdn.cn/dubbing/2018-08-01/test.mp4',
      // 视频封面地址
      poster:
        'https://cdn.cn/2024-03-19/test.jpg-thumb',
      video: {
        // 定义视频相关属性 自身
        target: null,
        // 当前时间
        currentTime: 0,
        // 当前时间展示字符串
        currentTimeString: '00:00',
        // 视频时长
        duration: 0,
        // 视频时长展示字符串
        durationString: '00:00',
        // 视频播放状态
        status: 'pause',
        // 进度条进度百分比
        progress: 0,
        // 视频是否开始播放
        starting: false,
        // control-pannel展示时间
        showTime: 2000,
        // control-pannel展示及时器
        showTimer: null,
        // 拖动的时间
        offsetCurrentTime: 0
      }
    }
  },
  created() {},
  mounted() {
    // 加载视频信息
    this.LoadVideo(this.$refs.video)
  },
  methods: {
    LoadVideo(video) {
     // 初始化视频属性及事件
    },
    GetTime(duration) {
     // 时间展示处理
      const minute = ~~(duration / 60)
      const second = ~~(duration % 60)
      const hour = ~~((duration - second) / 60 / 60)
      const ms = minute > 9 ? minute : `0${minute}`
      const ss = second > 9 ? second : `0${second}`
      const hs = hour > 9 ? hour : `0${hour}`
      return hour === 0 ? [ms, ss].join(':') : [hs, ms, ss].join(':')
    },
    Seek(e) {
     // seek通用
    },
    TouchStart(e) {
     // indicator拖动开始
    },
    TouchMove(e) {
      // indicator拖动
    },
    TouchEnd(e) {
       // indicator拖动结束
    },
    ShowControls(time = 2000) {
       // indicator拖动开始
    },
    ShowControl() {
       // control-pannel展示逻辑
    },
    PlayVideo() {
      // 播放视频通用
    },
    SetFullScreen() {
      // 全屏播放
    }
  }
}

说明 js这里的实现很偏面相对象了,实际上只是定义了整个播放器的属性和一些方法,这种编程思路比较符合我们正常人的思维模式,一般都是自上而下,从宏观到微观,当我们想好了我们的功能用例都需要哪些属性和方法之后,那么基本等于我们的工作完成了一大半,除非遇到个别技术细节,否则能把开发效率提高到一个很高的level。

总结

html+css基本是前端的身体,js才是灵魂,本篇我们已经把我们demo的身体和灵魂定义好了,下一篇即实现我们灵魂的升华~

相关推荐
落魄切图仔17 分钟前
echarts柱状选中shadow阴影背景宽度设置
前端·javascript·echarts
ZJ_.26 分钟前
Node.js 使用 gRPC:从定义到实现
java·开发语言·javascript·分布式·rpc·架构·node.js
阿垚啊44 分钟前
列表渲染 v-for
前端·javascript·vue.js·html
铁匠匠匠1 小时前
django学习入门系列之第三点《BootSrap初了解》
前端·经验分享·笔记·python·学习·django·前端框架
曲辒净1 小时前
如何实现图片垂直旋转90度的问题
javascript
前端宝哥1 小时前
Composition API VS Options API:谁才是你的最佳选择?
前端·javascript·vue.js
不露声色丶1 小时前
elementPlus表格二次封装
前端·javascript·vue.js
王天乐0071 小时前
ElementUI的搭建
前端·javascript·elementui
小秋菇娘1 小时前
vue 启动项目报错Syntax Error: Error: PostCSS received undefined instead of CSS string
css·vue.js·postcss
OpenTiny社区2 小时前
7月6日 VueConf 技术大会即将在深圳举办
前端·vue.js·github