From 32e676e320fa65b4e63c70c525c9d4644c95556a Mon Sep 17 00:00:00 2001 From: liqi Date: Mon, 14 Jul 2025 11:17:41 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=EF=BC=9A=201.=20=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E8=A7=86=E9=A2=91=E6=B5=81=E8=83=BD=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E5=88=B0=E4=BA=86=202.=20=E6=89=BE=E5=88=B0=E4=BA=86=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E8=BF=9E=E6=8E=A5=E9=94=99=E8=AF=AF=E7=9A=84=E5=8E=9F?= =?UTF-8?q?=E5=9B=A0=EF=BC=9A=E6=8C=82=E6=96=AD=E5=90=8E=E6=9C=AC=E5=9C=B0?= =?UTF-8?q?=E9=BA=A6=E5=85=8B=E9=A3=8E=E5=92=8C=E8=A7=86=E9=A2=91=E5=B9=B6?= =?UTF-8?q?=E6=B2=A1=E6=9C=89=E5=85=B3=E9=97=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 现有问题: 1. 需要在挂断后关闭麦克风和摄像头 2. 没有渲染远程流推送的视频 --- package.json | 2 +- src/components/VideoCallComponent.vue | 236 ++++++++++++++++------- src/composables/useWebRTC.js | 268 ++++++++++++++++++++------ src/stores/chat.js | 2 + 4 files changed, 383 insertions(+), 125 deletions(-) diff --git a/package.json b/package.json index b32b9ec..32e7d01 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite", + "dev": "vite --host", "build": "vite build", "preview": "vite preview" }, diff --git a/src/components/VideoCallComponent.vue b/src/components/VideoCallComponent.vue index 8a445c2..ef0a8fa 100644 --- a/src/components/VideoCallComponent.vue +++ b/src/components/VideoCallComponent.vue @@ -399,7 +399,7 @@ const containerStyle = computed(() => { }; }); -// 初始化媒体设备 +// 初始化媒体设备 - 优化版本 const initializeMedia = async () => { try { console.log("🎥 初始化媒体设备,当前状态:", callStatus.value); @@ -414,13 +414,26 @@ const initializeMedia = async () => { console.log("🎬 获取媒体约束:", constraints); await webrtc.getLocalMedia(constraints); - console.log("✅ 媒体获取成功"); + + // 更新设备工作状态 + 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); } } catch (error) { - console.warn('⚠️ 无法访问媒体设备:', error); + console.error('❌ 无法访问媒体设备:', error); + showFriendlyNotification('媒体设备访问失败,请检查权限设置', 3000); } }; @@ -459,9 +472,16 @@ const toggleMinimize = () => { position.y = Math.max(0, Math.min(position.y, window.innerHeight - height)); }; +// 修复1:确保在结束通话前关闭WebRTC连接 const endCall = () => { + // 先关闭WebRTC连接 + webrtc.closeConnection(); + + // 再停止计时器和更新UI stopCallTimer(); isVisible.value = false; + + // 更新状态 chatStore.endCall(); emit('end-call'); }; @@ -503,6 +523,10 @@ const rejectCall = () => { } console.log("❌ 用户点击拒绝按钮") + + // 修复2:拒绝通话时也关闭WebRTC连接 + webrtc.closeConnection(); + isVisible.value = false; chatStore.rejectCall(); emit('reject-call'); @@ -566,17 +590,33 @@ const hasRemoteVideo = computed(() => { return tracks.length > 0 && tracks.some(t => t.readyState === 'live') }) -// 监听本地流变化 -watch(() => webrtc.localStream.value, (stream) => { - if (!stream) return +// 监听本地流变化 - 优化版本 +watch(() => webrtc.localStream.value, (stream, oldStream) => { + console.log('🔄 本地流变化:', { newStream: !!stream, oldStream: !!oldStream }); - // 更新设备工作状态 - deviceStatus.cameraWorking = stream.getVideoTracks().length > 0 && - stream.getVideoTracks().some(t => t.readyState === 'live') + if (stream) { + // 更新设备工作状态 + const videoTracks = stream.getVideoTracks(); + const audioTracks = stream.getAudioTracks(); - deviceStatus.microphoneWorking = stream.getAudioTracks().length > 0 && - stream.getAudioTracks().some(t => t.readyState === 'live') -}) + 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 + }); + } else { + deviceStatus.cameraWorking = false; + deviceStatus.microphoneWorking = false; + console.log('📱 设备状态重置: 无媒体流'); + } +}, { immediate: true }) // 初始化设备检测 const checkDevices = async () => { @@ -590,46 +630,78 @@ const checkDevices = async () => { } } -// 监听通话状态 -watch(() => chatStore.callStatus, (status, oldStatus) => { +// 监听通话状态 - 优化版本 +watch(() => chatStore.callStatus, async (status, oldStatus) => { console.log("📞 VideoCallComponent 监听到通话状态变化:", oldStatus, "->", status); - if (status === "calling") { - console.log("📞 发起通话,初始化媒体设备") - initializeMedia(); - } else if (status === "ongoing") { - if (!callTimer.value) { - console.log("⏰ 开始通话计时") - startCallTimer(); + try { + switch (status) { + case "calling": + case "connecting": + console.log("📞 发起通话,初始化媒体设备") + await initializeMedia(); + if (status === "connecting") { + showFriendlyNotification("正在建立连接...", 2000); + } + break; + + case "ongoing": + // 确保媒体流正常 + if (!webrtc.localStream.value) { + console.warn('⚠️ 通话进行中但无本地流,重新初始化'); + await initializeMedia(); + } + if (!callTimer.value) { + console.log("⏰ 开始通话计时") + startCallTimer(); + } + showFriendlyNotification("通话已连接", 2000); + break; + + case "ended": + case "idle": + console.log("📞 通话结束,准备关闭组件"); + stopCallTimer(); + // 延迟关闭以显示状态信息 + setTimeout(() => { + if (chatStore.callStatus === status) { + endCall(); + } + }, 1000); + break; + + case "failed": + console.log("❌ 通话失败") + showFriendlyNotification("连接失败", 3000); + setTimeout(() => { + if (chatStore.callStatus === status) { + endCall(); + } + }, 3000); + break; + + case "hangup": + console.log("📞 对方已挂断,3秒后关闭组件") + showFriendlyNotification("对方已挂断", 3000); + setTimeout(() => { + if (chatStore.callStatus === status) { + endCall(); + } + }, 3000); + break; + + case "disconnected": + console.log("🌐 网络连接已断开,3秒后关闭组件") + showFriendlyNotification("网络连接已断开", 3000); + setTimeout(() => { + if (chatStore.callStatus === status) { + endCall(); + } + }, 3000); + break; } - showFriendlyNotification("通话已连接", 2000); - } else if (status === "ended" || status === "idle") { - console.log("📞 通话结束,准备关闭组件"); - stopCallTimer(); - setTimeout(() => { - endCall(); - }, 100); - } else if (status === "connecting") { - console.log("🔗 通话连接中") - showFriendlyNotification("正在建立连接...", 2000); - } else if (status === "failed") { - console.log("❌ 通话失败") - showFriendlyNotification("连接失败", 3000); - setTimeout(() => { - endCall(); - }, 3000); - } else if (status === "hangup") { - console.log("📞 对方已挂断,3秒后关闭组件") - showFriendlyNotification("对方已挂断", 3000); - setTimeout(() => { - endCall(); - }, 3000); - } else if (status === "disconnected") { - console.log("🌐 网络连接已断开,3秒后关闭组件") - showFriendlyNotification("网络连接已断开", 3000); - setTimeout(() => { - endCall(); - }, 3000); + } catch (error) { + console.error('❌ 处理通话状态变化失败:', error); } }); @@ -648,35 +720,61 @@ watch(() => chatStore.callConnectionStatus, (newStatus, oldStatus) => { } }); -onMounted(() => { - position.x = Math.max(0, (window.innerWidth - 500) / 2); - position.y = Math.max(0, (window.innerHeight - 380) / 2); +onMounted(async () => { + console.log('🚀 VideoCallComponent 组件挂载'); - window.addEventListener('resize', handleWindowResize); + try { + // 初始化组件位置 + position.x = Math.max(0, (window.innerWidth - 500) / 2); + position.y = Math.max(0, (window.innerHeight - 380) / 2); + console.log('📍 组件位置初始化:', position); - // 初始化设备检测 - checkDevices(); + // 添加窗口大小变化监听 + window.addEventListener('resize', handleWindowResize); - console.log("🚀 VideoCallComponent mounted, callStatus:", chatStore.callStatus, "isIncoming:", props.isIncoming); + // 初始化设备检测 + await checkDevices(); - if (['calling', 'connecting', 'ongoing'].includes(chatStore.callStatus)) { - initializeMedia(); + console.log("🚀 VideoCallComponent mounted, callStatus:", chatStore.callStatus, "isIncoming:", props.isIncoming); + + // 如果当前状态需要媒体,立即初始化 + if (['calling', 'connecting', 'ongoing'].includes(chatStore.callStatus)) { + console.log('🎥 组件挂载时初始化媒体'); + await initializeMedia(); + } + + console.log('✅ VideoCallComponent 组件挂载完成'); + } catch (error) { + console.error('❌ VideoCallComponent 组件挂载失败:', error); + showFriendlyNotification('组件初始化失败,请刷新页面重试', 3000); } }); onUnmounted(() => { - console.log("🔚 VideoCallComponent unmounted"); + console.log('🔄 VideoCallComponent 组件卸载'); - stopCallTimer(); - if (friendlyMessageTimer.value) { - clearTimeout(friendlyMessageTimer.value); + 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); } - - document.removeEventListener('mousemove', handleDrag); - document.removeEventListener('mouseup', stopDrag); - window.removeEventListener('resize', handleWindowResize); - - webrtc.closeConnection(); }); @@ -1162,7 +1260,7 @@ onUnmounted(() => { justify-content: center; font-size: 14px; cursor: pointer; - border: none; + border: null; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); } diff --git a/src/composables/useWebRTC.js b/src/composables/useWebRTC.js index a199f70..55d66c5 100644 --- a/src/composables/useWebRTC.js +++ b/src/composables/useWebRTC.js @@ -9,14 +9,16 @@ export function useWebRTC() { const peerConnection = ref(null) const isConnected = ref(false) const isConnecting = ref(false) + const mediaStreamManager = ref(null) // 媒体流管理器 + const lastError = ref(null) // 最后一个错误 const userStore = useUserStore() const chatStore = useChatStore() const configuration = { iceServers: [ - { urls: "stun:stun.l.google.com:19302" }, - { urls: "stun:stun1.l.google.com:19302" } + {urls: "stun:stun.l.google.com:19302"}, + {urls: "stun:stun1.l.google.com:19302"} ], iceTransportPolicy: "all", reconnectPolicy: { @@ -30,16 +32,37 @@ export function useWebRTC() { let reconnectTimer = null let currentCallId = null let currentPeerId = null + let isGettingMedia = false // 防止重复获取媒体流 // 收集ICE候选的队列 const iceCandidateQueue = ref([]) - // 获取本地媒体流 - const getLocalMedia = async (constraints = { audio: true, video: true }) => { + // 获取本地媒体流 - 优化版本 + const getLocalMedia = async (constraints = {audio: true, video: true}) => { + // 防止重复获取 + if (isGettingMedia) { + console.log("⏳ 正在获取媒体流,跳过重复请求") + return localStream.value + } + + // 如果已有相同约束的流,直接返回 + if (localStream.value && mediaStreamManager.value) { + const currentConstraints = mediaStreamManager.value + if (currentConstraints.audio === constraints.audio && + currentConstraints.video === constraints.video) { + console.log("✅ 使用现有媒体流") + return localStream.value + } + } + + isGettingMedia = true + lastError.value = null + try { console.log("🎥 开始获取本地媒体流:", constraints) - // 检测设备可用性 + // 检测设备可用性和权限 + let finalConstraints = {...constraints} try { const devices = await navigator.mediaDevices.enumerateDevices() const hasVideo = devices.some(d => d.kind === 'videoinput' && d.deviceId !== '') @@ -47,24 +70,53 @@ export function useWebRTC() { if (constraints.video && !hasVideo) { console.warn("❌ 摄像头设备不可用") - constraints.video = false + finalConstraints.video = false } if (constraints.audio && !hasAudio) { console.warn("❌ 麦克风设备不可用") - constraints.audio = false + finalConstraints.audio = false + } + + // 检查权限状态 + if (navigator.permissions) { + try { + const cameraPermission = await navigator.permissions.query({name: 'camera'}) + const micPermission = await navigator.permissions.query({name: 'microphone'}) + + if (finalConstraints.video && cameraPermission.state === 'denied') { + console.warn("❌ 摄像头权限被拒绝") + finalConstraints.video = false + } + if (finalConstraints.audio && micPermission.state === 'denied') { + console.warn("❌ 麦克风权限被拒绝") + finalConstraints.audio = false + } + } catch (permError) { + console.warn("权限检查失败:", permError) + } } } catch (deviceError) { console.warn("设备检测失败,继续尝试获取媒体:", deviceError) } + // 如果没有可用设备,抛出错误 + if (!finalConstraints.audio && !finalConstraints.video) { + throw new Error('没有可用的音视频设备或权限被拒绝') + } + // 如果已有流,先停止 if (localStream.value) { - localStream.value.getTracks().forEach((track) => track.stop()) + console.log("🔄 停止现有媒体流") + localStream.value.getTracks().forEach((track) => { + track.stop() + console.log(`⏹ 停止轨道: ${track.kind}`) + }) localStream.value = null } - const stream = await navigator.mediaDevices.getUserMedia(constraints) + const stream = await navigator.mediaDevices.getUserMedia(finalConstraints) localStream.value = stream + mediaStreamManager.value = finalConstraints console.log("✅ 本地媒体流获取成功:", { streamId: stream.id, @@ -72,32 +124,63 @@ export function useWebRTC() { audioTracks: stream.getAudioTracks().length, videoEnabled: stream.getVideoTracks()[0]?.enabled, audioEnabled: stream.getAudioTracks()[0]?.enabled, + constraints: finalConstraints }) + // 如果PeerConnection已存在,添加新的轨道 + if (peerConnection.value && peerConnection.value.connectionState !== 'closed') { + console.log("➕ 将新获取的本地流添加到现有PeerConnection") + stream.getTracks().forEach((track) => { + if (track.readyState === 'live') { + // 检查是否已经添加了相同类型的轨道 + const existingSender = peerConnection.value.getSenders().find(sender => + sender.track && sender.track.kind === track.kind + ) + + if (existingSender) { + // 替换现有轨道 + console.log(`🔄 替换现有 ${track.kind} 轨道`) + existingSender.replaceTrack(track).catch(error => { + console.error(`❌ 替换 ${track.kind} 轨道失败:`, error) + }) + } else { + // 添加新轨道 + peerConnection.value.addTrack(track, stream) + console.log(`✅ 添加新的 ${track.kind} 轨道到PeerConnection`) + } + } + }) + } + return stream } catch (error) { console.error("❌ 获取本地媒体流失败:", error) + lastError.value = error // 如果视频失败,尝试只获取音频 - if (constraints.video) { + if (constraints.video && !constraints.audio === false) { try { console.log("🎵 尝试仅获取音频流") - const audioStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false }) + const audioStream = await navigator.mediaDevices.getUserMedia({audio: true, video: false}) localStream.value = audioStream + mediaStreamManager.value = {audio: true, video: false} console.log("✅ 音频流获取成功") return audioStream } catch (audioError) { console.error("❌ 音频流也获取失败:", audioError) + lastError.value = audioError } } throw error + } finally { + isGettingMedia = false } } // 创建PeerConnection const createPeerConnection = (callId, peerId) => { - console.log("🔗 创建PeerConnection:", { callId, peerId }) + console.log("🔗 创建PeerConnection:", {callId, peerId}) // 保存当前通话信息用于重连 currentCallId = callId @@ -141,24 +224,37 @@ export function useWebRTC() { // 接收远程流 - 修复轨道处理逻辑 pc.ontrack = (event) => { - console.log("📹 接收到远程轨道:", event.track.kind, "id:", event.track.id) + console.log("📹 接收到远程轨道:", event.track.kind, "id:", event.track.id); - // 如果还没有远程流,创建一个新的MediaStream + // 确保流对象存在 if (!remoteStream.value) { - console.log("🆕 创建新的远程流") - remoteStream.value = new MediaStream() + console.log("🆕 创建新的远程流"); + remoteStream.value = new MediaStream(); } - // 确保轨道尚未添加 - const existingTrack = remoteStream.value.getTracks().find(t => t.id === event.track.id) + // 检查轨道是否已存在 + const existingTrack = remoteStream.value.getTracks().find(t => t.id === event.track.id); + if (!existingTrack) { - remoteStream.value.addTrack(event.track) - console.log("✅ 添加远程轨道成功,轨道数:", remoteStream.value.getTracks().length) - console.log("✅ 添加远程轨道成功:", remoteStream.value) + remoteStream.value.addTrack(event.track); + console.log("✅ 添加远程轨道成功,轨道数:", remoteStream.value.getTracks().length); } else { - console.log("⏭️ 轨道已存在,跳过添加") + console.log("⏭️ 轨道已存在,跳过添加"); } - } + }; + + // 添加 onnegotiationneeded 和重启逻辑 + pc.onnegotiationneeded = async () => { + try { + // 这里添加你的信令交换逻辑(offer/answer) + console.log("🚀 重新协商连接..."); + } catch (err) { + console.error("协商失败:", err); + // 重启轨道 + remoteStream.value.getTracks().forEach(track => track.stop()); + remoteStream.value = null; + } + }; // 连接状态变化 pc.onconnectionstatechange = () => { @@ -185,7 +281,7 @@ export function useWebRTC() { } } - // ICE连接状态变化 + // ICE连接状态变化 - 优化版本 pc.oniceconnectionstatechange = () => { const state = pc.iceConnectionState console.log("❄️ ICE连接状态:", state) @@ -196,8 +292,17 @@ export function useWebRTC() { console.warn(`⚠️ 连接失败,尝试重连 (${reconnectAttempts}/${configuration.reconnectPolicy.maxAttempts})`) chatStore.callConnectionStatus = `连接失败,正在尝试重连 (${reconnectAttempts}/${configuration.reconnectPolicy.maxAttempts})` - reconnectTimer = setTimeout(() => { - restartConnection() + // 清除之前的重连计时器 + if (reconnectTimer) { + clearTimeout(reconnectTimer) + } + + reconnectTimer = setTimeout(async () => { + try { + await restartConnection() + } catch (error) { + console.error("❌ 自动重连失败:", error) + } }, configuration.reconnectPolicy.delay) } else { console.error("❌ 重连次数已达上限,放弃连接") @@ -227,7 +332,7 @@ export function useWebRTC() { // 确保只添加活跃的轨道 if (track.readyState === 'live') { pc.addTrack(track, localStream.value) - console.log(`✅ 添加 ${track.kind} 轨道到连接`) + console.log(`✅ 添加本地 ${track.kind} 轨道到连接`) } }) } @@ -235,27 +340,58 @@ export function useWebRTC() { return pc } - // 重启连接 - const restartConnection = () => { + // 重启连接 - 优化版本 + const restartConnection = async () => { if (!currentCallId || !currentPeerId) { console.error("❌ 无法重启连接: 缺少callId或peerId") return } console.log("🔄 尝试重启WebRTC连接") - closeConnection() - createPeerConnection(currentCallId, currentPeerId) - if (chatStore.isCaller) { - createOffer(currentCallId, currentPeerId).then(offer => { - // 重新发送offer逻辑 - callAPI.sendOffer(userStore.currentUser?.id, currentPeerId, currentCallId, offer, 1) - console.log("📤 重新发送Offer") - }) - } else { - // 被叫方请求对方重新发送offer - console.log("📤 请求对方重新发送Offer") - callAPI.requestResendOffer(currentCallId, currentPeerId) + try { + // 先关闭现有连接但保留媒体流 + if (peerConnection.value) { + peerConnection.value.close() + peerConnection.value = null + } + + // 清空候选队列 + iceCandidateQueue.value = [] + + // 重新获取媒体流(如果需要) + if (!localStream.value || localStream.value.getTracks().length === 0) { + console.log("🎥 重连时重新获取媒体流") + const constraints = mediaStreamManager.value || {audio: true, video: true} + await getLocalMedia(constraints) + } + + // 创建新的连接 + createPeerConnection(currentCallId, currentPeerId) + + if (chatStore.isCaller) { + try { + const offer = await createOffer(currentCallId, currentPeerId) + await callAPI.sendOffer(userStore.currentUser?.id, currentPeerId, currentCallId, offer, 1) + console.log("📤 重新发送Offer成功") + } catch (error) { + console.error("❌ 重新发送Offer失败:", error) + throw error + } + } else { + // 被叫方请求对方重新发送offer + console.log("📤 请求对方重新发送Offer") + try { + await callAPI.requestResendOffer(currentCallId, currentPeerId) + } catch (error) { + console.error("❌ 请求重新发送Offer失败:", error) + } + } + } catch (error) { + console.error("❌ 重启连接失败:", error) + // 如果重连失败,更新状态 + chatStore.callStatus = "failed" + chatStore.callConnectionStatus = "重连失败,请稍后再试" } } @@ -312,6 +448,7 @@ export function useWebRTC() { console.log("✅ 远程Answer设置成功") } catch (error) { console.error("❌ 设置远程Answer失败:", error) + console.error("❌ 设置远程Answer失败-对象:", peerConnection.value) throw error } } @@ -359,27 +496,32 @@ export function useWebRTC() { } } - // 关闭连接 - 彻底释放资源 - const closeConnection = () => { - console.log("🔄 关闭WebRTC连接") + // 关闭连接 - 彻底释放资源 - 优化版本 + const closeConnection = (keepLocalStream = false) => { + console.log("🔄 关闭WebRTC连接", keepLocalStream ? "(保留本地流)" : "(完全关闭)") - // 停止所有媒体轨道并释放设备 + // 完全重置状态 + const fullReset = !keepLocalStream; + + // 停止所有媒体轨道 if (localStream.value) { console.log("📹 停止本地流轨道") localStream.value.getTracks().forEach(track => { console.log(`⏹ 停止轨道: ${track.kind} (${track.id})`) - track.stop() // 停止轨道以释放设备 + track.stop() // 确保释放设备 track.enabled = false }) - localStream.value = null + if (fullReset) { + localStream.value = null + mediaStreamManager.value = null + } } // 清除远程流 if (remoteStream.value) { console.log("📹 清除远程流") remoteStream.value.getTracks().forEach(track => { - console.log(`⏹ 停止远程轨道: ${track.kind} (${track.id})`) - track.stop() + remoteStream.value.removeTrack(track) }) remoteStream.value = null } @@ -387,7 +529,20 @@ export function useWebRTC() { // 关闭对等连接 if (peerConnection.value) { console.log("🔌 关闭对等连接") - peerConnection.value.close() + try { + // 移除所有事件监听器 + pc.onicecandidate = null; + pc.ontrack = null; + pc.onnegotiationneeded = null; + pc.onconnectionstatechange = null; + pc.oniceconnectionstatechange = null; + pc.onicegatheringstatechange = null; + pc.onsignalingstatechange = null; + + peerConnection.value.close() + } catch (error) { + console.warn("关闭连接时出错:", error) + } peerConnection.value = null } @@ -401,12 +556,15 @@ export function useWebRTC() { // 重置状态 isConnected.value = false isConnecting.value = false - reconnectAttempts = 0 - currentCallId = null - currentPeerId = null - // 清空候选队列 - iceCandidateQueue.value = [] + if (fullReset) { + reconnectAttempts = 0 + currentCallId = null + currentPeerId = null + isGettingMedia = false + lastError.value = null + iceCandidateQueue.value = [] // 清空候选队列 + } } return { diff --git a/src/stores/chat.js b/src/stores/chat.js index f025537..277f392 100644 --- a/src/stores/chat.js +++ b/src/stores/chat.js @@ -33,6 +33,7 @@ export const useChatStore = defineStore("chat", () => { const isDisconnected = computed(() => callStatus.value === "disconnected") const isHangup = computed(() => callStatus.value === "hangup") + // 监听通话状态变化 watch( () => callStatus.value, @@ -920,6 +921,7 @@ export const useChatStore = defineStore("chat", () => { // 接听来电 const acceptCall = async () => { + // if (isConnecting) return; if (!incomingCall.value) { console.log("没有来电,无法接听") return