diff --git a/src/components/VideoCallComponent.vue b/src/components/VideoCallComponent.vue index ef0a8fa..600bf41 100644 --- a/src/components/VideoCallComponent.vue +++ b/src/components/VideoCallComponent.vue @@ -35,45 +35,6 @@ - -
-
- - {{ friendlyMessage }} -
-
- - -
- -
- 未检测到摄像头 -
- - -
- 未检测到麦克风 -
- - -
- 摄像头未工作 -
- - -
- 麦克风未工作 -
- - -
- 对方未开启摄像头 -
-
-
- + @@ -274,6 +298,31 @@ const props = defineProps({ const emit = defineEmits(['end-call', 'accept-call', 'reject-call']); +// 时间戳日志函数 +const logWithTime = (level, message, ...args) => { + const timestamp = new Date().toLocaleTimeString('zh-CN', { + hour12: false, + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + fractionalSecondDigits: 3 + }); + + switch(level) { + case 'log': + console.log(`[${timestamp}] ${message}`, ...args); + break; + case 'error': + console.error(`[${timestamp}] ${message}`, ...args); + break; + case 'warn': + console.warn(`[${timestamp}] ${message}`, ...args); + break; + default: + console.log(`[${timestamp}] ${message}`, ...args); + } +}; + // 状态管理 const isVisible = ref(true); const isMinimized = ref(false); @@ -285,28 +334,31 @@ const callTimer = ref(null); const isDragging = ref(false); const isAccepting = ref(false); -// 友好提示相关 -const showFriendlyMessage = ref(false); -const friendlyMessage = ref(''); -const friendlyMessageTimer = ref(null); +// 结束通话提示相关状态 +const showEndingNotification = ref(false); +const endingCountdown = ref(3); +const endingTimer = ref(null); -// 拖拽相关状态 -const position = reactive({ x: 0, y: 0 }); -const startPosition = reactive({ x: 0, y: 0 }); -const container = ref(null); +// 视频重试相关状态 +const videoRetryCount = ref(0); +const maxVideoRetries = 3; +const videoRetryTimers = ref(new Map()); + +// 修复问题1: 调试相关状态 +const showDebugInfo = ref(false); +const videoCallbackCount = ref(0); // 元素引用 const localVideo = ref(null); const remoteVideo = ref(null); const remoteVideoPreview = ref(null); -// 新增:设备状态检测 -const deviceStatus = reactive({ - hasCamera: false, - hasMicrophone: false, - cameraWorking: false, - microphoneWorking: false, -}) +// 修复问题1: 视频回调取消注册函数 +const unregisterVideoCallback = ref(null); + +// 创建本地和远程流的引用 +const localStream = computed(() => webrtc.localStream?.value); +const remoteStream = computed(() => webrtc.remoteStream?.value); // 获取当前通话状态 const callStatus = computed(() => { @@ -331,6 +383,10 @@ const connectionText = computed(() => { return '网络连接已断开'; } + if (callStatus.value === "failed") { + return '连接失败,请检查网络'; + } + if (props.isIncoming && callStatus.value !== "ongoing") { return '来电中...'; } @@ -346,47 +402,246 @@ const connectionText = computed(() => { return '正在连接...'; }); -// 显示友好提示 -const showFriendlyNotification = (message, duration = 3000) => { - friendlyMessage.value = message; - showFriendlyMessage.value = true; - - if (friendlyMessageTimer.value) { - clearTimeout(friendlyMessageTimer.value); +// 修复问题1: 安全的视频元素更新函数 +const safeUpdateVideoElement = (videoElement, stream, type) => { + if (!videoElement) { + logWithTime('warn', `⚠️ ${type}视频元素不存在,跳过更新`); + return false; } - friendlyMessageTimer.value = setTimeout(() => { - showFriendlyMessage.value = false; - friendlyMessage.value = ''; - }, duration); + try { + logWithTime('log', `🔄 安全更新${type}视频元素`, { + hasStream: !!stream, + elementExists: !!videoElement, + currentSrc: !!videoElement.srcObject, + streamId: stream?.id + }); + + if (stream) { + // 检查流的有效性 + if (!stream.active) { + logWithTime('warn', `⚠️ ${type}流不是活跃状态`); + } + + const tracks = stream.getTracks(); + if (tracks.length === 0) { + logWithTime('warn', `⚠️ ${type}流没有轨道`); + return false; + } + + // 检查视频轨道 + const videoTracks = tracks.filter(t => t.kind === 'video'); + if (videoTracks.length === 0 && type !== 'local') { + logWithTime('warn', `⚠️ ${type}流没有视频轨道`); + } + + // 设置srcObject + if (videoElement.srcObject !== stream) { + videoElement.srcObject = stream; + logWithTime('log', `✅ ${type}视频srcObject已设置`); + + // 强制加载 + videoElement.load(); + + // 尝试播放 + videoElement.play().catch(error => { + logWithTime('error', `❌ ${type}视频播放失败:`, error); + // 启动重试 + retryVideoPlay(videoElement, type, 1); + }); + } + } else { + // 清理视频元素 + if (videoElement.srcObject) { + videoElement.srcObject = null; + logWithTime('log', `🔄 ${type}视频srcObject已清理`); + } + } + + return true; + } catch (error) { + logWithTime('error', `❌ 安全更新${type}视频元素失败:`, error); + return false; + } +}; + +// 修复问题1: 视频元素更新回调函数 +const handleVideoElementUpdate = (stream) => { + logWithTime('log', '📹 收到视频元素更新回调', { + hasStream: !!stream, + streamId: stream?.id, + hasMainVideo: !!remoteVideo.value, + hasPreviewVideo: !!remoteVideoPreview.value + }); + + // 更新主视频元素 + if (remoteVideo.value) { + safeUpdateVideoElement(remoteVideo.value, stream, 'main'); + } else { + logWithTime('warn', '⚠️ 主视频元素不存在'); + } + + // 更新预览视频元素 + if (remoteVideoPreview.value) { + safeUpdateVideoElement(remoteVideoPreview.value, stream, 'preview'); + } else { + logWithTime('warn', '⚠️ 预览视频元素不存在'); + } +}; + +// 修复问题3: 视频重试机制 +const retryVideoPlay = async (videoElement, type, attempt = 1) => { + if (attempt > maxVideoRetries) { + logWithTime('error', `❌ ${type}视频重试失败,已达到最大重试次数`); + videoRetryCount.value = 0; + return false; + } + + videoRetryCount.value = attempt; + logWithTime('log', `🔄 ${type}视频播放重试 ${attempt}/${maxVideoRetries}`); + + try { + // 等待递增的延迟时间 + await new Promise(resolve => setTimeout(resolve, 1000 * attempt)); + + if (!videoElement || !videoElement.srcObject) { + logWithTime('warn', `⚠️ ${type}视频元素或流已不存在,停止重试`); + return false; + } + + // 检查视频状态 + if (videoElement.readyState >= 2) { + await videoElement.play(); + logWithTime('log', `✅ ${type}视频重试播放成功`); + videoRetryCount.value = 0; + return true; + } else { + // 强制重新加载 + const currentStream = videoElement.srcObject; + videoElement.srcObject = null; + setTimeout(() => { + videoElement.srcObject = currentStream; + videoElement.load(); + }, 100); + + // 递归重试 + setTimeout(() => { + retryVideoPlay(videoElement, type, attempt + 1); + }, 1000); + } + } catch (error) { + logWithTime('error', `❌ ${type}视频重试播放失败:`, error); + + // 继续重试 + setTimeout(() => { + retryVideoPlay(videoElement, type, attempt + 1); + }, 1000 * attempt); + } +}; + +// 改进视频加载处理 +const handleVideoLoaded = async (event, type) => { + const video = event.target; + logWithTime('log', `📹 ${type} 视频元数据已加载`, { + readyState: video.readyState, + videoWidth: video.videoWidth, + videoHeight: video.videoHeight, + duration: video.duration, + paused: video.paused, + currentTime: video.currentTime + }); + + try { + if (video && video.readyState >= 2) { + await video.play(); + logWithTime('log', `✅ ${type} 视频开始播放`); + videoRetryCount.value = 0; // 重置重试计数 + + // 检查视频尺寸和轨道状态 + if (type === 'main' || type === 'preview') { + setTimeout(() => { + if (video.videoWidth === 0 && video.videoHeight === 0) { + logWithTime('warn', `⚠️ ${type} 视频尺寸为0,可能存在问题,尝试重新设置`); + const currentStream = video.srcObject; + if (currentStream) { + safeUpdateVideoElement(video, currentStream, type); + } + } else { + logWithTime('log', `✅ ${type} 视频尺寸正常: ${video.videoWidth}x${video.videoHeight}`); + } + }, 2000); + } + } + } catch (error) { + logWithTime('error', `❌ ${type} 视频播放失败:`, error); + + // 启动重试机制 + retryVideoPlay(video, type, 1); + } +}; + +const handleVideoLoadStart = (event, type) => { + const video = event.target; + logWithTime('log', `📹 ${type} 视频开始加载`); +}; + +const handleVideoWaiting = (event, type) => { + const video = event.target; + logWithTime('log', `📹 ${type} 视频等待数据`); +}; + +const handleVideoPlaying = (event, type) => { + const video = event.target; + logWithTime('log', `📹 ${type} 视频正在播放`); + videoRetryCount.value = 0; // 重置重试计数 +}; + +const handleVideoCanPlay = (event, type) => { + const video = event.target; + logWithTime('log', `📹 ${type} 视频可以播放`, { + readyState: video.readyState, + buffered: video.buffered.length + }); +}; + +const handleVideoError = (event, type) => { + const video = event.target; + logWithTime('error', `❌ ${type} 视频错误:`, { + error: video.error, + networkState: video.networkState, + readyState: video.readyState + }); + + // 视频错误时启动重试 + if (video.srcObject) { + retryVideoPlay(video, type, 1); + } }; // 通话计时器 const startCallTimer = () => { + logWithTime('log', "⏰ 开始通话计时"); if (callTimer.value) clearInterval(callTimer.value); callDuration.value = 0; callTimer.value = setInterval(() => { callDuration.value += 1; }, 1000); - console.log("⏰ 通话计时器已启动"); }; const stopCallTimer = () => { + logWithTime('log', "⏰ 停止通话计时"); if (callTimer.value) { clearInterval(callTimer.value); callTimer.value = null; - console.log("⏰ 通话计时器已停止"); } }; -// 格式化通话时间 const formattedDuration = computed(() => { const minutes = Math.floor(callDuration.value / 60); const seconds = callDuration.value % 60; return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; }); -// 计算容器样式 const containerStyle = computed(() => { const width = isMinimized.value ? (props.callType === 'audio' ? '250px' : '200px') : '500px'; const height = isMinimized.value ? '60px' : '380px'; @@ -399,41 +654,21 @@ const containerStyle = computed(() => { }; }); -// 初始化媒体设备 - 优化版本 +// 初始化媒体设备 const initializeMedia = async () => { try { - console.log("🎥 初始化媒体设备,当前状态:", callStatus.value); + logWithTime('log', "🎥 初始化媒体设备"); + const constraints = { + audio: true, + video: props.callType === 'video' + }; - if (['calling', 'connecting', 'ongoing'].includes(callStatus.value) || - (props.isIncoming && callStatus.value === 'ringing')) { - - const constraints = { - audio: true, - video: props.callType === 'video' - }; - - console.log("🎬 获取媒体约束:", constraints); - await webrtc.getLocalMedia(constraints); - - // 更新设备工作状态 - if (webrtc.localStream.value) { - deviceStatus.cameraWorking = webrtc.localStream.value.getVideoTracks().length > 0 && - webrtc.localStream.value.getVideoTracks().some(t => t.readyState === 'live'); - deviceStatus.microphoneWorking = webrtc.localStream.value.getAudioTracks().length > 0 && - webrtc.localStream.value.getAudioTracks().some(t => t.readyState === 'live'); - } - - console.log("✅ 媒体获取成功", { - camera: deviceStatus.cameraWorking, - microphone: deviceStatus.microphoneWorking - }); - - } else { - console.log("⏭️ 当前状态不需要获取媒体:", callStatus.value); - } + await webrtc.getLocalMedia(constraints); + logWithTime('log', "✅ 媒体设备初始化成功"); + return true; } catch (error) { - console.error('❌ 无法访问媒体设备:', error); - showFriendlyNotification('媒体设备访问失败,请检查权限设置', 3000); + logWithTime('error', '❌ 无法访问媒体设备:', error); + return false; } }; @@ -441,29 +676,25 @@ const initializeMedia = async () => { const toggleMic = () => { isMicOn.value = !isMicOn.value; webrtc.toggleMicrophone(isMicOn.value); + logWithTime('log', `🎤 麦克风${isMicOn.value ? '开启' : '关闭'}`); }; -const toggleCamera = async () => { +const toggleCamera = () => { if (props.callType === 'video') { isCameraOn.value = !isCameraOn.value; webrtc.toggleCamera(isCameraOn.value); + logWithTime('log', `📹 摄像头${isCameraOn.value ? '开启' : '关闭'}`); } }; const toggleSpeaker = () => { isSpeakerOn.value = !isSpeakerOn.value; - if (remoteVideo.value) { - remoteVideo.value.muted = !isSpeakerOn.value; - } - if (remoteVideoPreview.value) { - remoteVideoPreview.value.muted = !isSpeakerOn.value; - } - console.log("🔊 扬声器状态:", isSpeakerOn.value); + logWithTime('log', `🔊 扬声器${isSpeakerOn.value ? '开启' : '关闭'}`); }; const toggleMinimize = () => { isMinimized.value = !isMinimized.value; - console.log("📐 切换最小化状态:", isMinimized.value); + logWithTime('log', `${isMinimized.value ? '最小化' : '展开'}通话窗口`); const width = isMinimized.value ? (props.callType === 'audio' ? 250 : 200) : 500; const height = isMinimized.value ? 60 : 380; @@ -472,67 +703,108 @@ const toggleMinimize = () => { position.y = Math.max(0, Math.min(position.y, window.innerHeight - height)); }; -// 修复1:确保在结束通话前关闭WebRTC连接 +// 修复问题1: 调试信息切换 +const toggleDebugInfo = () => { + showDebugInfo.value = !showDebugInfo.value; + logWithTime('log', `🐛 调试信息${showDebugInfo.value ? '开启' : '关闭'}`); +}; + +// 结束通话逻辑 const endCall = () => { - // 先关闭WebRTC连接 + logWithTime('log', "📞 开始结束通话流程"); + + // 清理视频重试定时器 + videoRetryTimers.value.forEach(timer => clearTimeout(timer)); + videoRetryTimers.value.clear(); + videoRetryCount.value = 0; + + // 取消注册视频回调 + if (unregisterVideoCallback.value) { + unregisterVideoCallback.value(); + unregisterVideoCallback.value = null; + } + + // 立即释放媒体资源 + webrtc.releaseMediaResources(); + + // 显示结束提示 + showEndingNotification.value = true; + endingCountdown.value = 3; + + // 开始倒计时 + endingTimer.value = setInterval(() => { + endingCountdown.value--; + if (endingCountdown.value <= 0) { + clearInterval(endingTimer.value); + endingTimer.value = null; + performEndCall(); + } + }, 1000); + + // 关闭WebRTC连接 webrtc.closeConnection(); - - // 再停止计时器和更新UI stopCallTimer(); - isVisible.value = false; - - // 更新状态 chatStore.endCall(); +}; + +const performEndCall = () => { + logWithTime('log', "📞 执行结束通话操作"); + showEndingNotification.value = false; + isVisible.value = false; emit('end-call'); }; // 接听通话 const acceptCall = async () => { - if (isAccepting.value || ['connecting', 'ongoing'].includes(chatStore.callStatus)) { - console.log("⚠️ 已在处理接听或通话中,忽略重复点击") - return; - } + if (isAccepting.value) return; - console.log("📞 用户点击接听按钮") + logWithTime('log', "📞 开始接听通话"); isAccepting.value = true; try { - // 先获取媒体流 - await initializeMedia(); - - // 然后接听通话 - await chatStore.acceptCall(); - console.log("✅ 接听请求已发送") - showFriendlyNotification("正在接听通话...", 2000); + const mediaSuccess = await initializeMedia(); + if (mediaSuccess) { + await chatStore.acceptCall(); + emit('accept-call'); + logWithTime('log', "✅ 通话接听成功"); + } else { + logWithTime('error', "❌ 媒体设备初始化失败,无法接听"); + } } catch (error) { - console.error("❌ 接听失败:", error) + logWithTime('error', "❌ 接听失败:", error); } finally { setTimeout(() => { isAccepting.value = false; }, 3000); } - - emit('accept-call'); }; // 拒绝通话 const rejectCall = () => { - if (chatStore.callStatus === "idle" || isAccepting.value) { - console.log("⚠️ 通话已结束或正在接听,忽略拒绝操作") - return; + logWithTime('log', "📞 拒绝通话"); + + // 清理视频重试定时器 + videoRetryTimers.value.forEach(timer => clearTimeout(timer)); + videoRetryTimers.value.clear(); + videoRetryCount.value = 0; + + // 取消注册视频回调 + if (unregisterVideoCallback.value) { + unregisterVideoCallback.value(); + unregisterVideoCallback.value = null; } - console.log("❌ 用户点击拒绝按钮") - - // 修复2:拒绝通话时也关闭WebRTC连接 - webrtc.closeConnection(); - + webrtc.forceCleanup(); isVisible.value = false; chatStore.rejectCall(); emit('reject-call'); }; -// 拖拽功能 +// 拖拽相关 +const position = reactive({ x: 0, y: 0 }); +const startPosition = reactive({ x: 0, y: 0 }); +const container = ref(null); + const startDrag = (event) => { if (event.target.closest('.action-btn') || event.target.closest('.control-btn') || @@ -541,7 +813,6 @@ const startDrag = (event) => { } isDragging.value = true; - startPosition.x = event.clientX; startPosition.y = event.clientY; @@ -574,7 +845,6 @@ const stopDrag = () => { document.removeEventListener('mouseup', stopDrag); }; -// 窗口大小变化处理 const handleWindowResize = () => { const width = isMinimized.value ? (props.callType === 'audio' ? 250 : 200) : 500; const height = isMinimized.value ? 60 : 380; @@ -583,86 +853,63 @@ const handleWindowResize = () => { position.y = Math.max(0, Math.min(position.y, window.innerHeight - height)); }; -// 远端视频轨道检测 -const hasRemoteVideo = computed(() => { - if (!webrtc.remoteStream.value) return false - const tracks = webrtc.remoteStream.value.getVideoTracks() - return tracks.length > 0 && tracks.some(t => t.readyState === 'live') -}) +// 修复问题4: 监听ICE连接失败事件 +const handleIceConnectionFailed = (event) => { + const { callId, peerId } = event.detail; + logWithTime('log', '🧊 收到ICE连接失败事件,尝试重新协商'); -// 监听本地流变化 - 优化版本 -watch(() => webrtc.localStream.value, (stream, oldStream) => { - console.log('🔄 本地流变化:', { newStream: !!stream, oldStream: !!oldStream }); + // 通知chat store重新发送offer + if (chatStore.currentCall?.value?.callId === callId) { + setTimeout(() => { + chatStore.handleCallAccepted({ + call_id: callId, + sender_user_id: peerId + }); + }, 1000); + } +}; - if (stream) { - // 更新设备工作状态 - const videoTracks = stream.getVideoTracks(); - const audioTracks = stream.getAudioTracks(); +// 监听本地流变化 +watch(localStream, (newStream) => { + logWithTime('log', "📹 本地流状态变化", { + hasStream: !!newStream, + tracks: newStream?.getTracks().map(t => `${t.kind}:${t.id}`) + }); - deviceStatus.cameraWorking = videoTracks.length > 0 && - videoTracks.some(t => t.readyState === 'live'); - - deviceStatus.microphoneWorking = audioTracks.length > 0 && - audioTracks.some(t => t.readyState === 'live'); - - console.log('📱 设备状态更新:', { - camera: deviceStatus.cameraWorking, - microphone: deviceStatus.microphoneWorking, - videoTracks: videoTracks.length, - audioTracks: audioTracks.length + if (newStream && localVideo.value) { + nextTick(() => { + if (localVideo.value.srcObject !== newStream) { + logWithTime('log', "📹 更新本地视频"); + localVideo.value.srcObject = newStream; + } }); - } else { - deviceStatus.cameraWorking = false; - deviceStatus.microphoneWorking = false; - console.log('📱 设备状态重置: 无媒体流'); } -}, { immediate: true }) +}); -// 初始化设备检测 -const checkDevices = async () => { - try { - const devices = await navigator.mediaDevices.enumerateDevices() - deviceStatus.hasCamera = devices.some(d => d.kind === 'videoinput' && d.deviceId !== '') - deviceStatus.hasMicrophone = devices.some(d => d.kind === 'audioinput' && d.deviceId !== '') - console.log("📷 设备检测结果:", deviceStatus) - } catch (error) { - console.error("❌ 设备检测失败:", error) - } -} - -// 监听通话状态 - 优化版本 +// 监听通话状态变化 watch(() => chatStore.callStatus, async (status, oldStatus) => { - console.log("📞 VideoCallComponent 监听到通话状态变化:", oldStatus, "->", status); + logWithTime('log', "📞 通话状态变化:", { from: oldStatus, to: status }); try { switch (status) { case "calling": case "connecting": - console.log("📞 发起通话,初始化媒体设备") + logWithTime('log', "🔄 准备通话,初始化媒体"); await initializeMedia(); - if (status === "connecting") { - showFriendlyNotification("正在建立连接...", 2000); - } break; case "ongoing": - // 确保媒体流正常 - if (!webrtc.localStream.value) { - console.warn('⚠️ 通话进行中但无本地流,重新初始化'); - await initializeMedia(); - } + logWithTime('log', "✅ 通话进行中"); if (!callTimer.value) { - console.log("⏰ 开始通话计时") startCallTimer(); } - showFriendlyNotification("通话已连接", 2000); break; case "ended": case "idle": - console.log("📞 通话结束,准备关闭组件"); + logWithTime('log', "📞 通话结束,释放资源"); + webrtc.releaseMediaResources(); stopCallTimer(); - // 延迟关闭以显示状态信息 setTimeout(() => { if (chatStore.callStatus === status) { endCall(); @@ -671,8 +918,8 @@ watch(() => chatStore.callStatus, async (status, oldStatus) => { break; case "failed": - console.log("❌ 通话失败") - showFriendlyNotification("连接失败", 3000); + logWithTime('log', "❌ 通话失败,释放资源"); + webrtc.releaseMediaResources(); setTimeout(() => { if (chatStore.callStatus === status) { endCall(); @@ -681,8 +928,8 @@ watch(() => chatStore.callStatus, async (status, oldStatus) => { break; case "hangup": - console.log("📞 对方已挂断,3秒后关闭组件") - showFriendlyNotification("对方已挂断", 3000); + logWithTime('log', "📞 对方挂断,释放资源"); + webrtc.releaseMediaResources(); setTimeout(() => { if (chatStore.callStatus === status) { endCall(); @@ -691,8 +938,8 @@ watch(() => chatStore.callStatus, async (status, oldStatus) => { break; case "disconnected": - console.log("🌐 网络连接已断开,3秒后关闭组件") - showFriendlyNotification("网络连接已断开", 3000); + logWithTime('log', "🔌 连接断开,释放资源"); + webrtc.releaseMediaResources(); setTimeout(() => { if (chatStore.callStatus === status) { endCall(); @@ -701,85 +948,89 @@ watch(() => chatStore.callStatus, async (status, oldStatus) => { break; } } catch (error) { - console.error('❌ 处理通话状态变化失败:', error); - } -}); - -// 监听连接状态变化以显示友好提示 -watch(() => chatStore.callConnectionStatus, (newStatus, oldStatus) => { - if (newStatus && newStatus !== oldStatus) { - if (newStatus.includes("对方已接听")) { - showFriendlyNotification("对方已接听", 2000); - } else if (newStatus.includes("对方已拒绝")) { - showFriendlyNotification("对方已拒绝", 3000); - } else if (newStatus.includes("对方已挂断")) { - showFriendlyNotification("对方已挂断", 3000); - } else if (newStatus.includes("通话已连接")) { - showFriendlyNotification("通话已连接", 2000); - } + logWithTime('error', '❌ 处理通话状态变化失败:', error); + webrtc.forceCleanup(); } }); onMounted(async () => { - console.log('🚀 VideoCallComponent 组件挂载'); + logWithTime('log', "🚀 VideoCallComponent 组件挂载"); - try { - // 初始化组件位置 - position.x = Math.max(0, (window.innerWidth - 500) / 2); - position.y = Math.max(0, (window.innerHeight - 380) / 2); - console.log('📍 组件位置初始化:', position); + position.x = Math.max(0, (window.innerWidth - 500) / 2); + position.y = Math.max(0, (window.innerHeight - 380) / 2); - // 添加窗口大小变化监听 - window.addEventListener('resize', handleWindowResize); + window.addEventListener('resize', handleWindowResize); - // 初始化设备检测 - await checkDevices(); + // 修复问题4: 监听ICE连接失败事件 + window.addEventListener('iceConnectionFailed', handleIceConnectionFailed); - console.log("🚀 VideoCallComponent mounted, callStatus:", chatStore.callStatus, "isIncoming:", props.isIncoming); + // 修复问题1: 注册视频元素更新回调 + unregisterVideoCallback.value = webrtc.registerVideoCallback(handleVideoElementUpdate); - // 如果当前状态需要媒体,立即初始化 - if (['calling', 'connecting', 'ongoing'].includes(chatStore.callStatus)) { - console.log('🎥 组件挂载时初始化媒体'); - await initializeMedia(); + // 更新回调计数 + videoCallbackCount.value = webrtc.videoElementCallbacks?.value?.size || 0; + + const handleBeforeUnload = () => { + logWithTime('log', "🔄 页面即将卸载,释放WebRTC资源"); + + // 清理视频重试定时器 + videoRetryTimers.value.forEach(timer => clearTimeout(timer)); + videoRetryTimers.value.clear(); + + // 取消注册视频回调 + if (unregisterVideoCallback.value) { + unregisterVideoCallback.value(); + unregisterVideoCallback.value = null; } - console.log('✅ VideoCallComponent 组件挂载完成'); - } catch (error) { - console.error('❌ VideoCallComponent 组件挂载失败:', error); - showFriendlyNotification('组件初始化失败,请刷新页面重试', 3000); + webrtc.forceCleanup(); + }; + + window.addEventListener('beforeunload', handleBeforeUnload); + + onUnmounted(() => { + window.removeEventListener('beforeunload', handleBeforeUnload); + window.removeEventListener('iceConnectionFailed', handleIceConnectionFailed); + }); + + if (['calling', 'connecting', 'ongoing'].includes(chatStore.callStatus)) { + logWithTime('log', "🎥 当前状态需要媒体,立即初始化"); + await initializeMedia(); } }); onUnmounted(() => { - console.log('🔄 VideoCallComponent 组件卸载'); + logWithTime('log', "🔄 VideoCallComponent 组件卸载"); - try { - // 修复3:确保组件卸载时关闭WebRTC连接 - webrtc.closeConnection(); - - // 停止计时器 - stopCallTimer(); - if (friendlyMessageTimer.value) { - clearTimeout(friendlyMessageTimer.value); - friendlyMessageTimer.value = null; - } - console.log('⏲ 计时器已清除'); - - // 清除事件监听 - document.removeEventListener('mousemove', handleDrag); - document.removeEventListener('mouseup', stopDrag); - window.removeEventListener('resize', handleWindowResize); - console.log('🎧 事件监听器已清除'); - - console.log('✅ VideoCallComponent 组件卸载完成'); - } catch (error) { - console.error('❌ VideoCallComponent 组件卸载时出错:', error); + if (endingTimer.value) { + clearInterval(endingTimer.value); + endingTimer.value = null; } + + // 清理视频重试定时器 + videoRetryTimers.value.forEach(timer => clearTimeout(timer)); + videoRetryTimers.value.clear(); + videoRetryCount.value = 0; + + // 取消注册视频回调 + if (unregisterVideoCallback.value) { + unregisterVideoCallback.value(); + unregisterVideoCallback.value = null; + } + + webrtc.forceCleanup(); + stopCallTimer(); + + document.removeEventListener('mousemove', handleDrag); + document.removeEventListener('mouseup', stopDrag); + window.removeEventListener('resize', handleWindowResize); + + logWithTime('log', "✅ 组件卸载完成,所有资源已释放"); });