import { ref } from "vue" import { useChatStore } from "@/stores/chat" import { useUserStore } from "@/stores/user" import { callAPI } from "@/utils/request.js" export function useWebRTC() { const localStream = ref(null) const remoteStream = ref(null) const peerConnection = ref(null) const isConnected = ref(false) const isConnecting = ref(false) const userStore = useUserStore() const chatStore = useChatStore() const configuration = { iceServers: [ { urls: "stun:stun.l.google.com:19302" }, { urls: "stun:stun1.l.google.com:19302" } ], iceTransportPolicy: "all", reconnectPolicy: { maxAttempts: 3, // 最大重连次数 delay: 2000 // 重连延迟(ms) } } // 重连相关变量 let reconnectAttempts = 0 let reconnectTimer = null let currentCallId = null let currentPeerId = null // 收集ICE候选的队列 const iceCandidateQueue = ref([]) // 获取本地媒体流 const getLocalMedia = async (constraints = { audio: true, video: true }) => { try { console.log("🎥 开始获取本地媒体流:", constraints) // 检测设备可用性 try { const devices = await navigator.mediaDevices.enumerateDevices() const hasVideo = devices.some(d => d.kind === 'videoinput' && d.deviceId !== '') const hasAudio = devices.some(d => d.kind === 'audioinput' && d.deviceId !== '') if (constraints.video && !hasVideo) { console.warn("❌ 摄像头设备不可用") constraints.video = false } if (constraints.audio && !hasAudio) { console.warn("❌ 麦克风设备不可用") constraints.audio = false } } catch (deviceError) { console.warn("设备检测失败,继续尝试获取媒体:", deviceError) } // 如果已有流,先停止 if (localStream.value) { localStream.value.getTracks().forEach((track) => track.stop()) localStream.value = null } const stream = await navigator.mediaDevices.getUserMedia(constraints) localStream.value = stream console.log("✅ 本地媒体流获取成功:", { streamId: stream.id, videoTracks: stream.getVideoTracks().length, audioTracks: stream.getAudioTracks().length, videoEnabled: stream.getVideoTracks()[0]?.enabled, audioEnabled: stream.getAudioTracks()[0]?.enabled, }) return stream } catch (error) { console.error("❌ 获取本地媒体流失败:", error) // 如果视频失败,尝试只获取音频 if (constraints.video) { try { console.log("🎵 尝试仅获取音频流") const audioStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false }) localStream.value = audioStream console.log("✅ 音频流获取成功") return audioStream } catch (audioError) { console.error("❌ 音频流也获取失败:", audioError) } } throw error } } // 创建PeerConnection const createPeerConnection = (callId, peerId) => { console.log("🔗 创建PeerConnection:", { callId, peerId }) // 保存当前通话信息用于重连 currentCallId = callId currentPeerId = peerId if (peerConnection.value) { console.log("🔄 关闭现有连接") peerConnection.value.close() } const pc = new RTCPeerConnection(configuration) peerConnection.value = pc // 处理ICE候选队列 const processCandidateQueue = async () => { console.log(`🔄 处理候选队列 (${iceCandidateQueue.value.length} 项)`) while (iceCandidateQueue.value.length > 0) { const candidate = iceCandidateQueue.value.shift() try { await pc.addIceCandidate(candidate) console.log("✅ 从队列添加ICE候选成功") } catch (error) { console.error("❌ 添加队列ICE候选失败:", error) } } } // ICE候选事件 pc.onicecandidate = async (event) => { if (event.candidate) { console.log("📡 发送ICE候选:", event.candidate) try { await callAPI.sendCandidate(userStore.currentUser?.id, peerId, callId, event.candidate, 6) } catch (error) { console.error("❌ 发送ICE候选失败:", error) } } else { console.log("❄️ ICE候选收集完成") } } // 接收远程流 - 修复轨道处理逻辑 pc.ontrack = (event) => { console.log("📹 接收到远程轨道:", event.track.kind, "id:", event.track.id) // 如果还没有远程流,创建一个新的MediaStream if (!remoteStream.value) { console.log("🆕 创建新的远程流") remoteStream.value = new MediaStream() } // 确保轨道尚未添加 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) } else { console.log("⏭️ 轨道已存在,跳过添加") } } // 连接状态变化 pc.onconnectionstatechange = () => { const state = pc.connectionState console.log("🔗 连接状态:", state) if (state === "connected") { isConnected.value = true isConnecting.value = false reconnectAttempts = 0 // 重置重连计数器 clearTimeout(reconnectTimer) if (chatStore.callStatus === "connecting") { chatStore.callStatus = "ongoing" chatStore.callConnectionStatus = "通话已连接" } // 连接成功后处理候选队列 processCandidateQueue() } else if (state === "connecting") { isConnecting.value = true } else if (state === "disconnected" || state === "failed") { isConnected.value = false isConnecting.value = false } } // ICE连接状态变化 pc.oniceconnectionstatechange = () => { const state = pc.iceConnectionState console.log("❄️ ICE连接状态:", state) if (state === "failed") { if (reconnectAttempts < configuration.reconnectPolicy.maxAttempts) { reconnectAttempts++ console.warn(`⚠️ 连接失败,尝试重连 (${reconnectAttempts}/${configuration.reconnectPolicy.maxAttempts})`) chatStore.callConnectionStatus = `连接失败,正在尝试重连 (${reconnectAttempts}/${configuration.reconnectPolicy.maxAttempts})` reconnectTimer = setTimeout(() => { restartConnection() }, configuration.reconnectPolicy.delay) } else { console.error("❌ 重连次数已达上限,放弃连接") chatStore.callStatus = "disconnected" chatStore.callConnectionStatus = "连接失败,请稍后再试" } } else if (state === "connected") { reconnectAttempts = 0 clearTimeout(reconnectTimer) } } // ICE收集状态变化 pc.onicegatheringstatechange = () => { console.log("❄️ ICE收集状态:", pc.iceGatheringState) } // 信令状态变化 pc.onsignalingstatechange = () => { console.log("📶 信令状态:", pc.signalingState) } // 添加本地流到连接 if (localStream.value) { console.log("➕ 添加本地流到PeerConnection") localStream.value.getTracks().forEach((track) => { // 确保只添加活跃的轨道 if (track.readyState === 'live') { pc.addTrack(track, localStream.value) console.log(`✅ 添加 ${track.kind} 轨道到连接`) } }) } return pc } // 重启连接 const restartConnection = () => { 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) } } // 创建Offer const createOffer = async (callId, peerId) => { console.log("📤 创建Offer") if (!peerConnection.value) { createPeerConnection(callId, peerId) } try { const offer = await peerConnection.value.createOffer() await peerConnection.value.setLocalDescription(offer) console.log("✅ Offer创建成功:", offer.type) console.log("📋 SDP:", offer.sdp.substring(0, 100) + "...") return offer } catch (error) { console.error("❌ 创建Offer失败:", error) throw error } } // 创建Answer const createAnswer = async (offer, callId, peerId) => { console.log("📥 处理Offer并创建Answer") if (!peerConnection.value) { createPeerConnection(callId, peerId) } try { await peerConnection.value.setRemoteDescription(offer) const answer = await peerConnection.value.createAnswer() await peerConnection.value.setLocalDescription(answer) console.log("✅ Answer创建成功:", answer.type) console.log("📋 SDP:", answer.sdp.substring(0, 100) + "...") return answer } catch (error) { console.error("❌ 创建Answer失败:", error) throw error } } // 设置远程Answer const setRemoteAnswer = async (answer) => { console.log("📥 设置远程Answer") try { await peerConnection.value.setRemoteDescription(answer) console.log("✅ 远程Answer设置成功") } catch (error) { console.error("❌ 设置远程Answer失败:", error) throw error } } // 添加ICE候选 const addIceCandidate = async (candidate) => { if (!peerConnection.value) { console.log("⏳ 连接尚未建立,将候选加入队列") iceCandidateQueue.value.push(candidate) return } if (peerConnection.value.remoteDescription) { try { await peerConnection.value.addIceCandidate(candidate) console.log("✅ ICE候选添加成功") } catch (error) { console.error("❌ 添加ICE候选失败:", error) } } else { console.log("⏳ 等待远程描述,将候选加入队列") iceCandidateQueue.value.push(candidate) } } // 切换摄像头 const toggleCamera = async (enable) => { if (!localStream.value) return const videoTrack = localStream.value.getVideoTracks()[0] if (videoTrack) { videoTrack.enabled = enable console.log("📹 摄像头状态:", enable ? "开启" : "关闭") } } // 切换麦克风 const toggleMicrophone = async (enable) => { if (!localStream.value) return const audioTrack = localStream.value.getAudioTracks()[0] if (audioTrack) { audioTrack.enabled = enable console.log("🎤 麦克风状态:", enable ? "开启" : "关闭") } } // 关闭连接 - 彻底释放资源 const closeConnection = () => { console.log("🔄 关闭WebRTC连接") // 停止所有媒体轨道并释放设备 if (localStream.value) { console.log("📹 停止本地流轨道") localStream.value.getTracks().forEach(track => { console.log(`⏹ 停止轨道: ${track.kind} (${track.id})`) track.stop() // 停止轨道以释放设备 track.enabled = false }) localStream.value = null } // 清除远程流 if (remoteStream.value) { console.log("📹 清除远程流") remoteStream.value.getTracks().forEach(track => { console.log(`⏹ 停止远程轨道: ${track.kind} (${track.id})`) track.stop() }) remoteStream.value = null } // 关闭对等连接 if (peerConnection.value) { console.log("🔌 关闭对等连接") peerConnection.value.close() peerConnection.value = null } // 清理重连计时器 if (reconnectTimer) { console.log("⏲ 清除重连计时器") clearTimeout(reconnectTimer) reconnectTimer = null } // 重置状态 isConnected.value = false isConnecting.value = false reconnectAttempts = 0 currentCallId = null currentPeerId = null // 清空候选队列 iceCandidateQueue.value = [] } return { localStream, remoteStream, peerConnection, isConnected, isConnecting, getLocalMedia, createPeerConnection, createOffer, createAnswer, setRemoteAnswer, addIceCandidate, toggleCamera, toggleMicrophone, closeConnection, } }