从零用java实现 小红书 springboot vue uniapp(13)实战:用Swiper+Video打造抖音式丝滑视频流
移动端演示 http://8.146.211.120:8081/#/
管理端演示 http://8.146.211.120:8088/#/
项目整体介绍及演示
前言
在上一篇文章中我们实现了视频笔记的发布功能,现在,我们将攻克一个更核心的体验功能:创建一个像抖音、快手那样的全屏、可上下滑动切换的视频信息流。这不仅仅是UI的堆砌,背后涉及到 swiper
和 video
组件的深度联动、视频生命周期的精细化管理、以及数据预加载等关键技术。本文将详细拆解其实现过程。
核心技术实现
我们的目标是当用户向上或向下滑动时,上一个视频能自动暂停,下一个视频能自动播放,并且列表能无限滚动加载。我们将围绕 videoDetail.vue
文件来展开。
1. 基础布局:Swiper 与 Video 的结合
首先,我们搭建页面的骨架。外层使用 swiper
组件作为滑动容器,并设置其 vertical="true"
来实现垂直滚动。内部通过 v-for
循环渲染 swiper-item
,每个 swiper-item
中放置一个全屏的 video
组件。
<template>
<swiper class="swiper-container" :vertical="true" @change="handleSwiperChange" :current="currentIndex">
<swiper-item v-for="(video, index) in videoData" :key="video.noteId">
<view class="video-container">
<video
class="video"
:src="video.videoUrl"
:id="`video_${index}`"
:loop="true"
:controls="false"
@timeupdate="handleTimeUpdate">
</video>
<!-- 其他UI元素,如点赞、评论按钮等 -->
</view>
</swiper-item>
</swiper>
</template>
@change="handleSwiperChange"
:这是实现功能的核心,每当滑动切换视频时,该事件会被触发。:id="'video_' + index"
:为每个video组件设置一个唯一的ID,这是后续通过代码精确控制视频播放/暂停的关键。
2. 视频播放控制:uni.createVideoContext
要用代码控制视频,我们必须先获取到每个视频的实例,即 VideoContext
。
我们在 data
中创建一个数组 videoContexts: []
用于存储这些实例。然后,在页面数据加载并渲染完成后,初始化它们。
// script
export default {
data() {
return {
videoData: [],
currentIndex: 0,
videoContexts: []
};
},
onReady() {
// onReady生命周期确保了组件已渲染
this.initVideoContexts();
},
methods: {
initVideoContexts() {
this.videoContexts = []; // 清空旧实例
this.videoData.forEach((item, index) => {
// 通过 video 的 id 创建并存储 context
this.videoContexts[index] = uni.createVideoContext(`video_${index}`, this);
});
// 自动播放第一个视频
if (this.videoContexts[this.currentIndex]) {
this.videoContexts[this.currentIndex].play();
}
}
}
}
3. 核心交互:滑动切换与自动播放
所有的关键都发生在 handleSwiperChange
方法中。当用户滑动 swiper
时,我们需要:
- 暂停上一个正在播放的视频。
- 播放当前显示的新视频。
methods: {
handleSwiperChange(event) {
const { current } = event.detail;
// 记录上一个视频的索引
const previousIndex = this.currentIndex;
// 暂停上一个视频
if (this.videoContexts[previousIndex]) {
this.videoContexts[previousIndex].pause();
}
// 更新当前视频的索引
this.currentIndex = current;
this.paused = false; // 重置手动暂停状态
// 延时播放当前视频,确保滑动手势完成
setTimeout(() => {
if (this.videoContexts[this.currentIndex]) {
this.videoContexts[this.currentIndex].play();
}
}, 250);
},
}
4. 数据流:无限滚动与预加载
为了实现“刷不完”的效果,我们需要在用户快要滑到底部时,提前加载下一页的数据。
数据加载:
我们封装一个 loadVideos
方法,通过分页参数(page
, pageSize
)从后端获取视频列表,并追加到 videoData
数组中。
触发加载:
我们在 swiper
组件上监听 @scrolltolower
事件(在uniapp中,需要自己根据 currentIndex
模拟)。当用户滑动到倒数N个视频时(例如 preloadThreshold = 2
),就调用 loadVideos
。
// 在 handleSwiperChange 方法的最后调用
handleSwiperChange(event) {
// ...上面的播放/暂停逻辑...
// 检查是否需要加载更多视频
this.checkAndLoadMore();
},
methods: {
checkAndLoadMore() {
// 当滑动到倒数第 preloadThreshold 个视频时,加载更多
const isNearEnd = this.currentIndex >= this.videoData.length - this.preloadThreshold;
if (isNearEnd && this.hasMore && !this.loading) {
this.loadVideos(); // 该方法内部会请求API并追加数据
}
},
loadVideos() {
if(this.loading || !this.hasMore) return;
this.loading = true;
uni.app.get('/auth/getVideoNotes', { page: this.page, limit: this.pageSize }, '', (res => {
// ...处理返回的数据...
const newVideos = res.data.records || [];
this.videoData = [...this.videoData, ...newVideos];
this.hasMore = newVideos.length >= this.pageSize;
this.page++;
this.loading = false;
// !! 关键:数据更新后,需要重新初始化新增的 video context
this.$nextTick(() => {
this.initVideoContexts();
});
}));
}
}
注意: 每次加载新数据后,videoData
数组都变了,因此需要重新调用 initVideoContexts
来为新加入的视频创建 VideoContext
实例。
通过以上四步,我们就完整地实现了一个功能强大且体验流畅的抖音式视频流。