微信小程序 视频列表滑动无限循环(仿抖音)

一、写在前面:

1:安卓ios表现基本一致,不是swiper组件实现,滑动效果流畅不卡顿,实现了列表无限循环。不是使用官方的腾讯视频播放组件,完整代码在下面

2:实现功能:支持位置导航、拨打电话、复制微信号、分享、联系客服、解析腾讯视频地址和拖动视频进度功能,暂未添加评论留言功能,后期会逐步增加

3:关于视频存储的问题,这里增加了腾讯视频方式,可以减少自身存储的相关费用及性能问题,这里要说明一下,并非使用腾讯视频播放组件,那个会有几秒的广告,体验太差,这里是直接拿的腾讯视频源播放地址的mp4地址,并且没有腾讯视频的logo,如下图(将视频上传至腾讯视频,然后存储播放页面地址进行解析就可以了)

先上效果图

 二、开发背景说明

微信小程序中,同一个页面最多支持添加三个video组件,所以就通过数据处理的方式更新显示播放,有人说只写一个也行,如果只有一个那么上下滑动到一半位置的时候是看不到下一个视频内容的,衔接效果不好

三、代码(父级页面)

/*
*   navbar是我自己封装的头部导航组件
    avideo-swiper 为视频列表组件
*/
<navbar parameter='{ {parameter}}' showTitle="{ {showTitle}}" ></navbar>
<avideo-swiper 
  video-list="{ {videoList}}" 
  initial-index="{ {videoIndex}}" 
  bind:change="onChange" 
  duration="{ {duration}}" 
  bind:play="onPlay" 
  bind:wait="onWait"
></avideo-swiper>

他们官方让多写一些内容,要不然就提示内容不佳,我就把代码拿出来了

const app = getApp();

Page({
  data: {
    showTitle:true,
    parameter: {
      'navbar': '1',
      'return': '1',
      'title': '乡村线报',
      'color': true,
      'class': 'trans',
    },
    url: app.globalData.url,
    videoList: [],
    pageNum: 1,
    pageSize: 5,
    videoIndex: 0, //初始播放第几个
    duration: 500, //滑动切换时常
    dataIdxNow:0,  //当前播放第几个,获取分享内容用的
  },
  onLoad(options) {
    if(options.id){
      this.setData({
        shareId:options.id
      })
      // 获取视频详情
      this.getVideoDetail()

    } else {
      // 获取视频列表
      this.getVideoList()
    }
  },
  // 获取视频列表
  getVideoList() {
    let that = this,
      {
        pageSize,
        pageNum
      } = that.data
    wx.request({
      url: that.data.url + 'xxxxxxx&page=' + pageNum + '&limit=' + pageSize,
      header: {
        'content-type': 'application/json'
      },
      success(res){
        if (res.data.code == 0) {
          let list = res.data.data.list;
          list.push({})
          
          let LoadEnd = res.data.data.page_count <= pageNum ? true : false

          that.setData({
            LoadEnd,
            videoList:[...that.data.videoList, ...list]
          })
        }
      }
    })
  },
  // 获取视频详情
  getVideoDetail() {

    let that = this,shareId = that.data.shareId
    wx.request({
      url: that.data.url + 'xxxxxx&id=' + shareId,
      header: {
        'content-type': 'application/json'
      },
      success(res){
        if (res.data.code == 0) {
          let detail = res.data.data.data

          that.setData({
            videoList:[...[],...[detail]]
          })
        }
        that.getVideoList()
      }
    })
  },
  
  onChange(e) {
    let dataIdx = e.detail.dataIdx,
      {
        videoList,
        LoadEnd
      } = this.data

    // 剩余2个时加载下一页
    if ((dataIdx + 2) == videoList.length) {
      this.setData({
        pageNum: LoadEnd ? 1 : this.data.pageNum + 1
      })

      this.getVideoList()
    }

    this.setData({
      dataIdxNow:dataIdx
    })
  },
  onPlay(e) {
  },
  onWait(e) {
  },

  //分享,这里区分点击的是右上角三个点还是页面分享按钮
  onShareAppMessage (res) {
    let title = '',shareInfo=''

    if(res.from == 'button'){
      shareInfo = res.target.dataset.shareinfo;
      
    } else {
      let {videoList,dataIdxNow} = this.data
      shareInfo = videoList[dataIdxNow]
    }

    title = shareInfo.content
    if(shareInfo.province){
        title = title+"ꔷ"+shareInfo.province+""+shareInfo.position
    }

    return {
      title: title,
      path: '/pages/video_swiper/video-swiper?id='+shareInfo.id,
      imageUrl: shareInfo.pic_url
    }
  },
})

 四、代码(avideo-swiper组件)

