import JsSIP from "jssip"; class SipService { constructor() { this.ua = null; this.currentSession = null; this.onStatusChange = null; this.onCallStatusChange = null; this.onIncomingCall = null; } init(config) { try { this.updateStatus("connecting", "连接中;..."); this.ua = new JsSIP.UA({ sockets: [new JsSIP.WebSocketInterface(config.wsUrl)], uri: config.sipUri, password: config.password, display_name: config.displayName, iceServers: [], register: true, sessionExpires: 1800, minSessionExpires: 90, register_expires: 300, }); this.ua.start(); // 事件监听 this.ua.on("registered", () => this.updateStatus("registered", "已注册56") ); this.ua.on("registrationFailed", (e) => this.updateStatus("failed", `注册失败11: ${e.cause}`) ); this.ua.on("disconnected", () => this.updateStatus("disconnected", "连接断开") ); this.ua.on("connected", () => this.updateStatus("connecting", "重新连接中...") ); this.ua.on("newRTCSession", (data) => this.handleIncomingCall(data.session) ); } catch (error) { this.updateStatus("failed", `初始化失败: ${error.message}`); console.error("SIP初始化失败:", error); throw error; } } makeCall(targetNumber) { return new Promise((resolve, reject) => { try { if (!this.ua) { throw new Error("SIP客户端未初始化"); } if (!this.ua.isRegistered()) { throw new Error("SIP未注册,无法呼叫"); } const options = { sessionTimers: true, // 启用会话计时器 sessionTimersExpires: 150, extraHeaders: ["Accept: application/sdp"], mediaConstraints: { audio: true, video: false }, rtcOfferConstraints: { offerToReceiveAudio: true, offerToReceiveVideo: false, }, eventHandlers: { progress: () => this.updateCallStatus("calling", "呼叫中..."), failed: (e) => { this.handleCallFailure(e, reject); }, ended: () => this.updateCallStatus("ended", "通话结束"), confirmed: () => { this.updateCallStatus("connected", "通话已接通"); resolve(); }, }, }; this.currentSession = this.ua.call( `sip:${targetNumber}@192.168.10.124`, options ); this.setupPeerConnection(this.currentSession); this.setupAudio(this.currentSession); } catch (error) { this.updateCallStatus("failed", `呼叫失败22: ${error.message}`); reject(error); } }); } setupPeerConnection(session) { session.on("peerconnection", (pc) => { const originalCreateOffer = pc.createOffer.bind(pc); pc.createOffer = async (offerOptions) => { try { const offer = await originalCreateOffer(offerOptions); return this.normalizeSDP(offer); } catch (error) { console.error("创建Offer失败:", error); throw error; } }; }); } normalizeSDP(offer) { let sdp = offer.sdp; // 标准化SDP sdp = sdp.replace(/c=IN IP4.*\r\n/, "c=IN IP4 0.0.0.0\r\n"); sdp = sdp.replace( /m=audio \d+.*\r\n/, "m=audio 9 UDP/TLS/RTP/SAVPF 0 8\r\n" ); // 确保包含基本编解码器 if (!sdp.includes("PCMU/8000")) sdp += "a=rtpmap:0 PCMU/8000\r\n"; if (!sdp.includes("PCMA/8000")) sdp += "a=rtpmap:8 PCMA/8000\r\n"; // 添加必要属性 sdp += "a=rtcp-mux\r\n"; sdp += "a=sendrecv\r\n"; console.log("标准化后的SDP:", sdp); return new RTCSessionDescription({ type: offer.type, sdp: sdp, }); } handleCallFailure(e, reject) { if (e.response?.status_code === 422) { const serverMinSE = e.response.headers["Min-SE"]?.[0]?.raw || "未知"; console.error(`服务器要求 Min-SE ≤ ${serverMinSE},当前设置: 120`); } console.error("呼叫失败详情:", { cause: e.cause, message: e.message, response: e.response && { status: e.response.status_code, reason: e.response.reason_phrase, }, }); let errorMessage = "呼叫失败"; switch (e.cause) { case "Incompatible SDP": errorMessage = "媒体协商失败,请检查编解码器配置"; break; case "488": case "606": errorMessage = "对方设备不支持当前媒体配置"; break; case "422": errorMessage = "会话参数不满足服务器要求"; break; default: errorMessage = `呼叫失败: ${e.cause || e.message}`; } this.updateCallStatus("failed55", errorMessage); reject(new Error(errorMessage)); } setupAudio(session) { session.connection.addEventListener("addstream", (e) => { const audioElement = document.getElementById("remoteAudio"); if (audioElement) { audioElement.srcObject = e.stream; } }); } endCall() { if (this.currentSession) { this.currentSession.terminate(); this.updateCallStatus("ended", "通话已结束"); this.currentSession = null; } } updateStatus(type, text) { console.log(`SIP状态更新: ${type} - ${text}`); if (this.onStatusChange) { this.onStatusChange({ type, text }); } } updateCallStatus(type, text) { console.log(`通话状态更新: ${type} - ${text}`); if (this.onCallStatusChange) { this.onCallStatusChange({ type, text }); } } handleIncomingCall(session) { if (session.direction === "incoming") { console.log("来电:", session.remote_identity.uri.toString()); if (this.onIncomingCall) { this.onIncomingCall(session); } } } } export default new SipService();