+
{{ callerInfo.name.charAt(0) }}
@@ -150,7 +122,7 @@
{{ formattedDuration }}
+
+
+
+
+
+
+
+
视频加载中... ({{ videoRetryCount }}/{{ maxVideoRetries }})
+
+
+
+
+
远程流: {{ remoteStream ? '✅' : '❌' }}
+
主视频元素: {{ remoteVideo ? '✅' : '❌' }}
+
预览视频元素: {{ remoteVideoPreview ? '✅' : '❌' }}
+
回调数量: {{ videoCallbackCount }}
+
+
流ID: {{ remoteStream.id }}
+
轨道数: {{ remoteStream.getTracks().length }}
+
+ {{ track.kind }}: {{ track.readyState }} - {{ track.enabled ? '启用' : '禁用' }}
+
+
+
-
-
-
-
- 对方忙线中
-
-
-
-
-
+
+
+
+ 对方忙线中
+
-
+
+
+
+
+
-
+
+
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
通话已结束
+
{{ endingCountdown }}秒后关闭
+
+
@@ -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', "✅ 组件卸载完成,所有资源已释放");
});