<wxs module="touch" src="./touch.wxs"></wxs>
<view class="aswiper">
  <view 
    id="aswiper__track" 
    class="aswiper__track" 
    bind:touchstart="{
   
   {touch.touchstart}}" 
    catch:touchmove="{
   
   {touch.touchmove}}" 
    bind:touchend  ="{
   
   {touch.touchend}}" 
    change:trackData="{
   
   {touch.trackDataObserver}}" 
    trackData="{
   
   {trackData}}" 
    bind:transitionend="{
   
   {touch.onTransitionEnd}}"
  >
    <view 
      wx:for="{
   
   {players}}"
      wx:for-item="player" 
      wx:for-index="idx" 
      wx:key="id" 
      class="aswiper-item aswiper-item--hidden"
    >
      <!-- 有视频 -->
      <view class="aswiper-content" wx:if="{
   
   {player.src}}">
        <video 
          id="{
   
   {player.id}}" 
          class="aswiper-item-video" 
          data-player-idx="{
   
   {idx}}" 
          src="{
   
   {player.src}}" 
          loop="{
   
   {loop}}" 
          autoplay="{
   
   {playerIdx == idx ? true :false}}" 
          object-fit="{
   
   {objectFit}}" 
          enable-play-gesture="{
   
   {false}}" 
          enable-progress-gesture="{
   
   {true}}" 
          show-center-play-btn="{
   
   {false}}" 
          show-fullscreen-btn="{
   
   {false}}"
          show-progress="{
   
   {true}}" 
          controls="{
   
   {true}}" 
          bindplay="onPlay" 
          bindpause="onPause" 
          bindended="onEnded" 
          binderror="onError" 
          bindtimeupdate="onTimeUpdate" 
          bindwaiting="onWaiting" 
          bindprogress="onProgress" 
          bindloadedmetadata="onLoadedMetaData"
        ></video>
        <!-- 视频覆盖层 -->
        <view class="video-overlay" data-player-idx="{
   
   {idx}}" bind:tap="onVideoOverlayTap">
          <view class="aswiper-item-panel" hidden="{
   
   {delayShowPanel && !player.scene}}">
            <default-panel video="{
   
   {curQueue[idx]}}" player-idx="{
   
   {idx}}" cur-player-idx="{
   
   {playerIdx}}"></default-panel>
          </view>
          <!-- 暂停播放按钮 -->
          <image hidden="{
   
   {!player.scene || player.status !== 2}}" data-player-idx="{
   
   {idx}}" class="video-play-btn" src="./image/play-btn.png" mode="aspectFit" catch:tap="onVideoPlayBtnTap" />
        </view>
      </view>
      <!-- 无视频展示广告 -->
      <view class="aswiper-content" wx:if="{
   
   {player && !player.src}}" >
        <ad-custom unit-id="adunit-33e6e9f20e115550" style="height: 100%;"></ad-custom>
      </view>
      <view class="aswiper-content__overlay"></view>
    </view>
  </view>
</view>
const app = getApp()

