liujiaxin 2 minggu lalu
induk
melakukan
a5cf32e350

+ 175 - 29
pages/home/living.vue

@@ -15,7 +15,7 @@
 									style="padding: 6rpx 4rpx;height: 64rpx;background: rgba(0,0,0,0.5);border-radius: 32rpx;">
 									<u-avatar :src="liveItem.liveImgUrl||$img.logo" size="32"></u-avatar>
 									<view class="colorf ml10 mr6">
-										<view>{{liveItem.liveName?truncateString(liveItem.liveName,5):"未命名"}}</view>
+										<view>{{liveItem.liveName?truncateString(liveItem.liveName,8):"未命名"}}</view>
 									</view>
 								</view>
 							</view>
@@ -30,7 +30,8 @@
 						<!-- 右边的 -->
 						<view :class=" liveItem.showType==1 ? 'siderow-group' : 'side-group'">
 							<view class="side-item">
-								<image  class="image"   @click="onLike(liveItem)" src="/static/images/live/like.png"></image>
+								<image class="image" @click="onLike(liveItem)" src="/static/images/live/like.png">
+								</image>
 								<view>{{liveItem.liveViewData?.like||0}}</view>
 							</view>
 							<!-- <view class="side-item">
@@ -45,10 +46,13 @@
 							</view>
 						</view>
 						<!-- 红包内容 -->
-						<view style="position: fixed;top:120rpx;left: 0rpx; z-index: 5;" class="content-top">
+						<view class="hongbao-box" v-if="liveItem.redInfo?.redStatus==1">
 							<view class="u-flex-y-center">
-								<image @click="onRed(liveItem)" v-if="liveItem.redInfo?.redStatus==1"
-									style="width: 70rpx;height: 70rpx;" src="/static/images/hongbao.png"></image>
+								<view class="tip">领红包</view>
+								<view class="item">
+									<image @click="onRed(liveItem)" src="/static/images/redbag.png" mode="widthFix">
+									</image>
+								</view>
 							</view>
 						</view>
 
@@ -62,7 +66,8 @@
 						<view class="videolist" style="margin: auto 0">
 							<view class="video" style="height:100vh">
 								<!-- 视频组件 -->
-								<video v-if="currentSwiperIndex === index && liveItem.livingUrl" :id="'myVideo_' + liveItem.liveId"
+								<video v-if="currentSwiperIndex === index && liveItem.livingUrl"
+									:id="'myVideo_' + liveItem.liveId"
 									:class="liveItem.showType == 1 ? 'video_row' : 'videotop'" :src="liveItem.livingUrl"
 									:autoplay="currentSwiperIndex === index" :controls='false' object-fit='contain'
 									:custom-cache="false" :enable-progress-gesture="false"
@@ -78,11 +83,6 @@
 									:http-cache="false" loop @error="videoError">
 								</video>
 								<view v-if="liveItem.videoUrl" class="time">{{liveItem.totalTime}}</view>
-								<!-- 加载提示 -->
-								<!-- <view v-if="liveItem.loading" class="loading-tip">
-									<u-loading mode="circle" color="#ffffff" size="36"></u-loading>
-									<text class="loading-text">加载中...</text>
-								</view> -->
 							</view>
 						</view>
 
@@ -196,6 +196,39 @@
 							</view>
 						</view>
 					</u-popup>
+					
+					<!-- 抽奖 -->
+				<!-- 	<u-popup :show="true" @close="" round='40rpx' bgColor='#fff'>
+						<view class=" p20 bgf" style="border-radius: 40rpx;height: fit-content;">
+							<view class="u-flex-row-reverse u-flex">
+								<u-icon name="close" size="18" @click=""></u-icon>
+							</view>
+							<view class="column align-center h400">
+								<view class="fs36 " style="color: #000000;font-weight: bold;">答题获取红包/积分奖励</view>
+								<view class="answerpop" @click="noredanswer">
+									<view class="color6 w350 fs24">
+										观看直播参与抽奖
+									</view>
+									<view class="answera">参与抽奖</view>
+								</view>
+							</view>
+						</view>
+					</u-popup> -->
+
+					<u-popup :show="liveItem.integral?.status" @close="!liveItem.integral?.status" round='20rpx'
+						mode="center" bgColor='#ffffff'>
+						<view class="integral-box">
+							<view class="top">
+								<view class="title">观看视频领芳华币</view>
+								<image class="photo" src="/static/images/integral.png" mode="widthFix"></image>
+							</view>
+
+							<view class="item">
+								<view class="title ">{{liveItem.integral?.msg}}</view>
+								<view class="button" @click="liveItem.integral.status=flase">确认</view>
+							</view>
+						</view>
+					</u-popup>
 
 					<!-- 观众列表弹窗 -->
 					<u-popup :show="showadd" @close="close" @open="openViews" round='20rpx' bgColor='#ffffff'>
@@ -765,10 +798,11 @@
 
 				// 1. 立即关闭旧直播间的WebSocket(同步操作)
 				const oldLive = this.list[oldIndex];
+				this.closeShop()
 				if (oldLive) {
 					this.pauseVideo(oldLive);
 					this.clearTimeTimer(oldLive); // 清除旧直播间的时间定时器
-					console.log("oldLive",oldLive)
+					console.log("oldLive", oldLive)
 					// 强制关闭并删除实例,确保连接被释放
 					if (this.socketInstances[oldLive.liveId]) {
 						try {
@@ -999,9 +1033,13 @@
 					const res = await liveMsg(liveItem.liveId, 30, 1);
 					if (res.code == 200) {
 						// 确保使用响应式更新
-						this.$set(liveItem, 'talklist', [...res.rows]);
+						const reversedTalkList = [...res.rows].reverse();
+						// 确保使用响应式更新(赋值反转后的数组)
+						this.$set(liveItem, 'talklist', reversedTalkList);
+
 						this.$nextTick(() => {
-							this.$set(liveItem, 'scrollIntoView', `list_${liveItem.talklist.length-1}`);
+							// 滚动到最新消息(反转后最新消息在数组最后一位,索引为 reversedTalkList.length - 1)
+							this.$set(liveItem, 'scrollIntoView', `list_${reversedTalkList.length - 1}`);
 						});
 					}
 				} catch (error) {
@@ -1500,30 +1538,24 @@
 					});
 					return;
 				}
