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

相关推荐
Devil枫1 分钟前
Vue 3 单元测试与E2E测试
前端·vue.js·单元测试
尚梦36 分钟前
uni-app 封装刘海状态栏(适用小程序, h5, 头条小程序)
前端·小程序·uni-app
GIS程序媛—椰子1 小时前
【Vue 全家桶】6、vue-router 路由(更新中)
前端·vue.js
前端青山2 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
毕业设计制作和分享2 小时前
ssm《数据库系统原理》课程平台的设计与实现+vue
前端·数据库·vue.js·oracle·mybatis
程序媛小果2 小时前
基于java+SpringBoot+Vue的旅游管理系统设计与实现
java·vue.js·spring boot
从兄3 小时前
vue 使用docx-preview 预览替换文档内的特定变量
javascript·vue.js·ecmascript
凉辰3 小时前
设计模式 策略模式 场景Vue (技术提升)
vue.js·设计模式·策略模式
清灵xmf4 小时前
在 Vue 中实现与优化轮询技术
前端·javascript·vue·轮询
大佩梨4 小时前
VUE+Vite之环境文件配置及使用环境变量
前端