修复:

1. 本地视频流能获取到了
2. 找到了视频连接错误的原因:挂断后本地麦克风和视频并没有关闭

现有问题:
1. 需要在挂断后关闭麦克风和摄像头
2. 没有渲染远程流推送的视频
This commit is contained in:
2025-07-14 11:17:41 +08:00
parent 355a26051d
commit 32e676e320
4 changed files with 383 additions and 125 deletions

View File

@@ -4,7 +4,7 @@
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite --host",
"build": "vite build", "build": "vite build",
"preview": "vite preview" "preview": "vite preview"
}, },

View File

@@ -399,7 +399,7 @@ const containerStyle = computed(() => {
}; };
}); });
// 初始化媒体设备 // 初始化媒体设备 - 优化版本
const initializeMedia = async () => { const initializeMedia = async () => {
try { try {
console.log("🎥 初始化媒体设备,当前状态:", callStatus.value); console.log("🎥 初始化媒体设备,当前状态:", callStatus.value);
@@ -414,13 +414,26 @@ const initializeMedia = async () => {
console.log("🎬 获取媒体约束:", constraints); console.log("🎬 获取媒体约束:", constraints);
await webrtc.getLocalMedia(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 { } else {
console.log("⏭️ 当前状态不需要获取媒体:", callStatus.value); console.log("⏭️ 当前状态不需要获取媒体:", callStatus.value);
} }
} catch (error) { } 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)); position.y = Math.max(0, Math.min(position.y, window.innerHeight - height));
}; };
// 修复1确保在结束通话前关闭WebRTC连接
const endCall = () => { const endCall = () => {
// 先关闭WebRTC连接
webrtc.closeConnection();
// 再停止计时器和更新UI
stopCallTimer(); stopCallTimer();
isVisible.value = false; isVisible.value = false;
// 更新状态
chatStore.endCall(); chatStore.endCall();
emit('end-call'); emit('end-call');
}; };
@@ -503,6 +523,10 @@ const rejectCall = () => {
} }
console.log("❌ 用户点击拒绝按钮") console.log("❌ 用户点击拒绝按钮")
// 修复2拒绝通话时也关闭WebRTC连接
webrtc.closeConnection();
isVisible.value = false; isVisible.value = false;
chatStore.rejectCall(); chatStore.rejectCall();
emit('reject-call'); emit('reject-call');
@@ -566,17 +590,33 @@ const hasRemoteVideo = computed(() => {
return tracks.length > 0 && tracks.some(t => t.readyState === 'live') return tracks.length > 0 && tracks.some(t => t.readyState === 'live')
}) })
// 监听本地流变化 // 监听本地流变化 - 优化版本
watch(() => webrtc.localStream.value, (stream) => { watch(() => webrtc.localStream.value, (stream, oldStream) => {
if (!stream) return console.log('🔄 本地流变化:', { newStream: !!stream, oldStream: !!oldStream });
// 更新设备工作状态 if (stream) {
deviceStatus.cameraWorking = stream.getVideoTracks().length > 0 && // 更新设备工作状态
stream.getVideoTracks().some(t => t.readyState === 'live') const videoTracks = stream.getVideoTracks();
const audioTracks = stream.getAudioTracks();
deviceStatus.microphoneWorking = stream.getAudioTracks().length > 0 && deviceStatus.cameraWorking = videoTracks.length > 0 &&
stream.getAudioTracks().some(t => t.readyState === 'live') 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 () => { 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); console.log("📞 VideoCallComponent 监听到通话状态变化:", oldStatus, "->", status);
if (status === "calling") { try {
console.log("📞 发起通话,初始化媒体设备") switch (status) {
initializeMedia(); case "calling":
} else if (status === "ongoing") { case "connecting":
if (!callTimer.value) { console.log("📞 发起通话,初始化媒体设备")
console.log("⏰ 开始通话计时") await initializeMedia();
startCallTimer(); 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); } catch (error) {
} else if (status === "ended" || status === "idle") { console.error('❌ 处理通话状态变化失败:', error);
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);
} }
}); });
@@ -648,35 +720,61 @@ watch(() => chatStore.callConnectionStatus, (newStatus, oldStatus) => {
} }
}); });
onMounted(() => { onMounted(async () => {
position.x = Math.max(0, (window.innerWidth - 500) / 2); console.log('🚀 VideoCallComponent 组件挂载');
position.y = Math.max(0, (window.innerHeight - 380) / 2);
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)) { console.log("🚀 VideoCallComponent mounted, callStatus:", chatStore.callStatus, "isIncoming:", props.isIncoming);
initializeMedia();
// 如果当前状态需要媒体,立即初始化
if (['calling', 'connecting', 'ongoing'].includes(chatStore.callStatus)) {
console.log('🎥 组件挂载时初始化媒体');
await initializeMedia();
}
console.log('✅ VideoCallComponent 组件挂载完成');
} catch (error) {
console.error('❌ VideoCallComponent 组件挂载失败:', error);
showFriendlyNotification('组件初始化失败,请刷新页面重试', 3000);
} }
}); });
onUnmounted(() => { onUnmounted(() => {
console.log("🔚 VideoCallComponent unmounted"); console.log('🔄 VideoCallComponent 组件卸载');
stopCallTimer(); try {
if (friendlyMessageTimer.value) { // 修复3确保组件卸载时关闭WebRTC连接
clearTimeout(friendlyMessageTimer.value); 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();
}); });
</script> </script>
@@ -1162,7 +1260,7 @@ onUnmounted(() => {
justify-content: center; justify-content: center;
font-size: 14px; font-size: 14px;
cursor: pointer; cursor: pointer;
border: none; border: null;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
} }

View File

@@ -9,14 +9,16 @@ export function useWebRTC() {
const peerConnection = ref(null) const peerConnection = ref(null)
const isConnected = ref(false) const isConnected = ref(false)
const isConnecting = ref(false) const isConnecting = ref(false)
const mediaStreamManager = ref(null) // 媒体流管理器
const lastError = ref(null) // 最后一个错误
const userStore = useUserStore() const userStore = useUserStore()
const chatStore = useChatStore() const chatStore = useChatStore()
const configuration = { const configuration = {
iceServers: [ iceServers: [
{ urls: "stun:stun.l.google.com:19302" }, {urls: "stun:stun.l.google.com:19302"},
{ urls: "stun:stun1.l.google.com:19302" } {urls: "stun:stun1.l.google.com:19302"}
], ],
iceTransportPolicy: "all", iceTransportPolicy: "all",
reconnectPolicy: { reconnectPolicy: {
@@ -30,16 +32,37 @@ export function useWebRTC() {
let reconnectTimer = null let reconnectTimer = null
let currentCallId = null let currentCallId = null
let currentPeerId = null let currentPeerId = null
let isGettingMedia = false // 防止重复获取媒体流
// 收集ICE候选的队列 // 收集ICE候选的队列
const iceCandidateQueue = ref([]) 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 { try {
console.log("🎥 开始获取本地媒体流:", constraints) console.log("🎥 开始获取本地媒体流:", constraints)
// 检测设备可用性 // 检测设备可用性和权限
let finalConstraints = {...constraints}
try { try {
const devices = await navigator.mediaDevices.enumerateDevices() const devices = await navigator.mediaDevices.enumerateDevices()
const hasVideo = devices.some(d => d.kind === 'videoinput' && d.deviceId !== '') const hasVideo = devices.some(d => d.kind === 'videoinput' && d.deviceId !== '')
@@ -47,24 +70,53 @@ export function useWebRTC() {
if (constraints.video && !hasVideo) { if (constraints.video && !hasVideo) {
console.warn("❌ 摄像头设备不可用") console.warn("❌ 摄像头设备不可用")
constraints.video = false finalConstraints.video = false
} }
if (constraints.audio && !hasAudio) { if (constraints.audio && !hasAudio) {
console.warn("❌ 麦克风设备不可用") 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) { } catch (deviceError) {
console.warn("设备检测失败,继续尝试获取媒体:", deviceError) console.warn("设备检测失败,继续尝试获取媒体:", deviceError)
} }
// 如果没有可用设备,抛出错误
if (!finalConstraints.audio && !finalConstraints.video) {
throw new Error('没有可用的音视频设备或权限被拒绝')
}
// 如果已有流,先停止 // 如果已有流,先停止
if (localStream.value) { 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 localStream.value = null
} }
const stream = await navigator.mediaDevices.getUserMedia(constraints) const stream = await navigator.mediaDevices.getUserMedia(finalConstraints)
localStream.value = stream localStream.value = stream
mediaStreamManager.value = finalConstraints
console.log("✅ 本地媒体流获取成功:", { console.log("✅ 本地媒体流获取成功:", {
streamId: stream.id, streamId: stream.id,
@@ -72,32 +124,63 @@ export function useWebRTC() {
audioTracks: stream.getAudioTracks().length, audioTracks: stream.getAudioTracks().length,
videoEnabled: stream.getVideoTracks()[0]?.enabled, videoEnabled: stream.getVideoTracks()[0]?.enabled,
audioEnabled: stream.getAudioTracks()[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 return stream
} catch (error) { } catch (error) {
console.error("❌ 获取本地媒体流失败:", error) console.error("❌ 获取本地媒体流失败:", error)
lastError.value = error
// 如果视频失败,尝试只获取音频 // 如果视频失败,尝试只获取音频
if (constraints.video) { if (constraints.video && !constraints.audio === false) {
try { try {
console.log("🎵 尝试仅获取音频流") 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 localStream.value = audioStream
mediaStreamManager.value = {audio: true, video: false}
console.log("✅ 音频流获取成功") console.log("✅ 音频流获取成功")
return audioStream return audioStream
} catch (audioError) { } catch (audioError) {
console.error("❌ 音频流也获取失败:", audioError) console.error("❌ 音频流也获取失败:", audioError)
lastError.value = audioError
} }
} }
throw error throw error
} finally {
isGettingMedia = false
} }
} }
// 创建PeerConnection // 创建PeerConnection
const createPeerConnection = (callId, peerId) => { const createPeerConnection = (callId, peerId) => {
console.log("🔗 创建PeerConnection:", { callId, peerId }) console.log("🔗 创建PeerConnection:", {callId, peerId})
// 保存当前通话信息用于重连 // 保存当前通话信息用于重连
currentCallId = callId currentCallId = callId
@@ -141,24 +224,37 @@ export function useWebRTC() {
// 接收远程流 - 修复轨道处理逻辑 // 接收远程流 - 修复轨道处理逻辑
pc.ontrack = (event) => { 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) { if (!remoteStream.value) {
console.log("🆕 创建新的远程流") console.log("🆕 创建新的远程流");
remoteStream.value = new MediaStream() 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) { if (!existingTrack) {
remoteStream.value.addTrack(event.track) remoteStream.value.addTrack(event.track);
console.log("✅ 添加远程轨道成功,轨道数:", remoteStream.value.getTracks().length) console.log("✅ 添加远程轨道成功,轨道数:", remoteStream.value.getTracks().length);
console.log("✅ 添加远程轨道成功:", remoteStream.value)
} else { } 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 = () => { pc.onconnectionstatechange = () => {
@@ -185,7 +281,7 @@ export function useWebRTC() {
} }
} }
// ICE连接状态变化 // ICE连接状态变化 - 优化版本
pc.oniceconnectionstatechange = () => { pc.oniceconnectionstatechange = () => {
const state = pc.iceConnectionState const state = pc.iceConnectionState
console.log("❄️ ICE连接状态:", state) console.log("❄️ ICE连接状态:", state)
@@ -196,8 +292,17 @@ export function useWebRTC() {
console.warn(`⚠️ 连接失败,尝试重连 (${reconnectAttempts}/${configuration.reconnectPolicy.maxAttempts})`) console.warn(`⚠️ 连接失败,尝试重连 (${reconnectAttempts}/${configuration.reconnectPolicy.maxAttempts})`)
chatStore.callConnectionStatus = `连接失败,正在尝试重连 (${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) }, configuration.reconnectPolicy.delay)
} else { } else {
console.error("❌ 重连次数已达上限,放弃连接") console.error("❌ 重连次数已达上限,放弃连接")
@@ -227,7 +332,7 @@ export function useWebRTC() {
// 确保只添加活跃的轨道 // 确保只添加活跃的轨道
if (track.readyState === 'live') { if (track.readyState === 'live') {
pc.addTrack(track, localStream.value) pc.addTrack(track, localStream.value)
console.log(`✅ 添加 ${track.kind} 轨道到连接`) console.log(`✅ 添加本地 ${track.kind} 轨道到连接`)
} }
}) })
} }
@@ -235,27 +340,58 @@ export function useWebRTC() {
return pc return pc
} }
// 重启连接 // 重启连接 - 优化版本
const restartConnection = () => { const restartConnection = async () => {
if (!currentCallId || !currentPeerId) { if (!currentCallId || !currentPeerId) {
console.error("❌ 无法重启连接: 缺少callId或peerId") console.error("❌ 无法重启连接: 缺少callId或peerId")
return return
} }
console.log("🔄 尝试重启WebRTC连接") console.log("🔄 尝试重启WebRTC连接")
closeConnection()
createPeerConnection(currentCallId, currentPeerId)
if (chatStore.isCaller) { try {
createOffer(currentCallId, currentPeerId).then(offer => { // 先关闭现有连接但保留媒体流
// 重新发送offer逻辑 if (peerConnection.value) {
callAPI.sendOffer(userStore.currentUser?.id, currentPeerId, currentCallId, offer, 1) peerConnection.value.close()
console.log("📤 重新发送Offer") peerConnection.value = null
}) }
} else {
// 被叫方请求对方重新发送offer // 清空候选队列
console.log("📤 请求对方重新发送Offer") iceCandidateQueue.value = []
callAPI.requestResendOffer(currentCallId, currentPeerId)
// 重新获取媒体流(如果需要)
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设置成功") console.log("✅ 远程Answer设置成功")
} catch (error) { } catch (error) {
console.error("❌ 设置远程Answer失败:", error) console.error("❌ 设置远程Answer失败:", error)
console.error("❌ 设置远程Answer失败-对象:", peerConnection.value)
throw error throw error
} }
} }
@@ -359,27 +496,32 @@ export function useWebRTC() {
} }
} }
// 关闭连接 - 彻底释放资源 // 关闭连接 - 彻底释放资源 - 优化版本
const closeConnection = () => { const closeConnection = (keepLocalStream = false) => {
console.log("🔄 关闭WebRTC连接") console.log("🔄 关闭WebRTC连接", keepLocalStream ? "(保留本地流)" : "(完全关闭)")
// 停止所有媒体轨道并释放设备 // 完全重置状态
const fullReset = !keepLocalStream;
// 停止所有媒体轨道
if (localStream.value) { if (localStream.value) {
console.log("📹 停止本地流轨道") console.log("📹 停止本地流轨道")
localStream.value.getTracks().forEach(track => { localStream.value.getTracks().forEach(track => {
console.log(`⏹ 停止轨道: ${track.kind} (${track.id})`) console.log(`⏹ 停止轨道: ${track.kind} (${track.id})`)
track.stop() // 停止轨道以释放设备 track.stop() // 确保释放设备
track.enabled = false track.enabled = false
}) })
localStream.value = null if (fullReset) {
localStream.value = null
mediaStreamManager.value = null
}
} }
// 清除远程流 // 清除远程流
if (remoteStream.value) { if (remoteStream.value) {
console.log("📹 清除远程流") console.log("📹 清除远程流")
remoteStream.value.getTracks().forEach(track => { remoteStream.value.getTracks().forEach(track => {
console.log(`⏹ 停止远程轨道: ${track.kind} (${track.id})`) remoteStream.value.removeTrack(track)
track.stop()
}) })
remoteStream.value = null remoteStream.value = null
} }
@@ -387,7 +529,20 @@ export function useWebRTC() {
// 关闭对等连接 // 关闭对等连接
if (peerConnection.value) { if (peerConnection.value) {
console.log("🔌 关闭对等连接") 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 peerConnection.value = null
} }
@@ -401,12 +556,15 @@ export function useWebRTC() {
// 重置状态 // 重置状态
isConnected.value = false isConnected.value = false
isConnecting.value = false isConnecting.value = false
reconnectAttempts = 0
currentCallId = null
currentPeerId = null
// 清空候选队列 if (fullReset) {
iceCandidateQueue.value = [] reconnectAttempts = 0
currentCallId = null
currentPeerId = null
isGettingMedia = false
lastError.value = null
iceCandidateQueue.value = [] // 清空候选队列
}
} }
return { return {

View File

@@ -33,6 +33,7 @@ export const useChatStore = defineStore("chat", () => {
const isDisconnected = computed(() => callStatus.value === "disconnected") const isDisconnected = computed(() => callStatus.value === "disconnected")
const isHangup = computed(() => callStatus.value === "hangup") const isHangup = computed(() => callStatus.value === "hangup")
// 监听通话状态变化 // 监听通话状态变化
watch( watch(
() => callStatus.value, () => callStatus.value,
@@ -920,6 +921,7 @@ export const useChatStore = defineStore("chat", () => {
// 接听来电 // 接听来电
const acceptCall = async () => { const acceptCall = async () => {
// if (isConnecting) return;
if (!incomingCall.value) { if (!incomingCall.value) {
console.log("没有来电,无法接听") console.log("没有来电,无法接听")
return return