当前位置:   article > 正文

利用uniapp中模仿抖音、滑动视频组件、首个视频自动播放、预加载、实现加载更多,超高性能_uniapp video预加载

uniapp video预加载

抖音效果图

本内容主要实现了滑动视频组件、首个视频自动播放、预加载、实现加载更多,超高性能,

前言:最近在做短剧,于是就在网上找了很多不错的例子,但是不是很完美,基本上都比较卡顿,我也是在站在巨人的肩膀上优化了一下。本片主要基于vue3、setup和ts开发的。

相关参考:

video | uni-app官网 (dcloud.net.cn)

uni.createVideoContext(videoId, this) | uni-app官网 (dcloud.net.cn)

项目结构:

主要组件:代码里逻辑很清晰,就不再赘述了。video-play.vue

  1. <template>
  2. <swiper class="video-swiper" circular @change="swiperChange" :current="state.current" :vertical="true"
  3. duration="800">
  4. <swiper-item v-for="(item, index) in state.displaySwiperList" :key="index">
  5. <view class="swiper-item" @click="handleClick">
  6. <video :id="`video${index}`" :controls="controls" :show-fullscreen-btn="false" :autoplay="false"
  7. :loop="loop" @ended="ended" @controlstoggle="controlstoggle" @play="onPlay" @error="emits('error')"
  8. class="video-player" :src="item.src" v-if="index === 0 || !state.isFirstLoad"></video>
  9. <slot :item="item"></slot>
  10. </view>
  11. </swiper-item>
  12. </swiper>
  13. </template>
  14. <script lang="ts" setup>
  15. import { getCurrentInstance, watch, onMounted, onUnmounted } from "vue";
  16. import { useState } from './moudle'
  17. interface IvideoItem {
  18. src : string;//视频链接
  19. title : string;
  20. id : string;
  21. }
  22. interface Iprops {
  23. videoList : Array<IvideoItem>
  24. loop ?: boolean //是否循环播放一个视频
  25. controls ?: boolean
  26. autoplay ?: boolean
  27. autoChange ?: boolean //是否自动滚动播放
  28. loadMoreOffsetCount ?: number //滚动加载阈值(即播放到剩余多少个之后触发加载更多
  29. }
  30. const emits = defineEmits<{
  31. (e : 'play',value : Event) : void
  32. (e : 'error') : void
  33. (e : 'loadMore') : void
  34. (e : 'change',{
  35. index: number,
  36. detail: any,
  37. }) : void
  38. (e : 'controlstoggle',value : any) : void
  39. (e : 'click',value : Event) : void
  40. (e : 'ended') : void
  41. }>();
  42. const props = withDefaults(defineProps<Iprops>(), {
  43. videoList: () => [],
  44. loop: true,
  45. controls: true,
  46. autoplay: true,
  47. autoChange: true,
  48. loadMoreOffsetCount: 2
  49. })
  50. const state = useState()
  51. const initVideoContexts = () => {
  52. state.videoContexts = [
  53. uni.createVideoContext("video0", getCurrentInstance()),
  54. uni.createVideoContext("video1", getCurrentInstance()),
  55. uni.createVideoContext("video2", getCurrentInstance()),
  56. ];
  57. };
  58. const onPlay = (e : Event) => {
  59. emits("play", e);
  60. };
  61. function handleClick(e : Event) {
  62. state.toggleShow = !state.toggleShow;
  63. emits("click", e);
  64. }
  65. function ended() {
  66. // 自动切换下一个视频
  67. if (props.autoChange) {
  68. if (state.displayIndex < 2) {
  69. state.current = state.displayIndex + 1;
  70. } else {
  71. state.current = 0;
  72. }
  73. }
  74. emits("ended");
  75. }
  76. /**
  77. * 初始一个显示的swiper数据
  78. * @originIndex 从源数据的哪个开始显示默认0,如从其他页面跳转进来,要显示第n个,这个参数就是他的下标
  79. */
  80. function initSwiperData(originIndex = state.originIndex) {
  81. const originListLength = state.originList.length; // 源数据长度
  82. const displayList = [];
  83. displayList[state.displayIndex] = state.originList[originIndex];
  84. displayList[state.displayIndex - 1 == -1 ? 2 : state.displayIndex - 1] =
  85. state.originList[
  86. originIndex - 1 == -1 ? originListLength - 1 : originIndex - 1
  87. ];
  88. displayList[state.displayIndex + 1 == 3 ? 0 : state.displayIndex + 1] =
  89. state.originList[originIndex + 1 == originListLength ? 0 : originIndex + 1];
  90. state.displaySwiperList = displayList;
  91. if (state.oid >= state.originList.length) {
  92. state.oid = 0;
  93. }
  94. if (state.oid < 0) {
  95. state.oid = state.originList.length - 1;
  96. }
  97. // 暂停所有视频
  98. state.videoContexts.map((item : any) => item?.stop());
  99. setTimeout(() => {
  100. // 当前视频
  101. if (props.autoplay) {
  102. state.videoContexts[state.displayIndex].play()
  103. }
  104. }, 600);
  105. // 数据改变
  106. emits("change", {
  107. index: originIndex,
  108. detail: state.originList[originIndex],
  109. });
  110. // 加载更多
  111. var pCount = state.originList.length - props.loadMoreOffsetCount;
  112. if (originIndex == pCount) {
  113. emits("loadMore");
  114. }
  115. }
  116. /**
  117. * swiper滑动时候
  118. */
  119. function swiperChange(event : any) {
  120. const { current } = event.detail;
  121. state.isFirstLoad = false;
  122. const originListLength = state.originList.length; // 源数据长度
  123. // 向后滚动
  124. if (state.displayIndex - current == 2 || state.displayIndex - current == -1) {
  125. state.originIndex =
  126. state.originIndex + 1 == originListLength ? 0 : state.originIndex + 1;
  127. state.displayIndex =
  128. state.displayIndex + 1 == 3 ? 0 : state.displayIndex + 1;
  129. state.oid = state.originIndex - 1;
  130. initSwiperData(state.originIndex);
  131. }
  132. // 如果两者的差为-2或者1则是向前滑动
  133. else if (
  134. state.displayIndex - current == -2 ||
  135. state.displayIndex - current == 1
  136. ) {
  137. state.originIndex =
  138. state.originIndex - 1 == -1
  139. ? originListLength - 1
  140. : state.originIndex - 1;
  141. state.displayIndex =
  142. state.displayIndex - 1 == -1 ? 2 : state.displayIndex - 1;
  143. state.oid = state.originIndex + 1;
  144. initSwiperData(state.originIndex);
  145. }
  146. state.toggleShow = true;
  147. }
  148. function controlstoggle(e : any) {
  149. state.showControls = e.detail.show;
  150. emits("controlstoggle", e);
  151. }
  152. watch(
  153. () => props.videoList,
  154. () => {
  155. if (props.videoList?.length) {
  156. state.originList = props.videoList;
  157. if (state.isFirstLoad || !state.videoContexts?.length) {
  158. initSwiperData();
  159. initVideoContexts();
  160. }
  161. }
  162. },
  163. {
  164. immediate: true,
  165. }
  166. );
  167. let loadTimer : any = null;
  168. onMounted(() => {
  169. // 为了首次只加载一条视频(提高首次加载性能),延迟加载后续视频
  170. loadTimer = setTimeout(() => {
  171. state.isFirstLoad = false;
  172. clearTimeout(loadTimer);
  173. }, 3000);
  174. })
  175. onUnmounted(() => {
  176. clearTimeout(loadTimer);
  177. })
  178. </script>
  179. <style lang="scss" scoped>
  180. .video-swiper {
  181. width: 100%;
  182. height: 100vh;
  183. background-color: #000;
  184. swiper-item {
  185. .video-player {
  186. width: 100%;
  187. height: 100vh;
  188. }
  189. }
  190. }
  191. </style>

