微信小程序之自定义video组件

自定义video

  1. 小程序项目中使用到video组件,并且根据需求自定义播放、进度条和全屏按钮,本次需求比较简单,没有涉及试看,指定时长等功能,仅是简单的播放,记录一下,以防遗忘。贴下video组件的文档链接:video

  2. 大概效果:在这里插入图片描述

  3. 本文中用到的一些比较重要的属性和API
    - duration:指定视频时长(总时长)
    - controls:是否显示默认播放控件(播放/暂停按钮、播放进度、时间)
    - poster:视频封面
    - bindended:当视频播放到末尾触发
    - bindtimeupdate:视频进度发生变化时触发
    - videoContext:创建VideoContext实例,与video标签的id绑定,API地址---->传送门
    - 进度条用到了slider组件

话不多说,贴代码

wxml:(结构有些跟业务相关,实际按需求来实现)

<view class="video_area">
           <view class="cover">
               <video id="video" 
                   class="video_wrapper" 
                   src="{{videoSrc}}" 
                   controls="{{false}}" 
                   poster="{{poster}}"
                   show-center-play-btn="{{false}}"
                   custom-cache="{{false}}"
                   object-fit="contain" 
                   enable-progress-gesture="{{false}}"
                   vslide-gesture-in-fullscreen="{{false}}"
                   bindtimeupdate="bindtimeupdateFun"
                   bindtap="playStart"
                   bindended="bindEndedFun">
                   <block>
                       <view wx:if="{{startPlay}}" class="cover_bg"></view>
                       <view wx:elif="{{play}}" class="cover_bg playState"></view>
                       <view wx:else class="cover_bg pauseState"></view>
                   </block>
                   <!-- 首次会有一个播放按钮 -->
                   <image wx:if="{{startPlay}}" src="./images/play_begin.png" mode="widthFix" class="play_begin" catchtap="playStart" data-type="init"/>
                   <!-- 视频中间的播放按钮 -->
                   <image wx:if="{{!play && !startPlay}}" src="./images/play.svg" class="play" mode="widthFix"  catchtap="playStart"/>
                   <!-- 进度条相关 -->
                   <view class="process-area" wx:if="{{!startPlay}}">
                       <image src="{{!play ? './images/play_min.svg' : './images/pause.svg'}}" class="play_min" mode="widthFix" catchtap="playStart"/>
                       <text class="process_time time {{full && 'fullScreen'}}">{{process_duration}}</text>
                       <view class="slider_container">
                           <slider bindchange="sliderChange" 
                               bindchanging="sliderChanging" 
                               step="0.5" 
                               backgroundColor="#D8D8D8" activeColor="#FF835B"
                               block-size="12"
                               value="{{sliderValue}}" />
                       </view>
                       <text class="total_process time {{full && 'fullScreen'}}">{{total_duration}}</text>
                       <image src="{{!full? './images/fullScreen.svg' : './images/exit.svg'}}" class="screen_btn" mode="widthFix" catchtap="fullScreen"/>
                   </view>
               </video>
           </view>
       </view>

js

const { addZero } = require('../../utils/util');  // 格式化时间

let app = getApp();

Component({
  options: {
    styleIsolation: 'apply-shared'
  },
  /**
   * 组件的属性列表
   */
  properties: {
    
  },

  /**
   * 组件的初始数据
   */
  data: {
    poster: 'https://xxx/7831600741950_.pic.jpg', // 视频封面
    videoSrc: 'https://xxx/1600742007827574.mp4', // 视频地址
    startPlay: true, // 首次播放显示 播放视频按钮。
    play: false, // 播放true 暂停 false
    sliderValue: 0, // 进度条的value
    process_duration: '00:00', // 进度
    total_duration: '00:00', // 总时长
    full: false, // 全屏
    duration: '', // 视频长度 单位秒
  },
  ready() {
    this.videoContext = wx.createVideoContext('video', this); //  创建videoContext实例
    this.updateState = true; // 防止在播放过程中拖拽失败
  },
  /**
   * 组件的方法列表
   */
  methods: {
    // 播放
    playStart(e) {
      const {
        type = null
      } = e.currentTarget.dataset;
      if (type && type == 'init') {
        // 开始播放
        this.setData({
          startPlay: false,
          play: true
        })
        this.videoContext.play();
        return false;
      }
      this.data.play ? this.videoContext.pause() : this.videoContext.play();
      this.setData({
        play: !this.data.play,
        startPlay: false,
      })
    },
    // 全屏/退出全屏
    fullScreen() {
      !this.data.full ? this.videoContext.requestFullScreen() : this.videoContext.exitFullScreen();
      this.setData({
        full: !this.data.full
      })
    },
    // 完成一次拖动后触发的事件
    sliderChange(e) {
      if (this.data.duration) {
        // 视频跳转到指定位置
        this.videoContext.seek(e.detail.value / 100 * this.data.duration);
        this.updateState = true;
        this.setData({
          sliderValue: e.detail.value
        })
      }
    },
    // 拖动过程中触发的事件
    sliderChanging(e) {
      this.updateState = false; // 拖动过程中不允许更新进度条
    },
    // 播放进度
    bindtimeupdateFun(e) {
      if (this.updateState) {
        let sliderValue = e.detail.currentTime / e.detail.duration * 100;
        this.setData({
          sliderValue,
          duration: e.detail.duration,
          total_duration: this.formatSeconds(e.detail.duration), // 总时长 格式 00:00
          process_duration: this.formatSeconds(e.detail.currentTime), // 进度 格式 00:00
        })
      }
    },
    // 结束
    bindEndedFun(){
      this.setData({
        play: false
      })
    },
    // 格式化视频时长  格式 00:00
    formatSeconds(value) {
      if (value == undefined) {
        value = 0;
      }
      var second = parseInt(value); // 秒
      var min = 0; // 分
      var hour = 0; // 小时
      if (second > 60) {
        min = parseInt(second / 60);
        second = parseInt(second % 60);
        if (min > 60) {
          hour = parseInt(min / 60);
          min = parseInt(min % 60);
        }
      }
      
      var result = "";
      // if (hour > 0) {
      //   hour = addZero(parseInt(hour));
      //   result = hour + ":";
      // }
      if (min > 0) {
        min = addZero(parseInt(min));
        result = min + ":";
      }else{
        result = "00:";
      }
      if(second > 0){
        second = addZero(parseInt(second));
        result = result + second;
      }else{
        result = result + '00';
      }
      return result;
    },
  }
})

