Use uniapp to imitate Douyin, slide video components, automatically play the first video, preload, and achieve more loading with ultra-high performance

Douyin renderings

This content mainly implements sliding video components, automatic playback of the first video, preloading, and the realization of loading more, ultra-high performance,

Preface: I have been working on a short play recently, so I found a lot of good examples on the Internet, but they are not perfect and basically laggy. I also optimized them by standing on the shoulders of giants. This film is mainly developed based on vue3, setup and ts.

Related references:

video | uni-app official website (dcloud.net.cn)

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

Project structure:

Main components: The logic in the code is very clear, so I won’t go into details. 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>

Status management:

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}

Quote logic:

<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>

The above test logic is based on small program testing. I hope it will be helpful to you.

Guess you like

Origin blog.csdn.net/u012941592/article/details/133106744