赞
踩
抖音效果图
本内容主要实现了滑动视频组件、首个视频自动播放、预加载、实现加载更多,超高性能,
前言:最近在做短剧,于是就在网上找了很多不错的例子,但是不是很完美,基本上都比较卡顿,我也是在站在巨人的肩膀上优化了一下。本片主要基于vue3、setup和ts开发的。
相关参考:
video | uni-app官网 (dcloud.net.cn)
uni.createVideoContext(videoId, this) | uni-app官网 (dcloud.net.cn)
项目结构:
主要组件:代码里逻辑很清晰,就不再赘述了。video-play.vue
- <template>
- <swiper class="video-swiper" circular @change="swiperChange" :current="state.current" :vertical="true"
- duration="800">
- <swiper-item v-for="(item, index) in state.displaySwiperList" :key="index">
- <view class="swiper-item" @click="handleClick">
- <video :id="`video${index}`" :controls="controls" :show-fullscreen-btn="false" :autoplay="false"
- :loop="loop" @ended="ended" @controlstoggle="controlstoggle" @play="onPlay" @error="emits('error')"
- class="video-player" :src="item.src" v-if="index === 0 || !state.isFirstLoad"></video>
-
- <slot :item="item"></slot>
- </view>
- </swiper-item>
- </swiper>
- </template>
-
- <script lang="ts" setup>
- import { getCurrentInstance, watch, onMounted, onUnmounted } from "vue";
- import { useState } from './moudle'
-
- interface IvideoItem {
- src : string;//视频链接
- title : string;
- id : string;
- }
- interface Iprops {
- videoList : Array<IvideoItem>
- loop ?: boolean //是否循环播放一个视频
- controls ?: boolean
- autoplay ?: boolean
- autoChange ?: boolean //是否自动滚动播放
- loadMoreOffsetCount ?: number //滚动加载阈值(即播放到剩余多少个之后触发加载更多
-
-
- }
-
- const emits = defineEmits<{
- (e : 'play',value : Event) : void
- (e : 'error') : void
- (e : 'loadMore') : void
- (e : 'change',{
- index: number,
- detail: any,
- }) : void
- (e : 'controlstoggle',value : any) : void
- (e : 'click',value : Event) : void
- (e : 'ended') : void
-
-
- }>();
- const props = withDefaults(defineProps<Iprops>(), {
- videoList: () => [],
- loop: true,
- controls: true,
- autoplay: true,
- autoChange: true,
- loadMoreOffsetCount: 2
- })
-
-
-
- const state = useState()
-
- const initVideoContexts = () => {
- state.videoContexts = [
- uni.createVideoContext("video0", getCurrentInstance()),
- uni.createVideoContext("video1", getCurrentInstance()),
- uni.createVideoContext("video2", getCurrentInstance()),
- ];
- };
-
- const onPlay = (e : Event) => {
- emits("play", e);
- };
-
- function handleClick(e : Event) {
- state.toggleShow = !state.toggleShow;
- emits("click", e);
- }
- function ended() {
- // 自动切换下一个视频
- if (props.autoChange) {
- if (state.displayIndex < 2) {
- state.current = state.displayIndex + 1;
- } else {
- state.current = 0;
- }
- }
- emits("ended");
- }
- /**
- * 初始一个显示的swiper数据
- * @originIndex 从源数据的哪个开始显示默认0,如从其他页面跳转进来,要显示第n个,这个参数就是他的下标
- */
- function initSwiperData(originIndex = state.originIndex) {
- const originListLength = state.originList.length; // 源数据长度
- const displayList = [];
- displayList[state.displayIndex] = state.originList[originIndex];
- displayList[state.displayIndex - 1 == -1 ? 2 : state.displayIndex - 1] =
- state.originList[
- originIndex - 1 == -1 ? originListLength - 1 : originIndex - 1
- ];
- displayList[state.displayIndex + 1 == 3 ? 0 : state.displayIndex + 1] =
- state.originList[originIndex + 1 == originListLength ? 0 : originIndex + 1];
- state.displaySwiperList = displayList;
-
- if (state.oid >= state.originList.length) {
- state.oid = 0;
- }
- if (state.oid < 0) {
- state.oid = state.originList.length - 1;
- }
- // 暂停所有视频
- state.videoContexts.map((item : any) => item?.stop());
- setTimeout(() => {
- // 当前视频
- if (props.autoplay) {
- state.videoContexts[state.displayIndex].play()
-
- }
- }, 600);
- // 数据改变
- emits("change", {
- index: originIndex,
- detail: state.originList[originIndex],
- });
- // 加载更多
- var pCount = state.originList.length - props.loadMoreOffsetCount;
- if (originIndex == pCount) {
- emits("loadMore");
- }
- }
- /**
- * swiper滑动时候
- */
- function swiperChange(event : any) {
- const { current } = event.detail;
- state.isFirstLoad = false;
- const originListLength = state.originList.length; // 源数据长度
- // 向后滚动
- if (state.displayIndex - current == 2 || state.displayIndex - current == -1) {
- state.originIndex =
- state.originIndex + 1 == originListLength ? 0 : state.originIndex + 1;
- state.displayIndex =
- state.displayIndex + 1 == 3 ? 0 : state.displayIndex + 1;
- state.oid = state.originIndex - 1;
- initSwiperData(state.originIndex);
- }
- // 如果两者的差为-2或者1则是向前滑动
- else if (
- state.displayIndex - current == -2 ||
- state.displayIndex - current == 1
- ) {
- state.originIndex =
- state.originIndex - 1 == -1
- ? originListLength - 1
- : state.originIndex - 1;
- state.displayIndex =
- state.displayIndex - 1 == -1 ? 2 : state.displayIndex - 1;
- state.oid = state.originIndex + 1;
- initSwiperData(state.originIndex);
- }
- state.toggleShow = true;
- }
-
- function controlstoggle(e : any) {
- state.showControls = e.detail.show;
- emits("controlstoggle", e);
- }
-
- watch(
- () => props.videoList,
- () => {
- if (props.videoList?.length) {
- state.originList = props.videoList;
- if (state.isFirstLoad || !state.videoContexts?.length) {
- initSwiperData();
- initVideoContexts();
- }
- }
- },
- {
- immediate: true,
- }
- );
-
- let loadTimer : any = null;
- onMounted(() => {
- // 为了首次只加载一条视频(提高首次加载性能),延迟加载后续视频
- loadTimer = setTimeout(() => {
- state.isFirstLoad = false;
- clearTimeout(loadTimer);
- }, 3000);
- })
- onUnmounted(() => {
- clearTimeout(loadTimer);
- })
- </script>
-
- <style lang="scss" scoped>
- .video-swiper {
- width: 100%;
- height: 100vh;
- background-color: #000;
-
- swiper-item {
-
- .video-player {
- width: 100%;
- height: 100vh;
- }
- }
- }
- </style>
状态管理:
moudle.ts
- import { reactive } from "vue"
-
- const useState=()=>{
- return reactive({
- originList: [] as any, // 源数据
- displaySwiperList: [] as any, // swiper需要的数据
- displayIndex: 0, // 用于显示swiper的真正的下标数值只有:0,1,2。
- originIndex: 0, // 记录源数据的下标
- current: 0,
- oid: 0,
- showControls: "",
- toggleShow: true, // 显示面板
- videoContexts: [] as any,
- isFirstLoad: true,
- })
- }
- export {useState}
引用逻辑:
- <template>
- <div class="video-container">
- <video-play :video-list="state.videoList" @loadMore="loadMore" >
- <!-- 此处data是从子组件获取的数据 不明白参考https://cn.vuejs.org/guide/components/slots.html#dynamic-slot-names-->
- <template v-slot="data">
- <view class="video-title"> {{ data.item.title }} </view>
- </template>
- </video-play>
- </div>
- </template>
- <script lang="ts" setup>
- import { reactive } from "vue";
- // 导入组件
- const state = reactive({
- videoList: [
- {
- src: "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4",
- id: "1",
- title: "亲近大自然"
- },
- {
- src: "https://img.chenggua.com/mnzcdcjgs.mp4",
- id: "2",
- title: "亲近大自然"
- },
- {
- src: "https://img.chenggua.com/mnzcdcjgs.mp4",
- id: "3",
- title: "亲近大自然"
- },
- {
- src: "http://vjs.zencdn.net/v/oceans.mp4",
- id: "4",
- title: "亲近大自然"
- },
- {
- src: "https://xjc.demo.hongcd.com/uploads/20210128/0c64cbeea28b10c06eee8728c762449e.mp4",
- id: "5",
- title: "亲近大自然"
- },
- {
- src: "https://xjc.demo.hongcd.com/uploads/20210327/1b72e1b6153cd29df07f5449991e8083.mp4",
- id: "6",
- title: "亲近大自然"
- },
- {
- src: "https://xjc.demo.hongcd.com/uploads/20230214/7e1a0baaebc4e656bbbfbc44d7a55a60.mp4",
- id: "7",
- title: "亲近大自然"
- },
- ],
- });
-
- const loadMore = () => {
- state.videoList.push({
- src: "https://img.chenggua.com/mnzcdcjgs.mp4",
- id: state.videoList.length+"",
- title: '我是加载更多加载更多'+state.videoList.length
- })
- };
-
- </script>
- <style lang="scss">
- .video-title {
- position: absolute;
- left: 30rpx;
- top: 50rpx;
- color: #fff;
- }
- </style>
以上测试逻辑都是基于小程序测试的,希望对于您有所帮助。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。