Component({
	properties: {
		vertical: {
			type: Boolean,
			value: true
		},
		duration: {
			type: Number,
			value: 500
		},
		videoList: {
			type: Array,
			value: []
		},
		initialIndex: {
			type: Number,
			value: 0
		},
		objectFit: {
			type: String,
			value: 'contain'
		},
		loop: {
			type: Boolean,
			value: true
		},
		autoPlay: {
			type: Boolean,
			value: true
		},
		panelType: {
			type: String,
			value: 'default'
		},
		width: {
			type: Number,
			value: 0
		},
		height: {
			type: Number,
			value: 0
		}
	},
	data: {
		players: [
			{
				id: 'video_0',
				scene: false,
				status: 0, // 0: initial; 1: play; 2: pause
				src: null,
			},
			{
				id: 'video_1',
				scene: false,
				status: 0,
				src: null,
			},
			{
				id: 'video_2',
				scene: false,
				status: 0,
				src: null,
			}
		],
		playerIdx: 0,
		trackData: {
			width: 0,
			height: 0,
			vertical: true,
			duration: 500,
			operation: {}
		},
		curQueue: [{}, {}, {}],
    curVideo: null,
    navH:0,
	},
	observers: {
		
		initialIndex(index) {
			if (index < 0) {
				throw new Error('initialIndex can not be less than 0.');
			}
		},
		videoList(videoList) {
			if (!Array.isArray(videoList)) {
				throw new Error('videoList is expected an array.');
			}
		},
		'initialIndex, videoList': function (initialIndex, videoList) {
			const operation = {};
			if (initialIndex !== this._initialIndex && videoList.length > 0) {
				this._initialIndex = initialIndex;
				this._dataIdx = initialIndex;
				operation.dataIdx = initialIndex;
			}
			operation.dataCount = videoList.length;
			if (!this._videoList) {
				this._playing = this.data.autoPlay;
			}
			this.setData(
				{
					'trackData.operation': operation
				},
				() => {
					this.loadCurQueue(this._dataIdx, this._playing);
				}
			);
		}
	},
	created() {
		this._rect = null;
		this._videoList = null;
		this._initialIndex = -1;
		this._dataIdx = 0;
		this._lastDataIdx = -1;
		this._lastVideo = null;
		this._playing = true;
		this._pausing = {
			idx: -1,
			timmer: null
		};
		this._savedPlayerIdx = -1;
		this._playerIdx = 0;
		this._isAndroid = wx.getSystemInfoSync().platform === 'android';
	},
	attached() {
    // 创建视频对象
    this._videoContexts = [];
		this.data.players.forEach((item) => {
			this._videoContexts.push(wx.createVideoContext(item.id, this));
    })
	},
	ready() {
		this.initialize();
	},

	methods: {
		play() {
			const { curVideo } = this.data;
			if (curVideo) {
				this.playCurrent(this._playerIdx);
			}
		},
		pause() {
			this._videoContexts.forEach((ctx) => {
				ctx.pause();
			});
		},
		swiperChange(args) {
			const dataIdx = args.dataIdx;
			this._dataIdx = dataIdx;
			this.loadCurQueue(dataIdx, false);
		},
		loadCurQueue(dataIdx, playing = false) {
      const curQueue = this.data.curQueue.slice(0);
      const { videoList, players } = this.data;

			const maxIdx = videoList.length - 1;
			let curVideo = null;
			let curDataIdx = dataIdx;
			let cur = 0;
			if (maxIdx < 0) {
				curQueue.forEach((video) => {
					video = {};
				});
			} else {
				if (curDataIdx > maxIdx) {
					curDataIdx = maxIdx;
				}
				let preV = {},
					  nextV = {};
				let pre = 0,
					  next = 0;
				cur = curDataIdx % 3;
				pre = cur - 1;
				if (pre < 0) {
					pre = 2;
				}
				next = cur + 1;
				if (next > 2) {
					next = 0;
				}
				if (curDataIdx - 1 >= 0) {
					preV = videoList[curDataIdx - 1];
				}
				if (curDataIdx + 1 <= maxIdx) {
					nextV = videoList[curDataIdx + 1];
				}
				curQueue[pre] = preV;
				curQueue[next] = nextV;
				curVideo = videoList[curDataIdx];
				curQueue[cur] = curVideo;
				curVideo = videoList[curDataIdx];
			}

			for (let i = 0; i < 3; i++) {
				const video = curQueue[i];
				const player = players[i];
				const src = video.url || null;
				player.src = src;
      }

			this.setData({
				players,
				curQueue,
				curVideo
			});
			this._playerIdx = cur;
			this._savedPlayerIdx = -1;
			if (curVideo) {
				this._videoList = videoList;
				if (curDataIdx !== this._lastDataIdx) {
					this._lastDataIdx = curDataIdx;
					this.triggerEvent('change', {
						dataIdx: curDataIdx,
					})
				}
				this._lastVideo = curVideo;
				if (playing && curVideo) {
					wx.nextTick(() => {
						this._savedPlayerIdx = cur;
						this.playCurrent(cur);
					});
				}
			}
    },
    // 点击暂停播放
		onVideoOverlayTap(e) {
			const idx = e.currentTarget.dataset.playerIdx;
			const ctx = this._videoContexts[idx];
			const player = this.data.players[idx];
			if (player.status === 2) {
				if (player.src) {
					ctx.play();
				}
			} else {
				ctx.pause();
				const status = `players[${idx}].status`;
				const scene = `players[${idx}].scene`;
				this.setData({
					[status]: 2,
					[scene]: true
				});
			}
    },
    // 点击开始播放
		onVideoPlayBtnTap(e) {
			const idx = e.currentTarget.dataset.playerIdx;
			const ctx = this._videoContexts[idx];
			const player = this.data.players[idx];
			if (player.src) {
				ctx.play();
			}
		},
		onPlay(e) {
			const idx = e.currentTarget.dataset.playerIdx;
			const player = this.data.players[idx];
			const _pausing = this._pausing;
			const lastStatus = player.status;
			this._playing = true;
			if (idx === _pausing.idx) {
				clearTimeout(_pausing.timmer);
				this._pausing = {
					idx: -1,
					timmer: null
				};
      }
			if (lastStatus !== 1) {
				const scene = `players[${idx}].scene`;
				const status = `players[${idx}].status`;
				this.setData({
					[scene]: true,
					[status]: 1
				});
				if (lastStatus === 2) {
					this.trigger(e, 'replay');
				} else {
					this.trigger(e, 'play');
				}
			}
		},
		onPause(e) {
			const idx = e.currentTarget.dataset.playerIdx;
			const player = this.data.players[idx];
			this._playing = false;
			if (player.status !== 2) {
				const status = `players[${idx}].status`;
				this._pausing = {
					idx,
					timmer: setTimeout(() => {
						this.setData({
							[status]: 2
						});
						this._pausing = {
							idx: -1,
							timmer: null
						};
					}, 200)
				};
			}
			this.trigger(e, 'pause');
		},
		onEnded(e) {
			this.trigger(e, 'ended');
		},
		onError(e) {
			this.trigger(e, 'error');
		},
		onTimeUpdate(e) {
			this.trigger(e, 'timeupdate');
		},
		onWaiting(e) {
			this.trigger(e, 'wait');
		},
		onProgress(e) {
			this.trigger(e, 'progress');
		},
		onLoadedMetaData(e) {
			this.trigger(e, 'loadedmetadata');
		},
		trigger(e, type) {
			let ext = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
			let detail = e.detail;
			const { curVideo } = this.data;
			this.triggerEvent(type, Object.assign(Object.assign({}, detail), { video: curVideo }, ext));
		},
		playCurrent(cur) {
			const { players } = this.data;
			this._videoContexts.forEach((ctx, idx) => {
				const player = players[idx];
				if (cur === idx) {
					if (player.src) {
						ctx.play();
					}
				} else {
					player.scene = false;
					player.status = 0;
					ctx.stop();
				}
			});
			this.setData({
				playerIdx: cur,
				players
			});
		},
		onTransitionEnd() {
			const { curVideo } = this.data;
			if (this._playerIdx !== this._savedPlayerIdx) {
				if (curVideo) {
					this._savedPlayerIdx = this._playerIdx;
					this.playCurrent(this._playerIdx);
				}
			}
		},
		initialize() {
			this.getRect('#aswiper__track').then((rect) => {
        const { width, height } = this.data;
        
				this._rect = rect;
				this.setData({
					'trackData.width': width,
					'trackData.height': height,
					'trackData.operation': {
						rect
					}
				});
			});
		},
		getRect(selector, all) {
			var _this = this;
			return new Promise(function (resolve) {
				wx
					.createSelectorQuery()
					.in(_this)
				[all ? 'selectAll' : 'select'](selector)
					.boundingClientRect(function (rect) {
						if (all && Array.isArray(rect) && rect.length) {
							resolve(rect);
						}
						if (!all && rect) {
							resolve(rect);
						}
					})
					.exec();
			});
		},
		noop() {
		}
	}
});

 有问题请留言

猜你喜欢

转载自blog.csdn.net/fanhu6816/article/details/128081444