-				liveItem.shopping = !liveItem.shopping
+				liveItem.shopping = true
 			},
 			close() {
 				this.showadd = false
 			},
-			// closes() {
-			// 	this.showziliao = !this.showziliao
-			// },
+
 			// 关闭小黄车
 			closeShop() {
 				// 通过当前swiper索引获取当前直播间实例
 				const currentLive = this.list[this.currentSwiperIndex];
 				if (currentLive) {
-					this.$set(currentLive, 'shopping', !currentLive.shopping);
+					this.$set(currentLive, 'shopping', false);
 				}
 			},
 			// 关闭红包
 			closeRed() {
 				this.showRed = !this.showRed
 			},
-			// 触摸结束
-			// handleTouchEnd() {
-			// 	this.isZoom = false; // 恢复原状
-			// },
 			getEWechatSdk() {
 				// 只在H5平台执行
 				// #ifdef H5
@@ -1751,12 +1783,17 @@
 							// console.log("关闭欢迎消息:", liveItem.showWelcomeMessage);
 						}, 1000); // 1秒后隐藏
 					} else if (socketMessage.cmd == 'Integral') {
+						let integral = {
+							msg: socketMessage.msg,
+							status: true
+						}
 						// 观看奖励事件 弹出框2s 显示msg
-						uni.showToast({
-							title: socketMessage.msg,
-							icon: 'none',
-							duration: 2000
-						});
+						this.$set(liveItem, 'integral', integral);
+						// uni.showToast({
+						// 	title: socketMessage.msg,
+						// 	icon: 'none',
+						// 	duration: 2000
+						// });
 
 					} else if (socketMessage.cmd == 'blockUser') {
 						//拉黑事件 主动删除用户token,强制跳转到登录页面
@@ -1815,7 +1852,6 @@
 			},
 			sendMsg(liveItem) {
 				if (!liveItem || !liveItem.liveId) return;
-
 				// 防止连续点击发送两次(短时锁定)
 				if (liveItem.isSending) return;
 				liveItem.isSending = true;
@@ -2086,6 +2122,36 @@
 		flex-direction: column;
 		justify-content: space-between;
 
+		.hongbao-box {
+			position: fixed;
+			top: 200rpx;
+			left: 30rpx;
+			z-index: 5;
+			border-radius: 16rpx;
+			background-color: rgba(77, 77, 77, 0.5);
+
+			.tip {
+				position: absolute;
+				width: 100%;
+				bottom: 0;
+				color: #fafcff;
+				font-size: 20rpx;
+				background-color: rgba(15, 15, 15, 0.8);
+				border-radius: 16rpx;
+				text-align: center;
+				padding: 4rpx 0;
+			}
+
+			.item {
+				padding: 10rpx 4rpx;
+
+				image {
+					width: 80rpx;
+					margin: 6rpx 0;
+				}
+			}
+		}
+
 		.siderow-group {
 			position: absolute;
 			top: 65%;
@@ -2210,6 +2276,57 @@
 
 	}
 
+	.integral-box {
+		width: 300rpx;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		border-radius: 20rpx;
+		overflow: hidden;
+
+
+		.top {
+			width: 100%;
+			position: relative;
+
+			.title {
+				width: 100%;
+				font-weight: 600;
+				font-size: 30rpx;
+				color: #FFFFFF;
+				text-shadow: 0px 4px 8px rgba(255, 89, 2, 0.8);
+				position: absolute;
+				top: 30rpx;
+				text-align: center;
+			}
+
+			.photo {
+				width: 100%;
+
+			}
+		}
+
+		.item {
+			padding: 20rpx;
+
+			.title {
+				font-weight: 500;
+				font-size: 30rpx;
+				text-align: center;
+			}
+
+			.button {
+				margin-top: 20rpx;
+				background: linear-gradient(270deg, #ff4702 0%, #fe6304 100%);
+				color: #fff;
+				text-align: center;
+				padding: 16rpx 30rpx;
+				border-radius: 10rpx;
+				font-weight: 500;
+			}
+		}
+	}
+
 	.goods {
 		position: fixed;
 		// bottom: 160rpx;
@@ -2401,7 +2518,36 @@
 			background: #fff;
 		}
 	}
+// 抽奖
+	.answerpop {
+		background: linear-gradient(to right, #fff7f8, #fee1e2);
+		margin-top: 30rpx;
+		padding: 10rpx 20rpx;
+		width: calc(100% - 40rpx);
+		border-radius: 20rpx;
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		box-shadow: 2px 2px 4px 0 rgba(255, 124, 126, 0.1);
+
+		.answera {
+			display: flex;
+			justify-content: center;
+			background-color: #fff;
+			padding: 20rpx 0;
+			width: 35%;
+			border-radius: 40rpx;
+			align-items: center;
+			margin: 10rpx 0;
+			box-shadow: 2px 2px 4px 0 rgba(72, 72, 72, 0.1);
 
+			image {
+				width: 40rpx;
+				height: 40rpx;
+				margin-right: 10rpx;
+			}
+		}
+	}
 	.shoppop {
 		padding: 22rpx 16rpx;
 

+ 233 - 265
pages/list/index.vue

@@ -1,74 +1,67 @@
 <template>
-	<view class=" content ">
+	<view class="content">
 		<mescroll-body bottom="0" ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback"
 			:down="downOption" :up="upOption">
 			<view class="list">
 				<view class="list-item" @click="goLive(item)" v-for="(item,index) in list" :key="index">
-
-					<!-- <image :src="item.liveImgUrl"></image> -->
-
-
-					<video v-if="item.flvHlsUrl && item.liveId" :id="'myVideo_' + item.liveId" :src="item.flvHlsUrl"
-						:autoplay="false" :controls='false' object-fit='contain' :custom-cache="false"
-						:enable-progress-gesture="false" vslide-gesture-in-fullscreen='true'
-						:show-center-play-btn="false" :http-cache="false" @error="videoError">
-					</video>
-
-					<video v-if="item.videoUrl" :src="item.videoUrl" :autoplay="false" :controls='false'
-						object-fit='contain' :custom-cache="false" :enable-progress-gesture="false"
-						vslide-gesture-in-fullscreen='true' :show-center-play-btn="false" :http-cache="false" loop
-						@error="videoError">
-					</video>
+					<image v-if="!item._isInView && item.liveImgUrl" :src="item.liveImgUrl"></image>
+					<!-- 直播流 -->
+					<video v-if="item._isInView && item.liveType == 1 && item.flvHlsUrl" :id="'myVideo_' + item.liveId"
+						:src="item.flvHlsUrl" :autoplay="false" :controls="false" object-fit="contain"
+						:custom-cache="false" :enable-progress-gesture="false" :show-center-play-btn="false"
+						:http-cache="false" :muted="true" @error="onVideoError(item, $event)"></video>
+
+					<!-- 录播视频 -->
+					<video v-if="item._isInView && item.liveType == 2 && item.videoUrl " :id="'myVideo_' + item.liveId"
+						:src="item.videoUrl" :autoplay="false" :controls="false" object-fit="contain"
+						:custom-cache="false" :enable-progress-gesture="false" :show-center-play-btn="false"
+						:http-cache="false" :loop="true" :muted="true" @error="onVideoError(item, $event)"
+						@loadedmetadata="onVideoLoaded(item)"></video>
 
 					<view class="info">
+						<!-- <text v-if="item.liveType == 1" class="live-badge">直播</text>
+						<text v-else class="record-badge">录播</text> -->
 						<text>{{item.liveName}}</text>
 					</view>
-				</view>
 
+					<!-- 添加错误提示 -->
+					<!-- <view v-if="item._error" class="error-tip">
+						视频加载失败
+					</view> -->
+				</view>
 			</view>
 		</mescroll-body>
 	</view>
-
 </template>
+
 <script>
 	import Hls from 'hls.js';
 	import {
 		liveList
 	} from '@/api/list'
 	import MescrollMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js";
+
 	export default {
-		mixins: [MescrollMixin], // 使用mixin
+		mixins: [MescrollMixin],
 		data() {
 			return {
 
-				hlsPlayer: null, // HLS播放器实例,
-				// 统一直播/录播类型标识
-				LIVE_TYPE: {
-					LIVE: 1, // 直播
-					RECORD: 2 // 录播
-				},
-
-
-
-
-
 				list: [],
-				liveId: null, // mescroll配置
 				downOption: {
 					offset: 80,
 					use: true,
-					auto: false // 是否在初始化后自动执行下拉回调
+					auto: false
 				},
 				upOption: {
 					use: true,
-					auto: true, // 是否在初始化时自动执行上拉回调
+					auto: true,
 					page: {
-						num: 0, // 当前页码
-						size: 10 // 每页数据条数
+						num: 0,
+						size: 10
 					}
-
 				},
-				mescroll: null // mescroll实例
+				mescroll: null,
+				observer: null // 存储IntersectionObserver实例
 			}
 		},
 		onLoad() {
@@ -76,235 +69,203 @@
 				uni.navigateTo({
 					url: '/pages/auth/login'
 				});
-
-			} // 销毁HLS播放器(如果使用了hls.js)
-			if (this.hlsPlayer) {
-				this.hlsPlayer.destroy();
-				this.hlsPlayer = null;
 			}
 		},
 		onUnload() {
-			this.list.forEach(item => {
-				this.$set(item, 'isDestroyed', true); // 标记已销毁,阻止播放
-				this.pauseAndCleanVideo(item); // 统一清理
-			});
+			// 清理所有观察器和视频资源
+			this.cleanupAllVideos();
 		},
-		methods: { // 处理单个视频项,适配m3u8流
-			initVideoPlayer(item) {
-				// 基础校验:无地址/无ID,不初始化
-				const isLive = item.liveType === 1;
-				const hasValidUrl = isLive ? (item.flvHlsUrl?.includes('.m3u8')) : !!item.videoUrl;
-				if (!hasValidUrl || !item.liveId) return;
+		methods: {
+			// 视频加载错误处理
+			onVideoError(item, e) {
+				// console.error('视频加载错误:', e.detail, item);
+				this.$set(item, '_error', true);
+				this.pauseVideo(item);
+			},
+
+			// 视频元数据加载完成
+			onVideoLoaded(item) {
+				console.log('视频元数据加载完成:', item.liveId);
+				this.$set(item, '_error', false);
+			},
 
+			// 初始化所有视频观察器
+			initAllVideoObservers() {
+				// 先清理旧的
+				this.cleanupAllVideos();
+
+				// 使用setTimeout确保DOM已渲染
+				setTimeout(() => {
+					this.list.forEach(item => {
+						if (item.liveId) {
+							this.initVideoObserver(item);
+						}
+					});
+				}, 300);
+			},
+
+			// 初始化单个视频的观察器
+			initVideoObserver(item) {
 				const videoId = `myVideo_${item.liveId}`;
-				console.log(`[视口监听] 初始化:${videoId}`);
 
-				// 1. 创建交叉观察器(监听是否进入视口)
-				const observer = uni.createIntersectionObserver(this, {
-					thresholds: [0.2], // 20% 进入视口触发
-					observeAll: false
-				});
+				// 创建交叉观察器(监听视频项是否进入视口)
+				const observer = uni.createIntersectionObserver(this);
+
+				observer.relativeToViewport({
+					top: 100, // 顶部提前100px触发(优化体验,避免刚进入就播放)
+					bottom: 100 // 底部延迟100px触发(避免快速滑动时频繁切换)
+				}).observe(`#${videoId}`, (res) => {
+					const isInView = res.intersectionRatio > 0; // 是否进入视口(交叉比例>0)
 
-				// 2. 监听视口状态,触发按需播放/暂停
-				observer.relativeTo('.list').observe(`#${videoId}`, (res) => {
-					const isInView = res.intersectionRatio > 0; // 是否在视口内
+					// 1. 动态更新 _isInView 状态(确保响应式)
+					if (isInView !== item._isInView) {
+						this.$set(item, '_isInView', isInView);
+					}
 
+					// 2. 联动播放/暂停:进入视口播放,离开暂停
 					if (isInView) {
-						console.log(`[视口监听] 进入视口,触发按需播放:${videoId}`);
-						// 标记为在视口,避免重复调用
-						this.$set(item, 'isInView', true);
-						// 调用统一播放入口,实现按需播放
-						this.setupVideoPlayback(item);
+						this.playVideo(item); // 进入视口:播放视频
 					} else {
-						console.log(`[视口监听] 离开视口,暂停播放:${videoId}`);
-						// 标记为离开视口
-						this.$set(item, 'isInView', false);
-						// 暂停播放并清理资源
-						this.pauseAndCleanVideo(item);
+						this.pauseVideo(item); // 离开视口:暂停视频
 					}
 				});
 
-				// 3. 存储观察器到 item,便于后续销毁
-				this.$set(item, 'observer', observer);
-				// 标记未销毁,避免 setupVideoPlayback 跳过
-				this.$set(item, 'isDestroyed', false);
+				// 存储观察器引用,便于后续清理
+				this.$set(item, '_observer', observer);
 			},
-			pauseAndCleanVideo(item) {
-				// 1. 暂停录播/小程序直播(通过 videoContext)
-				if (item.videoContext) {
-					item.videoContext.pause();
-					this.$set(item, 'videoContext', null); // 清空上下文,避免重复调用
-				}
 
-				// 2. 销毁 HLS 实例(直播 H5 场景)
-				if (item.hlsInstance) {
-					item.hlsInstance.destroy();
-					this.$set(item, 'hlsInstance', null);
-				}
-
-				// 3. 销毁视口监听(避免列表刷新后仍监听)
-				if (item.observer) {
-					item.observer.disconnect();
-					this.$set(item, 'observer', null);
-				}
-
-				// 4. 标记为已销毁(防止重复初始化)
-				this.$set(item, 'isDestroyed', true);
-			},
-
-			setupVideoPlayback(item) {
-				// 1. 基础校验:先判断类型,再校验 URL(避免无效执行)
-				const isLive = item.liveType === this.LIVE_TYPE.LIVE;
-				const videoUrl = isLive ? item.flvHlsUrl : item.videoUrl;
-
-				// 无效场景直接返回(录播无URL、直播无m3u8、已销毁)
-				if (!videoUrl || item.isDestroyed || (isLive && !videoUrl.includes('.m3u8'))) {
-					console.warn(`[播放校验] 跳过无效播放:`, item.liveId, '类型:', isLive ? '直播' : '录播');
-					return;
-				}
+			// 播放视频
+			playVideo(item) {
+				// 仅当:进入视口(_isInView=true)、未在播放(_isPlaying=false)、无错误(_error=false)时播放
+				if (!item._isInView || item._isPlaying || item._error) return;
 
 				const videoId = `myVideo_${item.liveId}`;
-				console.log(`[按需播放] 触发:${isLive ? '直播' : '录播'},ID:${videoId}`);
-
-				// 2. 先清理旧资源(避免重复初始化)
-				this.pauseAndCleanVideo(item);
-
-				// 3. 直播逻辑:仅 H5 平台用 HLS,其他平台(小程序/App)用原生 video
-				if (isLive) {
-					// #ifdef H5
-					// 直播 + H5 + m3u8:初始化 HLS
-					this.initHlsPlayer(item);
-					// #endif
-
-					// #ifndef H5
-					// 小程序/App 直播:直接用原生 videoContext(无需 HLS,平台自带 m3u8 支持)
-					this.initNativeVideo(item, videoId, isLive);
-					// #endif
-				} else {
-					// 4. 录播逻辑:所有平台统一用原生 videoContext(无需 HLS)
-					this.initNativeVideo(item, videoId, isLive);
-				}
-			},
-			/**
-			 * 初始化原生视频(录播 + 小程序/App 直播)
-			 * @param {Object} item - 列表项数据
-			 * @param {String} videoId - 视频DOM ID
-			 * @param {Boolean} isLive - 是否为直播
-			 */
-			initNativeVideo(item, videoId, isLive) {
+				const isLive = item.liveType == 1;
+
+				// 获取video上下文
 				uni.createSelectorQuery().in(this)
 					.select(`#${videoId}`)
 					.fields({
 						context: true
-					}) // 仅获取视频上下文
+					})
 					.exec((res) => {
-						if (!res || !res[0]?.context) {
-							console.warn(`[原生视频] 未找到上下文:${videoId}`);
-							return;
-						}
-
-						const videoContext = res[0].context;
-						this.$set(item, 'videoContext', videoContext); // 存储上下文,用于后续暂停
-
-						// 直播:直接播放(需用户交互,失败时提示)
-						if (isLive) {
-							videoContext.play().catch(err => {
-								console.log(`[直播播放] 自动播放失败(需点击):${item.liveId}`, err);
-								// 可选:添加「播放按钮」,点击后调用 videoContext.play()
-							});
-						}
-						// 录播:支持断点续播(从上次进度开始)
-						else {
-							const startDuration = item.nowDuration || 0; // 从接口获取的上次播放进度
-							videoContext.seek(startDuration); // 跳转到指定进度
-							videoContext.play().catch(err => {
-								console.log(`[录播播放] 失败:${item.liveId}`, err);
-							});
+						if (res && res[0] && res[0].context) {
+							const videoContext = res[0].context;
+
+							try {
+								if (isLive) {
+									// 直播流处理
+									//	#ifdef H5
+									if (item.flvHlsUrl && item.flvHlsUrl.includes('.m3u8') && Hls.isSupported()) {
+										this.setupHlsPlayback(item, videoContext);
+									} else {
+										videoContext.play();
+									}
+									//#else
+									videoContext.play();
+									//#endif
+								} else {
+									// 录播视频处理 - 添加重试机制
+									const playAttempt = () => {
+										videoContext.play().then(() => {
+											console.log('录播视频播放成功:', item.liveId);
+											this.$set(item, '_isPlaying', true);
+											this.$set(item, '_videoContext', videoContext);
+										}).catch(err => {
+											console.error('录播视频播放失败:', err);
+											this.$set(item, '_error', true);
+										});
+									};
+
+									// 如果视频已加载,直接播放;否则监听加载事件
+									if (videoContext.duration > 0) {
+										playAttempt();
+									} else {
+										videoContext.onloadedmetadata = playAttempt;
+									}
+								}
+								// 播放成功后标记 _isPlaying=true
+								this.$set(item, '_isPlaying', true);
+								this.$set(item, '_videoContext', videoContext);
+							} catch (err) {
+								console.error(`播放失败 ${videoId}:`, err);
+								this.$set(item, '_error', true);
+							}
 						}
 					});
 			},
 
-			// 初始化HLS播放器
-			initHlsPlayer(item) {
-				// 兜底校验:确保是直播且有 m3u8 地址
-				if (item.liveType !== this.LIVE_TYPE.LIVE || !item.flvHlsUrl?.includes('.m3u8')) {
-					console.error(`[HLS 错误] 非直播场景调用:${item.liveId}`);
-					return;
+
+
+			// 暂停视频
+			pauseVideo(item) {
+				if (!item._isPlaying) return;
+
+				if (item._videoContext) {
+					item._videoContext.pause();
 				}
 
-				const videoUrl = item.flvHlsUrl;
-				if (!Hls.isSupported()) {
-					console.error(`[HLS 错误] 浏览器不支持 HLS:${item.liveId}`);
-					return;
+				// 清理HLS实例(如果是直播且使用了HLS)
+				//	#ifdef H5
+				if (item.liveType == 1 && item._hlsInstance) {
+					item._hlsInstance.destroy();
+					this.$set(item, '_hlsInstance', null);
 				}
+				//#endif
+				// 暂停后标记 _isPlaying=false(_isInView 已由视口监听控制)
+				this.$set(item, '_isPlaying', false);
+			},
 
-				// 获取 H5 视频 DOM 节点
-				const video = document.getElementById(`myVideo_${item.liveId}`);
-				if (!video) {
-					console.warn(`[HLS 错误] 未找到视频节点:myVideo_${item.liveId}`);
-					return;
+			// H5平台的HLS播放设置
+			// #ifdef H5
+			setupHlsPlayback(item, videoElement) {
+				if (item._hlsInstance) {
+					item._hlsInstance.destroy();
 				}
 
-				// 初始化 HLS(低延迟配置,适合直播)
 				const hls = new Hls({
 					enableWorker: true,
-					lowLatencyMode: true, // 直播低延迟关键配置
-					debug: false,
-					maxBufferLength: 3, // 减少缓冲,降低延迟(根据需求调整)
-					maxMaxBufferLength: 10
+					lowLatencyMode: true,
+					debug: false
 				});
 
-				// 绑定视频节点 + 加载流
-				hls.attachMedia(video);
-				hls.loadSource(videoUrl);
+				hls.attachMedia(videoElement);
+				hls.loadSource(item.flvHlsUrl);
 
-				// 解析成功后播放
 				hls.on(Hls.Events.MANIFEST_PARSED, () => {
-					console.log(`[HLS 直播] 解析成功:${item.liveId}`);
-					video.play().catch(err => {
-						console.log(`[HLS 播放] 自动播放失败(需点击):${item.liveId}`, err);
-					});
+					videoElement.play();
 				});
 
-				// 错误处理(自动恢复或销毁)
-				hls.on(Hls.Events.ERROR, (event, data) => {
-					console.error(`[HLS 错误] ${item.liveId}:`, data);
-					if (data.fatal) {
-						switch (data.type) {
-							case Hls.ErrorTypes.NETWORK_ERROR:
-								hls.startLoad(); // 网络错误:重试加载
-								break;
-							default:
-								hls.destroy(); // 其他致命错误:销毁实例
-								this.$set(item, 'hlsInstance', null);
-								break;
-						}
+				this.$set(item, '_hlsInstance', hls);
+			},
+			// #endif
+
+			// 清理所有视频资源
+			cleanupAllVideos() {
+				this.list.forEach(item => {
+					this.pauseVideo(item);
+					// 重置视口状态:未进入视口
+					this.$set(item, '_isInView', false);
+					// 销毁观察器
+					if (item._observer) {
+						item._observer.disconnect();
+						this.$set(item, '_observer', null);
 					}
 				});
-
-				// 存储 HLS 实例到 item(便于后续销毁)
-				this.$set(item, 'hlsInstance', hls);
 			},
 
-
 			// mescroll初始化
 			mescrollInit(mescroll) {
 				this.mescroll = mescroll;
 			},
+
 			// 下拉刷新回调
 			downCallback(mescroll) {
-				// 1. 先清理所有旧列表的视频资源(直播 HLS + 录播上下文)
-				this.list.forEach(item => {
-					this.pauseAndCleanVideo(item);
-				});
-
-				// 2. 重置列表和分页(必须在清理后执行)
+				this.cleanupAllVideos();
 				this.list = [];
-				mescroll.resetUpScroll(); // 重置上拉加载的页码
-
-				// 3. 可选:下拉后自动加载第一页(符合用户预期)
-				this.mescroll.triggerUpScroll();
+				mescroll.resetUpScroll();
 			},
+
 			// 上拉加载回调
 			upCallback(mescroll) {
 				const pageNum = mescroll.num;
@@ -318,24 +279,32 @@
 				liveList(data).then(res => {
 					if (res.code == 200) {
 						let curPageData = res.rows || [];
+						console.log("curPageData在这里>>>>", curPageData)
 						let totalSize = res.total || 0;
 
+						// 预处理数据,添加状态字段
+						curPageData = curPageData.map(item => {
+							return {
+								...item,
+								_error: false,
+								_isPlaying: false,
+								_isInView: false // 关键:初始化为未进入视口
+							};
+						});
+
 						if (pageNum === 1) {
 							this.list = [];
 						}
 
-						// 追加新数据
-					 this.list = this.list.concat(curPageData);
-
-      // 关键修改:用 $nextTick 等待DOM渲染完成
-      this.$nextTick(() => {
-        curPageData.forEach(item => {
-          this.initVideoPlayer(item); // 此时视频节点已存在
-        });
-      });
+						this.list = this.list.concat(curPageData);
+						console.log("list在这里>>>>", list)
+						// DOM更新后初始化视频观察器
+						this.$nextTick(() => {
+							this.initAllVideoObservers();
+						});
 
-      mescroll.endBySize(curPageData.length, totalSize);
-    } else {
+						mescroll.endBySize(curPageData.length, totalSize);
+					} else {
 						mescroll.endErr();
 						uni.showToast({
 							title: res.msg,
@@ -344,48 +313,14 @@
 					}
 				}).catch(err => {
 					mescroll.endErr();
-					console.log("请求异常:" + JSON.stringify(err));
 				});
 			},
 
-
-
-
 			goLive(item) {
-				this.liveId = item.liveId
-				console.log("要传的liveId", this.liveId)
 				uni.navigateTo({
 					url: `/pages/home/living?liveId=${item.liveId}&immediate=true`
 				});
-			},
-
-			// getList() {
-			// 	const data = {
-			// 		page: 1,
-			// 		page_size: 10,
-			// 	};
-			// 	uni.showLoading({
-			// 		title: "处理中..."
-			// 	});
-			// 	liveList(data)
-			// 		.then(res => {
-			// 			if (res.code == 200) {
-			// 				this.list = res.rows; // 直接赋值给 this.list
-			// 				console.log("list>>", this.list); // ✅ 打印已定义的 this.list
-			// 			} else {
-			// 				uni.showToast({
-			// 					title: res.msg,
-			// 					icon: 'none'
-			// 				});
-			// 			}
-			// 		})
-			// 		.catch(rej => {
-			// 			console.log("请求失败:", JSON.stringify(rej));
-			// 		})
-			// 		.finally(() => {
-			// 			uni.hideLoading();
-			// 		});
-			// }
+			}
 		}
 	}
 </script>
@@ -406,31 +341,64 @@
 				width: 340rpx;
 				height: 600rpx;
 				background-color: #0d0d0d;
-				margin-right: 10rpx;
 				margin-bottom: 24rpx;
 				overflow: hidden;
 				position: relative;
 
+				image {
+					width: 100%;
+					height: 100%;
+				}
+
 				video {
 					width: 100%;
 					height: 100%;
-					/* 视频填满列表项,确保可视区域判断准确 */
+					object-fit: cover;
 				}
 
 				.info {
 					position: absolute;
 					left: 20rpx;
 					bottom: 14rpx;
+					right: 20rpx;
 					color: #ffffff;
+					display: flex;
+					align-items: center;
+
+					.live-badge {
+						background-color: #e74c3c;
+						padding: 4rpx 12rpx;
+						border-radius: 8rpx;
+						font-size: 20rpx;
+						margin-right: 12rpx;
+					}
+
+					.record-badge {
+						background-color: #3498db;
+						padding: 4rpx 12rpx;
+						border-radius: 8rpx;
+						font-size: 20rpx;
+						margin-right: 12rpx;
+					}
 				}
 
-				image {
-					width: 100%;
-					height: 100%;
+				.error-tip {
+					position: absolute;
+					top: 50%;
+					left: 50%;
+					transform: translate(-50%, -50%);
+					color: #fff;
+					background-color: rgba(0, 0, 0, 0.7);
+					padding: 16rpx 24rpx;
+					border-radius: 8rpx;
+					font-size: 24rpx;
 				}
+			}
 
+			// 使列表项均匀分布
+			.list-item:nth-child(2n) {
+				margin-right: 0;
 			}
 		}
-
 	}
 </style>

TEMPAT SAMPAH
static/images/integral.png


TEMPAT SAMPAH
static/images/redbag.png


File diff ditekan karena terlalu besar
+ 0 - 0
unpackage/dist/dev/mp-weixin/common/assets.js


+ 1 - 1
unpackage/dist/dev/mp-weixin/pages/auth/loginIndex.js

@@ -1 +1 @@
-"use strict";const e=require("../../common/vendor.js");require("../../api/login.js");const g=require("../../common/assets.js"),u={data(){return{btnLoading:!1,agree:!1}},onLoad(){if(!this.$isLogin()){let t=getCurrentPages(),o=t[t.length-3];if(t.length>2&&o&&(o.route=="pages/auth/login"||o.route=="pages/auth/loginIndex"||o.route=="pages/common/launch"))e.index.navigateBack({delta:2});else if(String(navigator.userAgent.toLowerCase().match(/MicroMessenger/i))==="micromessenger"){const s=getCurrentPages();if(s.length>1){const a=s[s.length-2],n=a.options;if(n&&JSON.stringify(n)!="{}"){let r="/"+a.route+e.index.$u.queryParams(n);e.index.setStorageSync("beforLoginPage",r)}else e.index.setStorageSync("beforLoginPage","/"+a.route);e.index.redirectTo({url:"/pages/auth/h5WxLogin"})}else this.submit()}else e.index.redirectTo({url:"/pages/auth/login"})}},onShow(){this.$isLogin()&&e.index.reLaunch({url:"../course/index",animationType:"none",animationDuration:2e3})},methods:{goToWeb(t){e.index.setStorageSync("url",t==0?"https://userapp.his.cdwjyyh.com/web/userAgreement":"https://userapp.his.cdwjyyh.com/web/privacyPolicy"),e.index.navigateTo({url:"/pages/index/h5"})},handleAgree(){this.agree=!this.agree},submit(){this.$showLoginPage()},handleOtherLogin(){e.index.redirectTo({url:"/pages/auth/login"})},close(t){this.$refs.popup.close(),t=="agree"&&(this.agree=!0,this.submit())}}};function h(t,o,s,a,n,r){return{a:n.btnLoading,b:n.btnLoading,c:e.o((...i)=>r.submit&&r.submit(...i)),d:n.btnLoading,e:e.o((...i)=>r.handleOtherLogin&&r.handleOtherLogin(...i)),f:g._imports_0$5,g:!n.agree,h:g._imports_1$5,i:n.agree,j:e.o((...i)=>r.handleAgree&&r.handleAgree(...i)),k:e.o(i=>r.goToWeb(0)),l:e.o(i=>r.goToWeb(1))}}const c=e._export_sfc(u,[["render",h],["__scopeId","data-v-648b5fa7"]]);wx.createPage(c);
+"use strict";const e=require("../../common/vendor.js");require("../../api/login.js");const g=require("../../common/assets.js"),u={data(){return{btnLoading:!1,agree:!1}},onLoad(){if(!this.$isLogin()){let t=getCurrentPages(),o=t[t.length-3];if(t.length>2&&o&&(o.route=="pages/auth/login"||o.route=="pages/auth/loginIndex"||o.route=="pages/common/launch"))e.index.navigateBack({delta:2});else if(String(navigator.userAgent.toLowerCase().match(/MicroMessenger/i))==="micromessenger"){const s=getCurrentPages();if(s.length>1){const a=s[s.length-2],n=a.options;if(n&&JSON.stringify(n)!="{}"){let r="/"+a.route+e.index.$u.queryParams(n);e.index.setStorageSync("beforLoginPage",r)}else e.index.setStorageSync("beforLoginPage","/"+a.route);e.index.redirectTo({url:"/pages/auth/h5WxLogin"})}else this.submit()}else e.index.redirectTo({url:"/pages/auth/login"})}},onShow(){this.$isLogin()&&e.index.reLaunch({url:"../course/index",animationType:"none",animationDuration:2e3})},methods:{goToWeb(t){e.index.setStorageSync("url",t==0?"https://userapp.his.cdwjyyh.com/web/userAgreement":"https://userapp.his.cdwjyyh.com/web/privacyPolicy"),e.index.navigateTo({url:"/pages/index/h5"})},handleAgree(){this.agree=!this.agree},submit(){this.$showLoginPage()},handleOtherLogin(){e.index.redirectTo({url:"/pages/auth/login"})},close(t){this.$refs.popup.close(),t=="agree"&&(this.agree=!0,this.submit())}}};function h(t,o,s,a,n,r){return{a:n.btnLoading,b:n.btnLoading,c:e.o((...i)=>r.submit&&r.submit(...i)),d:n.btnLoading,e:e.o((...i)=>r.handleOtherLogin&&r.handleOtherLogin(...i)),f:g._imports_0$5,g:!n.agree,h:g._imports_1$4,i:n.agree,j:e.o((...i)=>r.handleAgree&&r.handleAgree(...i)),k:e.o(i=>r.goToWeb(0)),l:e.o(i=>r.goToWeb(1))}}const c=e._export_sfc(u,[["render",h],["__scopeId","data-v-648b5fa7"]]);wx.createPage(c);

File diff ditekan karena terlalu besar
+ 0 - 0
unpackage/dist/dev/mp-weixin/pages/home/live.js


File diff ditekan karena terlalu besar
+ 0 - 0
unpackage/dist/dev/mp-weixin/pages/home/living.js


File diff ditekan karena terlalu besar
+ 0 - 0
unpackage/dist/dev/mp-weixin/pages/home/living.wxml


File diff ditekan karena terlalu besar
+ 0 - 0
unpackage/dist/dev/mp-weixin/pages/home/living.wxss


File diff ditekan karena terlalu besar
+ 0 - 0
unpackage/dist/dev/mp-weixin/pages/list/index.js


+ 1 - 1
unpackage/dist/dev/mp-weixin/pages/list/index.wxml

@@ -1 +1 @@
-<view class="content data-v-7bd28468"><mescroll-body wx:if="{{f}}" class="r data-v-7bd28468" u-s="{{['d']}}" u-r="mescrollRef" bindinit="{{c}}" binddown="{{d}}" bindup="{{e}}" u-i="7bd28468-0" bind:__l="__l" u-p="{{f}}"><view class="list data-v-7bd28468"><view wx:for="{{a}}" wx:for-item="item" wx:key="j" class="list-item data-v-7bd28468" bindtap="{{item.i}}"><video wx:if="{{item.a}}" class="data-v-7bd28468" id="{{item.b}}" src="{{item.c}}" autoplay="{{false}}" controls="{{false}}" object-fit="contain" custom-cache="{{false}}" enable-progress-gesture="{{false}}" vslide-gesture-in-fullscreen="true" show-center-play-btn="{{false}}" http-cache="{{false}}" binderror="{{item.d}}"></video><video wx:if="{{item.e}}" class="data-v-7bd28468" src="{{item.f}}" autoplay="{{false}}" controls="{{false}}" object-fit="contain" custom-cache="{{false}}" enable-progress-gesture="{{false}}" vslide-gesture-in-fullscreen="true" show-center-play-btn="{{false}}" http-cache="{{false}}" loop binderror="{{item.g}}"></video><view class="info data-v-7bd28468"><text class="data-v-7bd28468">{{item.h}}</text></view></view></view></mescroll-body></view>
+<view class="content data-v-7bd28468"><mescroll-body wx:if="{{f}}" class="r data-v-7bd28468" u-s="{{['d']}}" u-r="mescrollRef" bindinit="{{c}}" binddown="{{d}}" bindup="{{e}}" u-i="7bd28468-0" bind:__l="__l" u-p="{{f}}"><view class="list data-v-7bd28468"><view wx:for="{{a}}" wx:for-item="item" wx:key="n" class="list-item data-v-7bd28468" bindtap="{{item.m}}"><image wx:if="{{item.a}}" class="data-v-7bd28468" src="{{item.b}}"></image><video wx:if="{{item.c}}" class="data-v-7bd28468" id="{{item.d}}" src="{{item.e}}" autoplay="{{false}}" controls="{{false}}" object-fit="contain" custom-cache="{{false}}" enable-progress-gesture="{{false}}" show-center-play-btn="{{false}}" http-cache="{{false}}" muted="{{true}}" binderror="{{item.f}}"></video><video wx:if="{{item.g}}" class="data-v-7bd28468" id="{{item.h}}" src="{{item.i}}" autoplay="{{false}}" controls="{{false}}" object-fit="contain" custom-cache="{{false}}" enable-progress-gesture="{{false}}" show-center-play-btn="{{false}}" http-cache="{{false}}" loop="{{true}}" muted="{{true}}" binderror="{{item.j}}" bindloadedmetadata="{{item.k}}"></video><view class="info data-v-7bd28468"><text class="data-v-7bd28468">{{item.l}}</text></view></view></view></mescroll-body></view>

+ 1 - 1
unpackage/dist/dev/mp-weixin/pages/list/index.wxss

@@ -1 +1 @@
-.content.data-v-7bd28468{background-color:#111;min-height:100vh;padding:24rpx}.content .list.data-v-7bd28468{display:flex;justify-content:space-between;flex-wrap:wrap}.content .list .list-item.data-v-7bd28468{border-radius:16rpx;width:340rpx;height:600rpx;background-color:#0d0d0d;margin-right:10rpx;margin-bottom:24rpx;overflow:hidden;position:relative}.content .list .list-item video.data-v-7bd28468{width:100%;height:100%}.content .list .list-item .info.data-v-7bd28468{position:absolute;left:20rpx;bottom:14rpx;color:#fff}.content .list .list-item image.data-v-7bd28468{width:100%;height:100%}
+.content.data-v-7bd28468{background-color:#111;min-height:100vh;padding:24rpx}.content .list.data-v-7bd28468{display:flex;justify-content:space-between;flex-wrap:wrap}.content .list .list-item.data-v-7bd28468{border-radius:16rpx;width:340rpx;height:600rpx;background-color:#0d0d0d;margin-bottom:24rpx;overflow:hidden;position:relative}.content .list .list-item image.data-v-7bd28468{width:100%;height:100%}.content .list .list-item video.data-v-7bd28468{width:100%;height:100%;object-fit:cover}.content .list .list-item .info.data-v-7bd28468{position:absolute;left:20rpx;bottom:14rpx;right:20rpx;color:#fff;display:flex;align-items:center}.content .list .list-item .info .live-badge.data-v-7bd28468{background-color:#e74c3c;padding:4rpx 12rpx;border-radius:8rpx;font-size:20rpx;margin-right:12rpx}.content .list .list-item .info .record-badge.data-v-7bd28468{background-color:#3498db;padding:4rpx 12rpx;border-radius:8rpx;font-size:20rpx;margin-right:12rpx}.content .list .list-item .error-tip.data-v-7bd28468{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:#fff;background-color:rgba(0,0,0,.7);padding:16rpx 24rpx;border-radius:8rpx;font-size:24rpx}.content .list .list-item.data-v-7bd28468:nth-child(2n){margin-right:0}

File diff ditekan karena terlalu besar
+ 0 - 0
unpackage/dist/dev/mp-weixin/pages_no/living827.js


File diff ditekan karena terlalu besar
+ 0 - 0
unpackage/dist/dev/mp-weixin/pages_no/zuizao.js


File diff ditekan karena terlalu besar
+ 0 - 0
unpackage/dist/dev/mp-weixin/pages_shop/confirmCreateOrder.js


File diff ditekan karena terlalu besar
+ 0 - 0
unpackage/dist/dev/mp-weixin/pages_shop/confirmPackageOrder.js


+ 1 - 1
unpackage/dist/dev/mp-weixin/pages_shop/storeOrderDelivery.js

@@ -1 +1 @@
-"use strict";const e=require("../common/vendor.js"),n=require("../common/assets.js"),d={data(){return{statusBarHeight:e.index.getStorageSync("menuInfo").statusBarHeight,orderId:null,deliveryId:null,express:{},expressList:[]}},onLoad(r){this.orderId=r.orderId,this.getExpress()},methods:{getExpress(){var r={orderId:this.orderId};getExpress(r).then(t=>{t.code==200?(this.express=t.express,this.expressList=t.data,this.deliveryId=t.deliveryId):e.index.showToast({icon:"none",title:"请求失败"})})},back(){e.index.navigateBack()},copyOrderSn(r){e.index.setClipboardData({data:r,success:()=>{e.index.showToast({title:"内容已成功复制到剪切板",icon:"none"})}})},callPhone(r){e.index.makePhoneCall({phoneNumber:r})}}};function a(r,t,p,x,s,o){return e.e({a:n._imports_0$13,b:s.statusBarHeight,c:n._imports_1$9,d:e.o((...i)=>o.back&&o.back(...i)),e:e.t(s.deliveryId),f:e.o(i=>o.copyOrderSn(s.deliveryId)),g:e.t(s.express.name),h:s.expressList!=null},s.expressList!=null?e.e({i:s.expressList.Traces!=null},s.expressList.Traces!=null?{j:e.f(s.expressList.Traces,(i,c,u)=>({a:e.t(i.AcceptStation),b:e.t(i.AcceptTime),c})),k:n._imports_2$6,l:n._imports_3$2}:{}):{})}const l=e._export_sfc(d,[["render",a]]);wx.createPage(l);
+"use strict";const e=require("../common/vendor.js"),n=require("../common/assets.js"),d={data(){return{statusBarHeight:e.index.getStorageSync("menuInfo").statusBarHeight,orderId:null,deliveryId:null,express:{},expressList:[]}},onLoad(r){this.orderId=r.orderId,this.getExpress()},methods:{getExpress(){var r={orderId:this.orderId};getExpress(r).then(t=>{t.code==200?(this.express=t.express,this.expressList=t.data,this.deliveryId=t.deliveryId):e.index.showToast({icon:"none",title:"请求失败"})})},back(){e.index.navigateBack()},copyOrderSn(r){e.index.setClipboardData({data:r,success:()=>{e.index.showToast({title:"内容已成功复制到剪切板",icon:"none"})}})},callPhone(r){e.index.makePhoneCall({phoneNumber:r})}}};function a(r,t,p,x,s,o){return e.e({a:n._imports_0$13,b:s.statusBarHeight,c:n._imports_1$9,d:e.o((...i)=>o.back&&o.back(...i)),e:e.t(s.deliveryId),f:e.o(i=>o.copyOrderSn(s.deliveryId)),g:e.t(s.express.name),h:s.expressList!=null},s.expressList!=null?e.e({i:s.expressList.Traces!=null},s.expressList.Traces!=null?{j:e.f(s.expressList.Traces,(i,c,u)=>({a:e.t(i.AcceptStation),b:e.t(i.AcceptTime),c})),k:n._imports_2$6,l:n._imports_3$3}:{}):{})}const l=e._export_sfc(d,[["render",a]]);wx.createPage(l);

+ 1 - 1
unpackage/dist/dev/mp-weixin/project.private.config.json

@@ -2,7 +2,7 @@
   "projectname": "签约",
   "setting": {
     "compileHotReLoad": true,
-    "urlCheck": false
+    "urlCheck": true
   },
   "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
   "condition": {

TEMPAT SAMPAH
unpackage/dist/dev/mp-weixin/static/images/integral.png


TEMPAT SAMPAH
unpackage/dist/dev/mp-weixin/static/images/redbag.png


Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini