import { sendMessage } from "@/websocket/onlineSocket"; import { onCallStore } from "@/store/VoiceTarget"; import { ref } from "vue"; const iceserver = { iceServers: [ { urls: process.env.STUN_URL, }, { urls: process.env.TURN_URL, username: process.env.TURN_USERNAME, credential: process.env.TURN_CREDENTIAL, }, ], }; interface message { cmd: "VOICE_ICE_CANDIDATE" | "VOICE_SDP_OFFER" | "VOICE_SDP_ANSWER", from: number, to: number, content: any, } const oncall = onCallStore(); const RTCpeerConnection = ref(null); const localstream = ref(null); const remotestream = ref(null); // 本地候选:为了避免“绑定 onicecandidate 太晚”导致丢失,统一先缓存,等 remoteDescription 就绪后再发送 const pendingLocalCandidates = ref([]); // 远端候选:避免远端 candidate 早到但 remoteDescription 未 set 导致 addIceCandidate 失败 const pendingRemoteCandidates = ref([]); // 当前会话对端信息(用于发送 ICE 时带上 from/to) const currentFrom = ref(null); const currentTo = ref(null); const canSendIceNow = () => { const pc: any = RTCpeerConnection.value; return ( !!pc && !!pc.localDescription && !!pc.remoteDescription && currentFrom.value !== null && currentTo.value !== null ); }; const flushLocalCandidatesIfReady = () => { if (!canSendIceNow()) return; if (pendingLocalCandidates.value.length === 0) return; pendingLocalCandidates.value.forEach((candidate) => { const message: message = { cmd: "VOICE_ICE_CANDIDATE", from: currentFrom.value as number, to: currentTo.value as number, content: candidate, }; sendMessage(message); }); pendingLocalCandidates.value = []; }; const flushRemoteCandidatesIfReady = async () => { const pc: any = RTCpeerConnection.value; if (!pc || !pc.remoteDescription) return; if (pendingRemoteCandidates.value.length === 0) return; for (const c of pendingRemoteCandidates.value) { await pc.addIceCandidate(new RTCIceCandidate(c)); } pendingRemoteCandidates.value = []; }; const getlocalStream = async () => { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); console.log("获取本地音频流成功"); return stream; // // 获取音频和视频轨道 // const audioTrack = stream.getAudioTracks()[0]; // // 将轨道添加到 RTCPeerConnection // peerConnection.addTrack(audioTrack, stream); }; const closeLocalStream = (stream: MediaStream) => { stream.getTracks().forEach(track => track.stop()); } export const initRTCconnection = async () => { RTCpeerConnection.value = new RTCPeerConnection(iceserver); localstream.value = await getlocalStream(); RTCpeerConnection.value.addTrack(localstream.value.getAudioTracks()[0], localstream.value); // 必须尽早绑定,否则可能在 setLocalDescription 后就把 candidate 产完了 RTCpeerConnection.value.onicecandidate = (event: { candidate: any }) => { if (!event.candidate) return; pendingLocalCandidates.value.push(event.candidate); flushLocalCandidatesIfReady(); }; // 远端音频:收到 track 后把 MediaStream 绑定到 UI 的