状态管理:

moudle.ts

  1. import { reactive } from "vue"
  2. const useState=()=>{
  3. return reactive({
  4. originList: [] as any, // 源数据
  5. displaySwiperList: [] as any, // swiper需要的数据
  6. displayIndex: 0, // 用于显示swiper的真正的下标数值只有:0,1,2。
  7. originIndex: 0, // 记录源数据的下标
  8. current: 0,
  9. oid: 0,
  10. showControls: "",
  11. toggleShow: true, // 显示面板
  12. videoContexts: [] as any,
  13. isFirstLoad: true,
  14. })
  15. }
  16. export {useState}

引用逻辑:

  1. <template>
  2. <div class="video-container">
  3. <video-play :video-list="state.videoList" @loadMore="loadMore" >
  4. <!-- 此处data是从子组件获取的数据 不明白参考https://cn.vuejs.org/guide/components/slots.html#dynamic-slot-names-->
  5. <template v-slot="data">
  6. <view class="video-title"> {{ data.item.title }} </view>
  7. </template>
  8. </video-play>
  9. </div>
  10. </template>
  11. <script lang="ts" setup>
  12. import { reactive } from "vue";
  13. // 导入组件
  14. const state = reactive({
  15. videoList: [
  16. {
  17. src: "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4",
  18. id: "1",
  19. title: "亲近大自然"
  20. },
  21. {
  22. src: "https://img.chenggua.com/mnzcdcjgs.mp4",
  23. id: "2",
  24. title: "亲近大自然"
  25. },
  26. {
  27. src: "https://img.chenggua.com/mnzcdcjgs.mp4",
  28. id: "3",
  29. title: "亲近大自然"
  30. },
  31. {
  32. src: "http://vjs.zencdn.net/v/oceans.mp4",
  33. id: "4",
  34. title: "亲近大自然"
  35. },
  36. {
  37. src: "https://xjc.demo.hongcd.com/uploads/20210128/0c64cbeea28b10c06eee8728c762449e.mp4",
  38. id: "5",
  39. title: "亲近大自然"
  40. },
  41. {
  42. src: "https://xjc.demo.hongcd.com/uploads/20210327/1b72e1b6153cd29df07f5449991e8083.mp4",
  43. id: "6",
  44. title: "亲近大自然"
  45. },
  46. {
  47. src: "https://xjc.demo.hongcd.com/uploads/20230214/7e1a0baaebc4e656bbbfbc44d7a55a60.mp4",
  48. id: "7",
  49. title: "亲近大自然"
  50. },
  51. ],
  52. });
  53. const loadMore = () => {
  54. state.videoList.push({
  55. src: "https://img.chenggua.com/mnzcdcjgs.mp4",
  56. id: state.videoList.length+"",
  57. title: '我是加载更多加载更多'+state.videoList.length
  58. })
  59. };
  60. </script>
  61. <style lang="scss">
  62. .video-title {
  63. position: absolute;
  64. left: 30rpx;
  65. top: 50rpx;
  66. color: #fff;
  67. }
  68. </style>

以上测试逻辑都是基于小程序测试的,希望对于您有所帮助。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/397076
推荐阅读
相关标签
  

闽ICP备14008679号