12_微信小程序之微信视频号滚动自动播放视频效果实现

12_微信小程序之微信视频号滚动自动播放视频效果实现

一.获取视频的分辨率、时长、缩略图

微信小程序提供了三种方式可以获取视频的分辨率、时长:

  • wx.getVideoInfo(Object) :只能用于本地视频

  • VideoDecoder.start(Object) :可用于网络视频或者本地视频

  • 借助video标签的bindloadedmetadata回调: 可用于网络视频或者本地视频,但必须在video标签被渲染的情况下才能获取到,并且获取速度较慢

      /**
       * @author gale
       * 传入视频链接,获取视频分辨率,时长,缩略图(视频第一帧的数据)
       */
      decoderSource: function (source) {
        return new Promise((resolve, reject) => {
          var decoder = wx.createVideoDecoder()
          decoder.on("start", (res) => {
            //获取视频分辨率
            var width = res.width
            var height = res.height
            //获取视频时长
            var duration = res.duration/1000
            var formatDur = this.formatDuration(duration)
    
            //获取视频的缩略图,即第一帧的图像
            var frameData = decoder.getFrameData()
            while (!frameData) {
              frameData = decoder.getFrameData()
            }
            var fileName = source.substring(source.lastIndexOf("/") + 1, source.lastIndexOf("."))
            let pngData = upng.encode([new Uint8ClampedArray(frameData.data).buffer], frameData.width, frameData.height)
            let base64 = wx.arrayBufferToBase64(pngData)
            var filePath = wx.env.USER_DATA_PATH + "/" + fileName + ".png"
            var systemManager = wx.getFileSystemManager()
            systemManager.writeFileSync(filePath, base64, "base64")
            resolve({
              width: width,
              height: height,
              duration: duration,
              formatDur: formatDur,
              thumbnail: filePath,
              src: source
            })
            decoder.remove()
          })
          decoder.start({
            source: source,
            mode: 1
          })
        })
      }
    

二.微信小程序中,同一个页面存在多个播放器的坑

在微信小程序中,一个页面内,存在多个播放器时,会导致部分视频不能正常播放,同一页面存在多个video时,video无法正常播放一直在加载转圈

三.格式化视频时长

  /**
   * @author gale
   * 格式化视频时长,如 14.6 格式化成 00:14
   */
  formatDuration: function(seconds) {
    var format = ""
    var h = parseInt(seconds/3600),
        m = parseInt(seconds%3600/60),
        s = parseInt(seconds%3600%60);
    if(h>0){
      h = h<10 ? '0'+h : h
      format += h+":"
    }
    m = m<10 ? '0'+m : m 
    s = s<10 ? '0'+s : s 
    format+=m+":"+s
    return format;
  }

四.在列表中先渲染视频的缩略图和时长,播放当前视频时,将图片替换为video

这里采用自定义组件的方式实现

<!--components/video-list/index.wxml-->
<scroll-view class="video-list" scroll-y bindscroll="onScroll">
  <view class="list">
    <view class="video-item-wrapper" style="width: {{item.videoWidth}}px;" wx:for="{{_videoList}}">
      <view class="video-item" style="height: {{item.videoHeight}}px; background: #00f;">
        <video wx:if="{{playIndex == index}}" id="player" class="player" src="{{item.src}}" object-fit="contain" show-center-play-btn="{{false}}" custom-cache="{{true}}" autoplay="{{true}}"></video>
        <block wx:else>
          <image class="thumbnail" src="{{item.thumbnail}}"/>
          <view class="action">
            <view class="play-wrapper" bindtap="play" data-index="{{index}}">
              <image class="play" src="./images/play.png"/>
              <view style="margin-top: 10rpx;">{{item.formatDur}}</view>
            </view>
          </view>
        </block>
      </view>
    </view>
  </view>
</scroll-view>
// components/video-list/index.js
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    videoList: {
      type: Array,
      value: [],
      observer: function(newVal, oldVal) {
        var that = this
        const query = that.createSelectorQuery()
        query.select(".video-list").boundingClientRect()
        query.exec((res) => {
          var itemWidth = res[0].width
          for(var i=0; i<newVal.length; i++) {
            newVal[i].videoWidth = Math.floor(itemWidth)
            newVal[i].videoHeight = Math.floor(itemWidth/(newVal[i].width/newVal[i].height))
          }
          that.setData({
            _videoList: newVal
          })
        })
      }
    },
    playIndex: {
      type: Number,
      value: -1,
      observer: function(newVal, oldVal) {
        var that = this
        this.setData({
          playIndex: newVal
        })
        if(newVal >= 0) {
          var videoContext = wx.createVideoContext('player', that)
          if(videoContext) {
            videoContext.stop()
          }
          var timer = setTimeout(function() {
          clearTimeout(timer)
            var videoContext = wx.createVideoContext('player', that)
            if(videoContext) {
              videoContext.play()
            }
          }, 500)
        }
      }
    }
  },

  /**
   * 组件的初始数据
   */
  data: {
    _videoList: []
  },

  /**
   * 组件的方法列表
   */
  methods: {
    play: function(event) {
      var that = this
      var index = event.currentTarget.dataset.index
      this.setData({
        playIndex: index
      })
    }
  }
})
/* components/video-list/index.wxss */
.video-list {
  width: 100%;
  height: 100%;
}

.list {
  width: 100%;
}

.video-item-wrapper {
  background: #000;
  padding-top: 200rpx;
  padding-bottom: 200rpx;
  margin-top: 20rpx;
}

.video-item-wrapper:last-child {
  margin-bottom: 20rpx;
}

.video-item {
  position: relative;
  width: 100%;
}

