|
@@ -817,6 +817,13 @@
|
|
|
errorCount: 0, // 错误计数
|
|
errorCount: 0, // 错误计数
|
|
|
lastPerformanceCheck: 0, // 上次性能检查时间
|
|
lastPerformanceCheck: 0, // 上次性能检查时间
|
|
|
|
|
|
|
|
|
|
+ // 观看时长统计相关
|
|
|
|
|
+ watchStartTime: 0, // 观看开始时间(毫秒时间戳)
|
|
|
|
|
+ accumulatedWatchDuration: 0, // 累计观看时长(秒)
|
|
|
|
|
+ isPageVisible: true, // 页面是否可见
|
|
|
|
|
+ lastPauseTime: 0, // 上次暂停时间
|
|
|
|
|
+ watchDurationTimer: null, // 观看时长计时器
|
|
|
|
|
+
|
|
|
stayTime: 0,
|
|
stayTime: 0,
|
|
|
startTime: 0,
|
|
startTime: 0,
|
|
|
|
|
|
|
@@ -1156,6 +1163,12 @@
|
|
|
// }
|
|
// }
|
|
|
// 恢复播放和连接
|
|
// 恢复播放和连接
|
|
|
await this.resumePageActivity();
|
|
await this.resumePageActivity();
|
|
|
|
|
+
|
|
|
|
|
+ // 恢复观看时长统计(如果之前已经开始统计)
|
|
|
|
|
+ if (this.watchStartTime > 0 || this.accumulatedWatchDuration > 0) {
|
|
|
|
|
+ this.resumeWatchDurationTracking();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// this.userinfo = JSON.parse(uni.getStorageSync('userInfo'));
|
|
// this.userinfo = JSON.parse(uni.getStorageSync('userInfo'));
|
|
|
this.userinfo = uni.getStorageSync('userinfo');
|
|
this.userinfo = uni.getStorageSync('userinfo');
|
|
|
this.isAgreement = uni.getStorageSync('isAgreement');
|
|
this.isAgreement = uni.getStorageSync('isAgreement');
|
|
@@ -1339,6 +1352,10 @@
|
|
|
// 清除所有定时器(使用增强清理)
|
|
// 清除所有定时器(使用增强清理)
|
|
|
// this.clearAllTimersEnhanced();
|
|
// this.clearAllTimersEnhanced();
|
|
|
// this.stopHeartBeat();
|
|
// this.stopHeartBeat();
|
|
|
|
|
+
|
|
|
|
|
+ // 暂停观看时长统计
|
|
|
|
|
+ this.pauseWatchDurationTracking();
|
|
|
|
|
+
|
|
|
// 页面隐藏时,提交当前流量数据
|
|
// 页面隐藏时,提交当前流量数据
|
|
|
if (this.videoLoaded && this.trafficStartTime && this.trafficTimer) {
|
|
if (this.videoLoaded && this.trafficStartTime && this.trafficTimer) {
|
|
|
const watchDuration = Math.floor((Date.now() - this.trafficStartTime) / 1000);
|
|
const watchDuration = Math.floor((Date.now() - this.trafficStartTime) / 1000);
|
|
@@ -1362,6 +1379,10 @@
|
|
|
onUnload() {
|
|
onUnload() {
|
|
|
// 保存视频进度
|
|
// 保存视频进度
|
|
|
this.saveVideoProgress();
|
|
this.saveVideoProgress();
|
|
|
|
|
+
|
|
|
|
|
+ // 停止观看时长统计
|
|
|
|
|
+ this.stopWatchDurationTracking();
|
|
|
|
|
+
|
|
|
// 用户退出时,再次请求一次10s流量接口
|
|
// 用户退出时,再次请求一次10s流量接口
|
|
|
if (this.videoLoaded && this.trafficStartTime) {
|
|
if (this.videoLoaded && this.trafficStartTime) {
|
|
|
const watchDuration = Math.floor((Date.now() - this.trafficStartTime) / 1000);
|
|
const watchDuration = Math.floor((Date.now() - this.trafficStartTime) / 1000);
|
|
@@ -1487,6 +1508,124 @@
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
methods: {
|
|
methods: {
|
|
|
|
|
+ // ======================== 观看时长统计相关方法 ========================
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 开始观看时长统计
|
|
|
|
|
+ * WebSocket连接成功后调用
|
|
|
|
|
+ */
|
|
|
|
|
+ startWatchDurationTracking() {
|
|
|
|
|
+ // 防止重复启动
|
|
|
|
|
+ if (this.watchStartTime > 0) {
|
|
|
|
|
+ console.log('观看时长统计已启动,跳过');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ this.watchStartTime = Date.now();
|
|
|
|
|
+ this.isPageVisible = true;
|
|
|
|
|
+ console.log('开始观看时长统计', new Date(this.watchStartTime).toLocaleString());
|
|
|
|
|
+
|
|
|
|
|
+ // 启动页面可见性监听
|
|
|
|
|
+ this.initPageVisibilityListener();
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取当前累计观看时长(秒)
|
|
|
|
|
+ * @returns {number} 观看时长(秒)
|
|
|
|
|
+ */
|
|
|
|
|
+ getCurrentWatchDuration() {
|
|
|
|
|
+ if (this.watchStartTime === 0) {
|
|
|
|
|
+ return this.accumulatedWatchDuration;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 如果页面可见,计算当前时段的时长
|
|
|
|
|
+ if (this.isPageVisible) {
|
|
|
|
|
+ const currentDuration = Math.floor((Date.now() - this.watchStartTime) / 1000);
|
|
|
|
|
+ return this.accumulatedWatchDuration + currentDuration;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 页面不可见,返回累计时长
|
|
|
|
|
+ return this.accumulatedWatchDuration;
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 暂停观看时长统计(页面隐藏/后台时)
|
|
|
|
|
+ */
|
|
|
|
|
+ pauseWatchDurationTracking() {
|
|
|
|
|
+ if (!this.isPageVisible || this.watchStartTime === 0) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 累加本次观看的时长
|
|
|
|
|
+ const currentSessionDuration = Math.floor((Date.now() - this.watchStartTime) / 1000);
|
|
|
|
|
+ this.accumulatedWatchDuration += currentSessionDuration;
|
|
|
|
|
+ this.lastPauseTime = Date.now();
|
|
|
|
|
+ this.isPageVisible = false;
|
|
|
|
|
+
|
|
|
|
|
+ console.log(`暂停观看统计: 本次=${currentSessionDuration}秒, 累计=${this.accumulatedWatchDuration}秒`);
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 恢复观看时长统计(页面显示/前台时)
|
|
|
|
|
+ */
|
|
|
|
|
+ resumeWatchDurationTracking() {
|
|
|
|
|
+ if (this.isPageVisible) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 重新记录开始时间
|
|
|
|
|
+ this.watchStartTime = Date.now();
|
|
|
|
|
+ this.isPageVisible = true;
|
|
|
|
|
+
|
|
|
|
|
+ const pauseDuration = this.lastPauseTime > 0 ? Math.floor((Date.now() - this.lastPauseTime) / 1000) : 0;
|
|
|
|
|
+ console.log(`恢夏观看统计: 暂停了${pauseDuration}秒, 当前累计=${this.accumulatedWatchDuration}秒`);
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 停止观看时长统计
|
|
|
|
|
+ */
|
|
|
|
|
+ stopWatchDurationTracking() {
|
|
|
|
|
+ if (this.isPageVisible && this.watchStartTime > 0) {
|
|
|
|
|
+ const currentSessionDuration = Math.floor((Date.now() - this.watchStartTime) / 1000);
|
|
|
|
|
+ this.accumulatedWatchDuration += currentSessionDuration;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ console.log(`停止观看统计: 总时长=${this.accumulatedWatchDuration}秒`);
|
|
|
|
|
+
|
|
|
|
|
+ // 重置状态
|
|
|
|
|
+ this.watchStartTime = 0;
|
|
|
|
|
+ this.isPageVisible = true;
|
|
|
|
|
+ this.lastPauseTime = 0;
|
|
|
|
|
+
|
|
|
|
|
+ // 移除监听
|
|
|
|
|
+ this.removePageVisibilityListener();
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 初始化页面可见性监听
|
|
|
|
|
+ */
|
|
|
|
|
+ initPageVisibilityListener() {
|
|
|
|
|
+ // uni-app 的页面生命周期已经在 onShow/onHide 中处理
|
|
|
|
|
+ // 这里主要监听小程序的前后台切换
|
|
|
|
|
+ uni.onAppShow(() => {
|
|
|
|
|
+ console.log('小程序回到前台');
|
|
|
|
|
+ this.resumeWatchDurationTracking();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ uni.onAppHide(() => {
|
|
|
|
|
+ console.log('小程序切换到后台');
|
|
|
|
|
+ this.pauseWatchDurationTracking();
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 移除页面可见性监听
|
|
|
|
|
+ */
|
|
|
|
|
+ removePageVisibilityListener() {
|
|
|
|
|
+ // uni-app 不需要手动移除监听,生命周期自动管理
|
|
|
|
|
+ console.log('页面可见性监听已移除');
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // ======================== 以下是原有方法 ========================
|
|
|
noticeHeightFun() {
|
|
noticeHeightFun() {
|
|
|
const query = uni.createSelectorQuery().in(this);
|
|
const query = uni.createSelectorQuery().in(this);
|
|
|
query.select('.notice-message').boundingClientRect(rect => {
|
|
query.select('.notice-message').boundingClientRect(rect => {
|
|
@@ -3031,7 +3170,7 @@
|
|
|
);
|
|
);
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
- //发送心跳
|
|
|
|
|
|
|
+ //发送心跳(包含观看时长)
|
|
|
sendHeartBeat() {
|
|
sendHeartBeat() {
|
|
|
if (!this.isSocketAvailable() || !this.isNetworkAvailable) {
|
|
if (!this.isSocketAvailable() || !this.isNetworkAvailable) {
|
|
|
console.warn('网络不可用或Socket连接异常,跳过心跳发送');
|
|
console.warn('网络不可用或Socket连接异常,跳过心跳发送');
|
|
@@ -3039,6 +3178,9 @@
|
|
|
}
|
|
}
|
|
|
this.lastHeartBeatTime = Date.now();
|
|
this.lastHeartBeatTime = Date.now();
|
|
|
|
|
|
|
|
|
|
+ // 计算当前累计观看时长
|
|
|
|
|
+ const currentWatchDuration = this.getCurrentWatchDuration();
|
|
|
|
|
+
|
|
|
try {
|
|
try {
|
|
|
const heartBeatMsg = JSON.stringify({
|
|
const heartBeatMsg = JSON.stringify({
|
|
|
cmd: 'heartbeat',
|
|
cmd: 'heartbeat',
|
|
@@ -3046,12 +3188,14 @@
|
|
|
userId: this.userInfo.userId || '',
|
|
userId: this.userInfo.userId || '',
|
|
|
liveId: this.liveId,
|
|
liveId: this.liveId,
|
|
|
timestamp: this.lastHeartBeatTime,
|
|
timestamp: this.lastHeartBeatTime,
|
|
|
- networkType: this.networkType
|
|
|
|
|
|
|
+ networkType: this.networkType,
|
|
|
|
|
+ data: String(currentWatchDuration)
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
this.socket.send({
|
|
this.socket.send({
|
|
|
data: heartBeatMsg,
|
|
data: heartBeatMsg,
|
|
|
success: () => {
|
|
success: () => {
|
|
|
|
|
+ console.log(`心跳发送成功: 观看时长=${currentWatchDuration}秒`);
|
|
|
this.heartBeatRetryCount = 0; // 成功后重置重试次数
|
|
this.heartBeatRetryCount = 0; // 成功后重置重试次数
|
|
|
this.adjustHeartBeatInterval(true); // 网络良好,可适当延长间隔
|
|
this.adjustHeartBeatInterval(true); // 网络良好,可适当延长间隔
|
|
|
this.startPingTimeout(); // 启动超时检测
|
|
this.startPingTimeout(); // 启动超时检测
|
|
@@ -4496,7 +4640,12 @@
|
|
|
} else {
|
|
} else {
|
|
|
console.log(`WebSocket重连成功(第${this.reconnectCount}次尝试)`);
|
|
console.log(`WebSocket重连成功(第${this.reconnectCount}次尝试)`);
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // 启动心跳
|
|
|
this.startHeartBeat();
|
|
this.startHeartBeat();
|
|
|
|
|
+
|
|
|
|
|
+ // 启动观看时长统计
|
|
|
|
|
+ this.startWatchDurationTracking();
|
|
|
|
|
|
|
|
});
|
|
});
|
|
|
|
|
|
|
@@ -4544,6 +4693,9 @@
|
|
|
this.isSocketOpen = false;
|
|
this.isSocketOpen = false;
|
|
|
this.isConnecting = false;
|
|
this.isConnecting = false;
|
|
|
this.stopHeartBeat(); // 清除心跳定时器
|
|
this.stopHeartBeat(); // 清除心跳定时器
|
|
|
|
|
+
|
|
|
|
|
+ // 暂停观看时长统计
|
|
|
|
|
+ this.pauseWatchDurationTracking();
|
|
|
|
|
|
|
|
// 根据关闭原因决定是否重连
|
|
// 根据关闭原因决定是否重连
|
|
|
if (!this.isManualClose) {
|
|
if (!this.isManualClose) {
|