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

news2024/10/7 18:23:22

一、写在前面:

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() {
		}
	}
});

 有问题请留言

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/45565.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何实现fastdds的topic调试工具

在使用fastdds进行实际的开发调试中发现&#xff0c;常常需要对已经发布的话题进行进一步的调试&#xff0c;比如话题存在&#xff0c;话题内容&#xff0c;话题频率等等信息都需要确认&#xff0c;尤其是话题内容。这时候就需要一个能进行这项操作的调试工具。可能对于用过ros…

hit_os_lab2 操作系统启动

前置知识 1.1 基础概念 入理论课程的学习。 如果网易云上的课程无法查看&#xff0c;也可以看 Bilibili 上的 操作系统哈尔滨工业大学李治军老师。 L2 开始揭开钢琴的盖子L3 操作系统启动 同济大学赵炯博士的《Linux 内核 0.11 完全注释&#xff08;修正版 V3.0&#xff09…

防火墙练习实验

♥️作者&#xff1a;小刘在C站 ♥️每天分享云计算网络运维课堂笔记&#xff0c;一起努力&#xff0c;共赴美好人生&#xff01; ♥️夕阳下&#xff0c;是最美的&#xff0c;绽放。 目录 二. 实验命令 一.实验图纸 二. 实验命令 ciscoasa> en Password: ciscoasa# co…

DJ12-2-4 串操作指令

目录 1. REP 重复前缀 2. 串操作指令的基本概念 3. 串操作指令的指令类型 &#xff08;1&#xff09;串传送指令 MOVS &#xff08;2&#xff09;串传送指令 CMPS &#xff08;3&#xff09;串扫描指令 SCAS &#xff08;4&#xff09;串装入指令 LODS &#xff08;5&a…

如果把网络原理倒过来看,从无到有,一切如此清晰(下)

人生若只如初见。 前言 当我在台灯下&#xff0c;听着远隔17年前五月天的歌&#xff0c;而在数日后&#xff0c;我的文字也会纵使相隔万里远的来到你的屏幕前&#xff0c;就觉得这一切妙不可言。 OSI 网络七层模型 《如果把网络原理倒过来看&#xff0c;从无到有&#xff0c…

Metabase学习教程:仪表盘-5

如何进行时间段比较 我们通过不同的方法来比较一个指标在不同日期范围内的表现。 我们将研究不同的策略来比较两个不同时期的指标&#xff0c;比如将本周与上周、去年同期与上一周进行比较。我们将使用Metabase附带的示例数据库&#xff0c;这样您就可以继续学习了。这个示例…

【Android App】Vulkan实现宇宙中旋转雷达动画效果(附源码和原始视频 超详细必看)

需要源码请点赞关注收藏后评论区留言私信~~~ 一、Vulkan简介 Vulkan是一个跨平台的图形绘制接口&#xff0c;被称为下一代OpenGL&#xff0c;因为尽管OpenGL提供了丰富的图形API&#xff0c;但他在底层实现的C代码早已封装起来&#xff0c;由于开发者修改不了底层代码&#xf…

社区系统项目复盘-5

文章目录Kafka消息队列实现系统通知功能什么是Kafka&#xff1f;Spring是怎么整合Kafka的&#xff1f;发送系统通知显示系统通知Kafka消息队列实现系统通知功能 阻塞队列 可以用阻塞队列来实现消息队列&#xff0c;阻塞队列是一个接口&#xff1a;BlockingQueue&#xff0c;可以…

易云维医院后勤综合管理平台为医院智慧后勤的建设与发展做出贡献

近年来&#xff0c;随着国家医疗卫生改革进程的不断推进&#xff0c;越来越多的医院开始关注运营成本控制问题&#xff0c;医院后勤管理服务模式的创新和优化变得越来越重要。利用医院后勤综合管理平台将医院后勤管理信息化将极大地提高医院智慧后勤建设与发展。在这种形势下&a…

Mac下安装Hadoop

1、引言 如果想在Mac下安装Hadoop而且让Hadoop能正常运行&#xff0c;那安装之前需要先安装java&#xff0c;在Mac环境下安装Hadoop。 2、配置ssh环境 在Mac下如果想使用Hadoop&#xff0c;必须要配置ssh环境&#xff0c; 如果不执行这一步&#xff0c;后面启动hadoop时会出现…

Spring MVC应该怎么学?这份教程带你快速入门,深入剖析源码!

前言: 什么是MVC&#xff1f; MVC&#xff08;Model-View-Controller&#xff09;&#xff1a;它是一种软件架构设计模式&#xff0c;分为三个部分&#xff1a; Model&#xff08;模型&#xff09;&#xff1a;业务的数据模型&#xff1b; View&#xff08;视图&#xff09;&…

xss-labs/level5

输入 <script>alert(xss)</script> 查看回显 如下所示 能够发现script被恶意替换为scr_ipt 查看源代码 第一个输出点被转义了 所以没有利用价值了 第二个输出点如同刚才所言被进行了关键字的恶意替换操作 那没办法 我们只能继续尝试一下在标签内部构造一个新…

91183-98-1,UDP-N-acetylglucosamine,5′-二磷酸尿嘧啶核苷-N-乙酰半乳糖胺二钠盐

5′-二磷酸尿嘧啶核苷-N-乙酰半乳糖胺二钠盐 英文名称&#xff1a;UDPAG&#xff1b;UDP-GlcNAc&#xff1b;UDP-N-acetylglucosamine&#xff1b;Uridine 5′-diphospho-N-acetylglucosamine sodium salt 其他名称&#xff1a;尿苷-5′-二磷酸-N-乙酰基-葡糖胺钠盐 CAS号&am…

Linux进阶-进程

目录 终端查询进程参数 进程状态 进程状态转换 子进程被Linux内核调入CPU执行的过程 子进程进入睡眠状态 子进程结束 进程控制 pid_t fork(void)&#xff1a;创建子进程 exec()函数族&#xff1a;运行一个可执行文件。 void exit(int status)&#xff1a;结束进程 w…

Illuminate/22圆桌回顾:Web3互操作性的未来现已到来

Illuminate/22 由Moonbeam主办的Illuminate/22于2022年11月10-11日成功举办。为期2天的线上会议聚集了60演讲嘉宾超过40个话题讨论。通过本次会议&#xff0c;来自行业领先的项目及负责人分享了通过互操作性和跨互连合约实现的最新进展。 本次以“Web3互操作性的未来现已到来”…

AI是如何影响全球的安防监控产业

全球AI安防市场现状 人工智能安防监控技术正在以更快的速度传播到更广泛的国家。全球176个国家中&#xff0c;至少有75个国家正在积极将AI技术用于监视目的。其中包括&#xff1a;智慧城市/安全城市平台&#xff08;56个国家&#xff09;&#xff0c;面部识别系统&#xff08;6…

【torch】如何把给定mask按比例选取再次划分mask?

背景 在以torch为基础的很多框架下有一些集成的数据集&#xff0c;数据集往往自带已经划分好的mask。但是如何能够把框架给出的mask再次划分&#xff1f;比如按比例划分出来80%的train mask。 解决 新生成一个每个元素都是0-1分布的与mask2的true位置相同的矩阵&#xff0c;…

xss-labs/level4

首先还是输入我们最熟悉的payload <script>alert(xss)</script> 查看界面回显 发现表单中的尖括号都消失了 说明后台服务器将尖括号删除了 再去查看源代码 通过源代码我们可以知道存在两个有意义的输出点 第一个输出点被转义了 没办法利用了script标签去执行js代…

亲戚小孩月薪17k,而我只有4k+,好慌......

我们总是在悲观与乐观中反复折磨自己&#xff0c;感觉自己一事无成。总是眼高手低&#xff0c;总以为大运会砸到自己&#xff0c;遇到挫折就会感到很沮丧。 大学四年没考到英语六级证书&#xff0c;小学教资考了两次。现在想要考研&#xff0c;但总是觉得来不及&#xff0c;或…

SpringBoot概念、创建和运行

文章目录什么是Spring Boot &#xff1f;为什么要学Spring Boot &#xff1f;Spring Boot 优点Spring Boot 项目创建项目目录介绍和运行约定大于配置什么是Spring Boot &#xff1f;为什么要学Spring Boot &#xff1f; Spring 的诞生是为了简化 Java 程序的开发的&#xff0c…