.thumbnail, .player {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 100%;
  height: 100%;
  transform: translate(-50%, -50%);
}

.action {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, .6);
}

.play-wrapper {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: #fff;
}

.play {
  width: 48rpx;
  height: 48rpx;
}

这里给播放器上下各100rpx的间距,是因为让整个item高度撑开,以保证每一个视频都能滑动自动播放,实际上传视频的时候,只需要让上传的视频宽高比能撑开播放器而不设置间距即可

在这里插入图片描述

五.实现滑动自动播放

在这里插入图片描述

// components/video-list/index.js
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    videoList: {
      type: Array,
      value: [],
      observer: function(newVal, oldVal) {
        var that = this
        const query = that.createSelectorQuery()
        query.select(".video-list").boundingClientRect()
        query.exec((res) => {
          var itemWidth = res[0].width
          for(var i=0; i<newVal.length; i++) {
            newVal[i].videoWidth = Math.floor(itemWidth)
            newVal[i].videoHeight = Math.floor(itemWidth/(newVal[i].width/newVal[i].height))
          }
          that.setData({
            _videoList: newVal
          })

          const query = that.createSelectorQuery()
          query.selectAll(".video-item-wrapper").boundingClientRect()
          query.exec((res) => {
            var items = res[0]
            const query = that.createSelectorQuery()
            query.select(".list").boundingClientRect()
            query.exec((res) => {
              var listHeight = res[0].height
              that.setData({
                videoItems: items,
                listHeight: listHeight
              })
            })
          })
        })
      }
    },
    playIndex: {
      type: Number,
      value: -1,
      observer: function(newVal, oldVal) {
        var that = this
        this.setData({
          playIndex: newVal
        })
        if(newVal >= 0) {
          var videoContext = wx.createVideoContext('player', that)
          if(videoContext) {
            videoContext.stop()
          }
          var timer = setTimeout(function() {
            clearTimeout(timer)
            var videoContext = wx.createVideoContext('player', that)
            if(videoContext) {
              videoContext.play()
            }
          }, 500)
        }
      }
    }
  },

  /**
   * 组件的初始数据
   */
  data: {
    _videoList: [],
    videoItems: [],
    listHeight: 0,
    contentHeight: 0
  },

  lifetimes: {
    attached: function() {
      var systemInfo = wx.getSystemInfoSync()
      var contentHeight = systemInfo.windowHeight
      this.setData({
        contentHeight: contentHeight
      })
    }
  },

  /**
   * 组件的方法列表
   */
  methods: {
    play: function(event) {
      var that = this
      var index = event.currentTarget.dataset.index
      this.setData({
        playIndex: index
      })
    },

    onScroll: function(event) {
      var contentHeight = this.data.contentHeight

      var scrollHeight = event.detail.scrollHeight
      var scrollTop = event.detail.scrollTop

      var scrollPosition = Math.floor(scrollTop + contentHeight/2)
      var index = this.getCurrentPlayIndex(scrollPosition, scrollHeight)
      var playIndex = this.data.playIndex
      if(index != playIndex) {
        this.setData({
          playIndex: index
        })
      }
    },

    getCurrentPlayIndex: function(scrollPosition, scrollHeight) {
      var contentHeight = this.data.contentHeight
      var listHeight = this.data.listHeight ? this.data.listHeight:0
      var top = scrollHeight - listHeight
  
      var current = -1
      var videoItems = this.data.videoItems
      if(!videoItems || videoItems.length <= 0) {
        return -1
      } 
      var offset = top - videoItems[0].top
      for(var i=0; i<videoItems.length; i++) {
        if(scrollPosition >= (videoItems[i].top + offset) && scrollPosition <= (videoItems[i].bottom + offset)) {
          current = i
          break
        }
      }
  
      return current
    },
  }
})

在这里插入图片描述

  • 1
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您好,这是两个不同的功能,我来分别回答。 1. Java实现微信、手机号登录 要实现微信、手机号登录,您需要先了解微信开放平台和短信验证码服务商的接口文档。 对于微信登录,您可以通过微信开放平台提供的OAuth2.0授权登录接口实现。具体步骤如下: - 用户点击登录按钮,跳转到微信授权页面。 - 用户在微信授权页面上确认授权登录。 - 微信授权服务器返回授权码。 - 通过授权码获取access_token和openid。 - 使用openid作为用户唯一标识,完成登录逻辑。 对于手机号登录,您可以使用短信验证码服务商提供的接口实现。具体步骤如下: - 用户输入手机号码并点击获取验证码按钮。 - 调用短信验证码服务商提供的发送短信验证码接口,将验证码发送到用户手机上。 - 用户输入收到的验证码并点击登录按钮。 - 调用短信验证码服务商提供的校验短信验证码接口进行验证。 - 验证通过后,使用手机号码作为用户唯一标识,完成登录逻辑。 2. 微信小程序获取手机号授权用户登录功能 要在微信小程序中获取用户手机号码,您可以通过微信小程序提供的button组件和wx.login接口实现。具体步骤如下: - 用户点击获取手机号按钮。 - 调用wx.login接口获取临时登录凭证code。 - 将code发送到您的后台服务器,调用微信提供的auth.code2Session接口换取openid和session_key。 - 后台服务器返回openid和session_key给前端。 - 前端调用微信提供的button组件,设置属性为open-type="getPhoneNumber",并将session_key作为参数传递。 - 用户点击授权登录按钮,微信返回加密的手机号信息和iv。 - 前端将加密的手机号信息和iv发送到后台服务器,调用微信提供的解密算法解密手机号信息。 - 后台服务器返回解密后的手机号给前端,完成登录逻辑。 希望以上回答能够帮助您。如果您还有疑问,请随时提出。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值