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

相关推荐
不是吧这都有重名21 分钟前
利用systemd启动部署在服务器上的web应用
运维·服务器·前端
霸王蟹21 分钟前
React中巧妙使用异步组件Suspense优化页面性能。
前端·笔记·学习·react.js·前端框架
Maỿbe30 分钟前
利用html制作简历网页和求职信息网页
前端·html
森叶1 小时前
Electron 主进程中使用Worker来创建不同间隔的定时器实现过程
前端·javascript·electron
霸王蟹1 小时前
React 19 中的useRef得到了进一步加强。
前端·javascript·笔记·学习·react.js·ts
霸王蟹1 小时前
React 19版本refs也支持清理函数了。
前端·javascript·笔记·react.js·前端框架·ts
繁依Fanyi1 小时前
ColorAid —— 一个面向设计师的色盲模拟工具开发记
开发语言·前端·vue.js·编辑器·codebuddy首席试玩官
codelxy1 小时前
vue引用cesium,解决“Not allowed to load local resource”报错
javascript·vue.js
程序猿阿伟2 小时前
《社交应用动态表情:RN与Flutter实战解码》
javascript·flutter·react native
明似水2 小时前
Flutter 开发入门:从一个简单的计数器应用开始
前端·javascript·flutter