utils/util.js
addZero = (i) => {
  i = typeof i === 'string' ? Number(i) : i;
  return i < 10 ? "0" + i : "" + i;
}

wxss

.video_area{
    width: 100%;
    height: 350rpx;
    position: relative;
}

.cover{
    position: absolute;
    top: 0;
    left: -62rpx;
    width: 620rpx;
    height: 350rpx;
    border-radius: 20rpx;
    box-shadow: 0 7rpx 30rpx rgba(136,136,136,.6);
    overflow: hidden;
}

.video_area .video_wrapper{
    width:100%;
    height: 100%;
    position: relative;
    border-radius: 20rpx;
}
.cover_bg{
    width: 100%;
    height: 100%;
    opacity: 0.3;
    background: #000;
    position: absolute;
    top: 0;
    left: 0;
    z-index: 10;
}
.cover_bg.playState{
    opacity: 0.75;
    background: linear-gradient(180deg, rgba(0,0,0,0.00) 79%, #040404 98%);
}
.cover_bg.pauseState{
    opacity: 0.5;
    background: #000;
}

.video_area .play_begin{
    width: 234rpx;
    height: auto;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 20;
}
.video_area .play{
    width: 71rpx;
    height: auto;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 20;
}
.process-area{
    width: 100%;
    height: 60rpx;
    padding: 0 4% 0 4%;
    position: absolute;
    bottom: 13rpx;
    left: 0;
    display: flex;
    align-items: center;
    box-sizing: border-box;
    z-index: 20;
}
.process-area .time{
    display: inline-block;
    flex:1;
    color: #fff;
    opacity: 0.95;
    font-family: PingFangSC-Regular;
    font-size: 14rpx;
    color: #F4F3F0;
}
.process-area .time.fullScreen{
    font-size: 18rpx
}
.process-area  .time.process_time{
    text-align: right;
    margin-right: -10rpx;
}
.process-area  .time.total_process{
    text-align: left;
    margin-left: -14rpx;
}
.play_min{
    max-width: 24rpx;
    max-height: 24rpx;
    height: auto;
    flex:  1;
    display: inline-block;
    padding: 20rpx;
    margin:-20rpx;
}
.screen_btn{
    max-width: 24rpx;
    max-height: 24rpx;
    height: auto;
    flex:  1;
    display: inline-block;
    padding: 20rpx;
    margin:-20rpx;
}
.slider_container{
    flex:6;
}

—2020 0925
额,测试的时候发现个问题,安卓手机在暂停的播放时,拖动进度条,不会触发bindtimeupdate方法,如果直接拖到进度结束,也不会触发video的bindended。不知道有没有大佬们遇到过这个问题,坑啊。。。

自定义微信小程序video的进度条,可以使用wx.createVideoContext()方法获取video组件实例,然后通过该实例的属性和方法来实现自定义进度条的效果。 以下是一个简单的示例代码: wxml文件部分: ```xml <video src="{{src}}" id="myVideo"></video> <view class="progress-bar" style="width: {{progress}}%;"></view> ``` js文件部分: ```javascript Page({ data: { src: '视频地址', duration: 0, // 视频总时长 currentTime: 0, // 当前播放时间 progress: 0 // 进度条进度 }, onLoad: function () { // 获取video组件实例 this.videoContext = wx.createVideoContext('myVideo', this); // 监听视频播放时间变化事件 this.videoContext.onTimeUpdate(this.handleTimeUpdate); // 获取视频总时长 this.videoContext.duration((duration) => { this.setData({ duration: duration }); }); }, handleTimeUpdate: function (e) { // 更新当前播放时间和进度条进度 this.setData({ currentTime: e.detail.currentTime, progress: e.detail.currentTime / this.data.duration * 100 }); } }); ``` css文件部分: ```css .progress-bar { height: 6px; background-color: #ccc; } ``` 在上述代码中,我们首先通过wx.createVideoContext()方法获取video组件实例,然后在onLoad()生命周期函数中监听视频播放时间变化事件,并获取视频总时长。在handleTimeUpdate()函数中,我们更新当前播放时间和进度条进度,最后在wxml文件中使用style属性来设置进度条的宽度。 需要注意的是,为了让Video组件支持进度条拖动功能,还需要在wxml文件中添加bindtouchstart、bindtouchmove和bindtouchend事件,并在js文件中添加相应的处理函数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值