Files
2026-03-08 20:07:52 +08:00

591 lines
15 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="video-swiper-container">
<swiper
class="video-swiper"
:style="'width: '+ windowWidth +'px; height: '+ windowHeight +'px; background-color: #000000;'"
:vertical="true"
:current="currentIndex"
:indicator-dots="false"
@change="onSwiperChange"
>
<swiper-item v-for="(video, index) in innerVideoList" :key="video.id">
<view class="video-item">
<video
:id="`video-${index}`"
:src="video.video"
:autoplay="index === currentIndex"
:muted="false"
:controls="false"
objectFit="contain"
:loop="true"
:style="{ width: windowWidth + 'px', height: windowHeight + 'px' }"
@play="handleVideoPlay(index)"
@pause="handleVideoPause(index)"
@loadeddata="handleVideoLoaded(index)"
></video>
<!-- 点击暂停/播放 -->
<view class="video-cover" @click="togglePlay(index)" v-if=" !playingMap[index]">
<image class="play-icon" src="../static/images/play.png" mode="aspectFit"></image>
</view>
<view class="loading-indicator" v-if="isLoading">
<text class="loading-text">加载中...</text>
</view>
<view class="video-controls" @click="togglePlay(index)"></view>
<!-- 视频上展示内容-->
<video-content :list="video"></video-content>
<!-- 审核信息-->
<view v-if="video.auditStatus !== 1">
<review-status :auditStatus="video.auditStatus" :refusal="video.refusal"></review-status>
</view>
<!-- 右侧操作按钮-->
<operation-btn :list="video" :noteId="noteId"></operation-btn>
</view>
</swiper-item>
</swiper>
</view>
</template>
<script>
import {noteDetailApi, noteRecommendApi} from "../../../api/discover";
import reviewStatus from "./reviewStatus";
import operationBtn from "./operationBtn";
import videoContent from "./videoContent";
export default {
name: 'swiperVideo',
props: {
noteId: {
type: [String, Number],
default: ''
},
fromTo: {
type: String,
default: ''
}
},
components: {
reviewStatus,
operationBtn,
videoContent
},
computed: {
windowHeight() {
return this.getHeight.windowHeight - this.getHeight.barTop
},
},
data() {
return {
getHeight: this.$util.getWXStatusHeight(),
innerVideoList: [],
currentIndex: 0,
windowWidth: 0,
isLoading: false,
videoContexts: {},
touchStartTime: 0,
touchEndTime: 0,
playingMap: {},
// 分页相关
where: {
page: 1,
limit: 5,
noteId: ''
},
loadend: false,
loading: false,
// 播放控制相关
isPlaying: false,
playTimer: null,
pauseTimer: null,
// 用户手动暂停状态记录
userPausedMap: {},
// 手机浏览器自动播放相关
hasUserInteracted: false,
canAutoplay: false,
autoplayAttempted: false
}
},
watch: {
noteId: {
immediate: true,
handler(newNoteId) {
if (newNoteId) {
this.windowWidth = uni.getSystemInfoSync().windowWidth
this.where.noteId = newNoteId;
this.loadVideoData();
}
}
}
},
onLoad() {
// 初始化窗口尺寸
this.windowWidth = uni.getSystemInfoSync().windowWidth;
// 初始化时设置canAutoplay为true允许默认播放
this.canAutoplay = true;
// 如果有noteId加载视频数据
if (this.noteId) {
this.where.noteId = this.noteId;
this.loadVideoData();
}
},
// 组件挂载后尝试自动播放第一个视频
mounted() {
this.$nextTick(() => {
if (this.innerVideoList.length > 0 && this.currentIndex === 0) {
this.playVideo(0);
}
});
},
onShow() {
},
methods: {
// 加载视频数据
async loadVideoData() {
try {
// this.isLoading = true;
// 先获取单个视频详情
await this.getNoteDetail();
// 然后获取推荐视频列表
await this.getRecommendVideos();
} catch (error) {
this.isLoading = false;
} finally {
this.isLoading = false;
}
},
// 获取单个视频详情
async getNoteDetail() {
if (!this.noteId) return;
try {
this.isLoading = true;
const res = await noteDetailApi(this.noteId);
const videoData = res.data;
// 处理视频数据格式
const processedVideo = this.processVideoData(videoData);
// 如果是第一个视频,直接添加到列表开头
if (this.innerVideoList.length === 0) {
this.innerVideoList.push(processedVideo);
this.$nextTick(() => {
this.initVideoContexts();
this.playVideo(0);
});
} else {
// 检查是否已存在,避免重复
const exists = this.innerVideoList.some(video => video.id === processedVideo.id);
if (!exists) {
this.innerVideoList.unshift(processedVideo);
this.$nextTick(() => {
this.initVideoContexts();
});
}
}
this.isLoading = false;
} catch (error) {
this.isLoading = false;
console.error('获取视频详情失败:', error);
}
},
// 获取推荐视频列表
async getRecommendVideos() {
if (this.loadend) return;
try {
this.loading = true;
const res = await noteRecommendApi(this.where);
const list = res.data.list || [];
// 避免重复添加相同的视频数据
const filteredList = list.filter(video =>
!this.innerVideoList.some(existingVideo => existingVideo.id === video.id)
);
if (filteredList.length > 0) {
// 处理视频数据格式
const processedList = filteredList.map(video => this.processVideoData(video));
// 添加到视频列表
this.innerVideoList.push(...processedList);
// 更新分页信息
this.where.page = this.where.page + 1;
this.loadend = this.where.page > res.data.totalPage;
this.$nextTick(() => {
this.initVideoContexts();
});
}
this.isLoading = false;
} catch (error) {
console.error('获取推荐视频失败:', error);
} finally {
this.loading = false;
}
},
// 清理视频URL,针对小程序端报错信息
cleanVideoUrl(url) {
if (!url) return '';
// 移除开发工具添加的参数
if (url.includes('#devtools_no_referrer')) {
url = url.split('#devtools_no_referrer')[0];
}
// 移除其他可能的开发工具参数
if (url.includes('#devtools')) {
url = url.split('#devtools')[0];
}
// 确保URL格式正确
if (url.startsWith('//')) {
url = 'https:' + url;
}
return url;
},
// 处理视频数据格式
processVideoData(video) {
// 清理视频URL
const videoUrl = this.cleanVideoUrl(video.video);
return {
...video,
video: videoUrl,
platReplySwitch: video.platReplySwitch !== false,
// 播放状态
playIng: false,
state: 'pause',
isplay: false,
loading: false
};
},
initVideoContexts() {
// 确保 videoContexts 和 playingMap 被正确初始化
if (!this.videoContexts) {
this.videoContexts = {};
}
if (!this.playingMap) {
this.playingMap = {};
}
if (!this.userPausedMap) {
this.userPausedMap = {};
}
this.innerVideoList.forEach((_, index) => {
try {
this.videoContexts[index] = uni.createVideoContext(`video-${index}`, this);
} catch (error) {
}
});
},
// 处理视频播放事件
handleVideoPlay(index) {
if (this.playingMap && typeof this.playingMap === 'object') {
this.$set(this.playingMap, index, true);
this.videoPlaying(index);
}
},
// 处理视频暂停事件
handleVideoPause(index) {
if (this.playingMap && typeof this.playingMap === 'object') {
this.$set(this.playingMap, index, false);
this.videoPaused(index);
}
},
// 处理视频加载完成事件
handleVideoLoaded(index) {
if (index === this.currentIndex) {
this.playVideo(index);
}
},
// 轮播切换时的处理
onSwiperChange(e) {
const newIndex = e.detail.current;
// 确保 playingMap 和 userPausedMap 已初始化
if (!this.playingMap) {
this.playingMap = {};
}
if (!this.userPausedMap) {
this.userPausedMap = {};
}
// 暂停之前的视频
if (this.currentIndex !== newIndex) {
// 暂停旧视频
if (this.videoContexts && this.videoContexts[this.currentIndex]) {
try {
this.videoContexts[this.currentIndex].pause();
if (this.playingMap) {
this.playingMap[this.currentIndex] = false;
}
this.videoPaused(this.currentIndex);
} catch (error) {
console.log('暂停旧视频失败:', error);
}
}
// 更新当前索引
this.currentIndex = newIndex;
// 重置用户暂停状态,确保新视频能自动播放
if (this.userPausedMap) {
this.userPausedMap[newIndex] = false;
}
// 标记用户已交互,提高自动播放成功率
this.hasUserInteracted = true;
// 播放新视频
this.$nextTick(() => {
setTimeout(() => {
this.playVideo(newIndex);
}, 100); // 小延迟确保DOM已更新
});
}
},
// 播放视频
playVideo(index) {
// 确保 videoContexts 和 playingMap 已初始化
if (!this.videoContexts || !this.videoContexts[index] || !this.playingMap) {
console.log('播放视频失败:视频上下文或播放状态映射未初始化');
return;
}
try {
this.videoContexts[index].play();
this.$set(this.playingMap, index, true);
this.canAutoplay = true;
} catch (error) {
// 标记需要用户交互
this.hasUserInteracted = false;
}
},
// 暂停视频
pauseVideo(index) {
// 确保 videoContexts 和 playingMap 已初始化
if (!this.videoContexts || !this.videoContexts[index] || !this.playingMap) {
console.log('暂停视频失败:视频上下文或播放状态映射未初始化');
return;
}
try {
this.videoContexts[index].pause();
this.$set(this.playingMap, index, false);
} catch (error) {
}
},
// 视频开始播放时更新状态
videoPlaying(index) {
// 确保 playingMap 已初始化
if (!this.playingMap || typeof this.playingMap !== 'object') {
this.playingMap = {};
}
this.$set(this.playingMap, index, true);
if (this.innerVideoList && this.innerVideoList[index]) {
this.innerVideoList[index].playIng = true;
this.innerVideoList[index].state = 'playing';
this.innerVideoList[index].isplay = true;
}
},
// 视频暂停时更新状态
videoPaused(index) {
// 确保 playingMap 已初始化
if (!this.playingMap || typeof this.playingMap !== 'object') {
this.playingMap = {};
}
this.$set(this.playingMap, index, false);
if (this.innerVideoList && this.innerVideoList[index]) {
this.innerVideoList[index].playIng = false;
this.innerVideoList[index].state = 'pause';
this.innerVideoList[index].isplay = false;
}
},
// 切换播放状态
togglePlay(index) {
// 确保 playingMap 和 userPausedMap 已初始化
if (!this.playingMap || typeof this.playingMap !== 'object') {
this.playingMap = {};
}
if (!this.userPausedMap || typeof this.userPausedMap !== 'object') {
this.userPausedMap = {};
}
// 标记用户已交互
this.hasUserInteracted = true;
if (this.playingMap[index]) {
// 暂停视频
this.pauseVideo(index);
this.userPausedMap[index] = true;
} else {
// 播放视频
this.playVideo(index);
this.userPausedMap[index] = false;
// 确保视频元素已准备好
setTimeout(() => {
if (this.playingMap && this.videoContexts && !this.playingMap[index] && this.videoContexts[index]) {
try {
this.videoContexts[index].play();
} catch (error) {
console.log('二次尝试播放失败:', error);
}
}
}, 100);
}
}
}
}
</script>
<style scoped>
.video-swiper-container {
width: 100%;
height: 100vh;
background-color: #000;
}
.video-swiper {
/*width: 100%;*/
/*height: 100%;*/
}
.video-item {
position: relative;
width: 100%;
height: 100%;
}
.video-cover {
position: absolute;
top: 50%;
left: 50%;
display: flex;
justify-content: center;
align-items: center;
z-index: 10;
animation: pulse 2s infinite;
}
.pause-cover {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 120rpx;
height: 120rpx;
background-color: rgba(0, 0, 0, 0.3);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
z-index: 10;
}
.pause-icon {
width: 60rpx;
height: 60rpx;
opacity: 0.8;
}
@keyframes pulse {
0% {
opacity: 0.8;
transform: translate(-50%, -50%) scale(1);
}
50% {
opacity: 0.5;
transform: translate(-50%, -50%) scale(1.1);
}
100% {
opacity: 0.8;
transform: translate(-50%, -50%) scale(1);
}
}
.play-icon {
width: 120rpx;
height: 120rpx;
opacity: 0.8;
}
.play-hint {
position: absolute;
left: 50%;
transform: translateX(-50%);
background-color: rgba(0, 0, 0, 0.7);
padding: 20rpx 40rpx;
border-radius: 40rpx;
}
.hint-text {
color: #fff;
font-size: 28rpx;
}
.loading-indicator {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.5);
padding: 20rpx 40rpx;
border-radius: 40rpx;
z-index: 20;
}
.loading-text {
color: #fff;
font-size: 28rpx;
}
.video-controls {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
/*z-index: 20;*/
}
.video-info {
position: absolute;
bottom: 180rpx;
left: 40rpx;
right: 140rpx;
color: #fff;
z-index: 30;
}
.video-title {
font-size: 36rpx;
font-weight: bold;
display: block;
margin-bottom: 20rpx;
}
.video-description {
font-size: 28rpx;
line-height: 40rpx;
opacity: 0.9;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
</style>