如何快速开发一个自定义的视频播放器(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的身体和灵魂定义好了,下一篇即实现我们灵魂的升华~

相关推荐
hackeroink1 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者3 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-3 小时前
验证码机制
前端·后端
燃先生._.4 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖5 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235245 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240256 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar6 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人7 小时前
前端知识补充—CSS
前端·css