From 1aaa2392eea4694076c6a329a5b88436ceb50194 Mon Sep 17 00:00:00 2001 From: WXL (wul) <wl_5969728@163.com> Date: 星期三, 17 九月 2025 15:13:48 +0800 Subject: [PATCH] 电话更新 --- src/api/AiCentre/index.js | 28 src/utils/sipService-cs.js | 201 ++++++++++++ /dev/null | 253 --------------- src/api/AiCentre/phoneCall.js | 18 + src/views/followvisit/record/detailpage/index.vue | 31 + src/utils/sipService.js | 317 +++++++++++-------- src/components/CallButton/index.vue | 80 ++++ 7 files changed, 512 insertions(+), 416 deletions(-) diff --git a/src/api/AiCentre/index.js b/src/api/AiCentre/index.js index 60f658f..9af0b74 100644 --- a/src/api/AiCentre/index.js +++ b/src/api/AiCentre/index.js @@ -1,14 +1,14 @@ -export * from './indicator' -export * from './Problemspeaking' -export * from './Followup' -export * from './general' -export * from './publicity' -export * from './Qtemplate' -export * from './questionnaire' -export * from './SingleTask' -export * from './external' -export * from './patientexternal' -export * from './EChartsdata' -export * from './satisfactionse' -export * from './satisfaction' - +export * from "./indicator"; +export * from "./Problemspeaking"; +export * from "./Followup"; +export * from "./general"; +export * from "./publicity"; +export * from "./Qtemplate"; +export * from "./questionnaire"; +export * from "./SingleTask"; +export * from "./external"; +export * from "./patientexternal"; +export * from "./EChartsdata"; +export * from "./satisfactionse"; +export * from "./satisfaction"; +export * from "./phoneCall"; diff --git a/src/api/AiCentre/phoneCall.js b/src/api/AiCentre/phoneCall.js new file mode 100644 index 0000000..84769dc --- /dev/null +++ b/src/api/AiCentre/phoneCall.js @@ -0,0 +1,18 @@ +import request from "@/utils/request"; + + +// 鍒犻櫎澶栭儴鎮h�呰〃 +export function CallgetList() { + return request({ + url: "/smartor/ServiceTelInfo/getList", + method: "get", + }); +} +// 鏌ヨ澶栭儴鎮h�呰〃 +export function CallsetState(data) { + return request({ + url: "/smartor/ServiceTelInfo/setState", + method: "get", + params: data, + }); +} diff --git a/src/components/CallButton/index.vue b/src/components/CallButton/index.vue index 429a327..d3cb005 100644 --- a/src/components/CallButton/index.vue +++ b/src/components/CallButton/index.vue @@ -28,6 +28,7 @@ <script> import sipService from "@/utils/sipService"; +import { CallsetState, CallgetList } from "@/api/AiCentre/index"; export default { props: { @@ -37,15 +38,17 @@ }, }, data() { - const randomNum = Math.floor(Math.random() * 20) + 1000; // 鍐呴儴瀹氫箟 + const randomNum = Math.floor(Math.random() * 20) + 1000; // 瀹氫箟闅忔満鍒嗘満鍙� return { isCalling: false, + randomNum: randomNum, + randomID: null, callStatus: "idle", // idle, calling, connected, ended sipStatus: "鏈繛鎺�", sipStatusClass: "status-disconnected", sipConfig: { wsUrl: "wss://192.168.100.6:7443", - sipUri: `${randomNum}` + "@192.168.100.6", + sipUri: "", password: "Smartor@2023", displayName: "Web 灏忛緳", // realm: "9.208.5.18:8090", @@ -78,13 +81,22 @@ return this.isCalling ? "閫氳瘽涓�..." : "涓�閿懠鍙�"; }, }, - mounted() { - console.log('褰撳墠鍒嗘満鍙�',this.sipConfig); + created() { + // CallgetList(); + }, + async mounted() { + await this.CallgetList(); sipService.init(this.sipConfig); + // 璁剧疆鐘舵�佸洖璋� sipService.onStatusChange = (status) => { this.sipStatus = status.text; this.sipStatusClass = `status-${status.type}`; + + // 澶勭悊娉ㄥ唽澶辫触鍜屾柇寮�杩炴帴鎯呭喌 + if (status.type === "failed" || status.type === "disconnected") { + this.overCallsetState(); // 閲婃斁鍒嗘満鍙� + } }; // 鐩戝惉閫氳瘽鐘舵�佸彉鍖� @@ -115,7 +127,7 @@ this.isCalling = true; console.log("寮�濮嬪懠鍙細", sipService); - await sipService.makeCall("0"+this.phoneNumber); + await sipService.makeCall("0" + this.phoneNumber); } catch (error) { let registrationTime = Date.now(); // 璁板綍娉ㄩ攢鎴愬姛鏃堕棿 console.log(registrationTime, "鍛煎彨澶辫触鏃堕棿"); @@ -123,7 +135,7 @@ // this.callStatus = "ended"; // this.isCalling = false; //this.$message.error(`鍛煎彨澶辫触: ${error.message}`); - try { + try { // 鍏堟鏌ユ槸鍚﹀彲浠ュ懠鍙� const { canCall, reason } = sipService.canMakeCall(); if (!canCall) { @@ -133,19 +145,69 @@ this.isCalling = true; console.log("寮�濮嬪懠鍙細", sipService); - await sipService.makeCall("0"+this.phoneNumber); + await sipService.makeCall("0" + this.phoneNumber); } catch (error) { - this.callStatus = "ended"; - this.isCalling = false; + this.callStatus = "ended"; + this.isCalling = false; } } }, + // 鏌ヨ鍙敤鍒嗘満鍙� + async CallgetList() { + try { + const res = await CallgetList(); + this.randomNum = res.data[0].tel; + this.randomID = res.data[0].id; + // 姝g‘璁剧疆 sipUri + this.sipConfig.sipUri = `${this.randomNum}@192.168.100.6`; + this.startCallsetState(); + } catch (error) { + console.error("鑾峰彇鍒嗘満鍙峰け璐�:", error); + this.updateStatus("failed", "鑾峰彇鍒嗘満鍙峰け璐�"); + } + }, + async startCallsetState() { + try { + await CallsetState({ id: this.randomID, state: 1 }); + console.log("鍒嗘満鍙风姸鎬佹洿鏂颁负浣跨敤涓�"); + } catch (error) { + console.error("鏇存柊鍒嗘満鍙风姸鎬佸け璐�:", error); + } + }, + async overCallsetState() { + try { + if (this.randomID) { + await CallsetState({ id: this.randomID, state: 0 }); + console.log("鍒嗘満鍙风姸鎬佹洿鏂颁负鍙敤"); + } + } catch (error) { + console.error("閲婃斁鍒嗘満鍙峰け璐�:", error); + } + }, endCall() { sipService.endCall(); this.callStatus = "ended"; this.isCalling = false; }, + cleanupResources() { + // 缁撴潫閫氳瘽 + if (this.isCalling) { + sipService.endCall(); + } + + // 閲婃斁鍒嗘満鍙� + this.overCallsetState(); + + // 鏂紑 SIP 杩炴帴 + if (sipService.ua) { + sipService.ua.stop(); + } + }, + }, + beforeUnmount() { + // 缁勪欢閿�姣佹椂纭繚閲婃斁璧勬簮 + this.cleanupResources(); }, }; </script> diff --git a/src/utils/sipService-cs.js b/src/utils/sipService-cs.js new file mode 100644 index 0000000..7c63647 --- /dev/null +++ b/src/utils/sipService-cs.js @@ -0,0 +1,201 @@ +import JsSIP from "jssip"; + +class SipService { + constructor() { + this.ua = null; + this.currentSession = null; + this.onStatusChange = null; // 鐘舵�佸彉鍖栧洖璋� + this.onCallStatusChange = null; // 鏂板閫氳瘽鐘舵�佸洖璋� + } + + // 鍒濆鍖朣IP瀹㈡埛绔� + init(config) { + try { + this.updateStatus("connecting", "杩炴帴涓�..."); +console.log(config); + + this.ua = new JsSIP.UA({ + sockets: [new JsSIP.WebSocketInterface(config.wsUrl)], + uri: config.sipUri, + password: config.password, + display_name: config.displayName, + iceservers: [], + // realm: config.realm, + register: true, + session_expires: 180, + sessionTimersExpires: 300, // 璁剧疆 Session-Expires=120锛堝繀椤� >= Min-SE锛� + extraHeaders: [ + "Min-SE: 120", // 鍙�夛細鏄惧紡鍛婅瘔鏈嶅姟鍣ㄤ綘鏀寔鐨勬渶灏忓�� + ], + register_expires: 300, // 娉ㄥ唽鏈夋晥鏈�(绉�) + connection_recovery_min_interval: 2, // 鏈�灏忛噸杩為棿闅� + connection_recovery_max_interval: 30, // 鏈�澶ч噸杩為棿闅� + }); + + this.ua.start(); + + // 娉ㄥ唽浜嬩欢鐩戝惉 + this.ua.on("registered", () => { + this.updateStatus("registered", "宸叉敞鍐�"); + }); + + this.ua.on("registrationFailed", (e) => { + this.updateStatus("failed", `娉ㄥ唽澶辫触: ${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); + } + } + handleIncomingCall(session) { + if (session.direction === "incoming") { + console.log("鏉ョ數:", session.remote_identity.uri.toString()); + // 鍙互鍦ㄨ繖閲岃Е鍙� UI 閫氱煡 + if (this.onIncomingCall) { + this.onIncomingCall(session); + } + } + } + + // 鏇存柊鐘舵�佸苟閫氱煡UI + updateStatus(type, text) { + console.log(`SIP鐘舵�佹洿鏂�: ${type} - ${text}`); + if (this.onStatusChange) { + this.onStatusChange({ type, text }); + } + } + + // 涓�閿嫧鍙� - 澧炲姞娉ㄥ唽鐘舵�佹鏌� + makeCall(targetNumber) { + if (!this.ua) { + throw new Error("SIP瀹㈡埛绔湭鍒濆鍖�"); + } + + if (!this.ua.isRegistered()) { + throw new Error("SIP鏈敞鍐岋紝鏃犳硶鍛煎彨"); + } + + const options = { + sessionTimers: true, + sessionTimersExpires: 300, + extraHeaders: [ + "Min-SE: 120", + "Route: <sip:@192.168.100.6>", + "Accept: application/sdp", + "Supported: replaces, timer", + "Allow: INVITE, ACK, BYE, CANCEL, OPTIONS", + ], + eventHandlers: { + progress: (e) => { + this.updateCallStatus("calling", "鍛煎彨涓�..."); + }, + failed: (e) => { + this.updateCallStatus("ended", `鍛煎彨澶辫触: ${e.cause}`); + }, + ended: (e) => { + this.updateCallStatus("ended", "閫氳瘽缁撴潫"); + }, + confirmed: (e) => { + this.updateCallStatus("connected", "閫氳瘽宸叉帴閫�"); + }, + }, + mediaConstraints: { + audio: true, + video: false, + }, + rtcOfferConstraints: { + offerToReceiveAudio: 1, + offerToReceiveVideo: 0, + mandatory: { + OfferToReceiveAudio: true, + OfferToReceiveVideo: false, + }, + }, + pcConfig: { + iceServers: [{ urls: "stun:stun.l.google.com:19302" }], + iceTransportPolicy: "all", + bundlePolicy: "balanced", + rtcpMuxPolicy: "require", + codecs: { + audio: [ + { name: "PCMU", clockRate: 8000, payloadType: 0 }, + { name: "PCMA", clockRate: 8000, payloadType: 8 }, + ], + video: [], + }, + }, + }; + + this.currentSession = this.ua.call( + `sip:${targetNumber}@192.168.100.6`, + options + ); + // 鍦ㄤ細璇濆垱寤哄悗淇敼 SDP + this.currentSession.on("peerconnection", (pc) => { + this.updateCallStatus('calling', '鍛煎彨涓�...'); + pc.createOffer = (offerOptions) => { + return RTCPeerConnection.prototype.createOffer + .call(pc, offerOptions) + .then((offer) => { + const modifiedSdp = offer.sdp + .replace(/c=IN IP4 192\.168\.100\.10/g, "c=IN IP4 192.168.100.6") + .replace(/m=audio \d+ RTP\/AVP.*/, "m=audio 7078 RTP/AVP 0 8"); + return new RTCSessionDescription({ + type: "offer", + sdp: modifiedSdp, + }); + }); + }; + }); + this.currentSession.on('failed', (e) => { + this.updateCallStatus('failed', `鍛煎彨澶辫触2: ${e}`); + }); + + this.currentSession.on('ended', () => { + this.updateCallStatus('ended', '閫氳瘽宸茬粨鏉�'); + }); + + this.currentSession.on('confirmed', () => { + this.updateCallStatus('connected', '閫氳瘽宸叉帴閫�'); + }); + this.setupAudio(this.currentSession); + } + 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; + } + } + // 鏂板鏂规硶锛氭洿鏂伴�氳瘽鐘舵�� + updateCallStatus(type, text) { + console.log(`閫氳瘽鐘舵�佹洿鏂�: ${type} - ${text}`); + if (this.onCallStatusChange) { + this.onCallStatusChange({ type, text }); + } + } +} + +export default new SipService(); diff --git a/src/utils/sipService-zs.js b/src/utils/sipService-zs.js deleted file mode 100644 index dbc4a4e..0000000 --- a/src/utils/sipService-zs.js +++ /dev/null @@ -1,253 +0,0 @@ -import JsSIP from "jssip"; - -class SipService { - constructor() { - this.ua = null; - this.currentSession = null; - this.onStatusChange = null; - this.onCallStatusChange = null; - this.onIncomingCall = null; - this.isRegistered = false; // 鏂板娉ㄥ唽鐘舵�佹爣蹇� - this.registrationTime = 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.isRegistered = true; - this.registrationTime = Date.now(); // 璁板綍娉ㄥ唽鎴愬姛鏃堕棿 - console.log(this.registrationTime, "娉ㄥ唽鏃堕棿"); - - this.updateStatus("registered", "宸叉敞鍐�"); - }); - - this.ua.on("registrationFailed", (e) => { - this.isRegistered = false; - this.updateStatus("failed", `娉ㄥ唽澶辫触: ${e.cause}`); - }); - - this.ua.on("unregistered", () => { - this.isRegistered = false; - let registrationTime = Date.now(); // 璁板綍娉ㄩ攢鎴愬姛鏃堕棿 - console.log(registrationTime, "娉ㄩ攢鏃堕棿"); - this.updateStatus("disconnected", "宸叉敞閿�"); - }); - 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; - } - } - // 鏂板鏂规硶锛氭鏌ユ槸鍚﹀彲浠ュ懠鍙� - canMakeCall(minDelay = 2000) { - if (!this.isRegistered) { - return { canCall: false, reason: "SIP鏈敞鍐岋紝鏃犳硶鍛煎彨" }; - } - - const now = Date.now(); - const timeSinceRegistration = now - this.registrationTime; - - if (timeSinceRegistration < minDelay) { - const remaining = minDelay - timeSinceRegistration; - return { - canCall: false, - reason: `娉ㄥ唽鎴愬姛锛岃绛夊緟 ${Math.ceil(remaining / 1000)} 绉掑悗鍐嶅懠鍙玚, - }; - } - - return { canCall: true, reason: "" }; - } - makeCall(targetNumber) { - const { canCall, reason } = this.canMakeCall(); - if (!canCall) { - return Promise.reject(new Error(reason)); - } - 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}@1192.170.66.107`, - 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; - - // 鏍囧噯鍖朣DP - 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("鏍囧噯鍖栧悗鐨凷DP:", 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 = "濯掍綋鍗忓晢澶辫触锛岃妫�鏌ョ紪瑙g爜鍣ㄩ厤缃�"; - break; - case "488": - case "606": - errorMessage = "瀵规柟璁惧涓嶆敮鎸佸綋鍓嶅獟浣撻厤缃�"; - break; - case "422": - errorMessage = "浼氳瘽鍙傛暟涓嶆弧瓒虫湇鍔″櫒瑕佹眰"; - break; - default: - errorMessage = `鍛煎彨澶辫触3: ${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(); diff --git a/src/utils/sipService.js b/src/utils/sipService.js index 1654ccc..dbc4a4e 100644 --- a/src/utils/sipService.js +++ b/src/utils/sipService.js @@ -4,174 +4,211 @@ constructor() { this.ua = null; this.currentSession = null; - this.onStatusChange = null; // 鐘舵�佸彉鍖栧洖璋� - this.onCallStatusChange = null; // 鏂板閫氳瘽鐘舵�佸洖璋� + this.onStatusChange = null; + this.onCallStatusChange = null; + this.onIncomingCall = null; + this.isRegistered = false; // 鏂板娉ㄥ唽鐘舵�佹爣蹇� + this.registrationTime = null; // 鏂板娉ㄥ唽鎴愬姛鏃堕棿鎴� } - // 鍒濆鍖朣IP瀹㈡埛绔� init(config) { try { - this.updateStatus("connecting", "杩炴帴涓�..."); + 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: [], - // realm: config.realm, + iceServers: [], register: true, - session_expires: 180, - sessionTimersExpires: 300, // 璁剧疆 Session-Expires=120锛堝繀椤� >= Min-SE锛� - extraHeaders: [ - "Min-SE: 120", // 鍙�夛細鏄惧紡鍛婅瘔鏈嶅姟鍣ㄤ綘鏀寔鐨勬渶灏忓�� - ], - register_expires: 300, // 娉ㄥ唽鏈夋晥鏈�(绉�) - connection_recovery_min_interval: 2, // 鏈�灏忛噸杩為棿闅� - connection_recovery_max_interval: 30, // 鏈�澶ч噸杩為棿闅� + sessionExpires: 1800, + minSessionExpires: 90, + register_expires: 300, }); this.ua.start(); - // 娉ㄥ唽浜嬩欢鐩戝惉 + // 浜嬩欢鐩戝惉 this.ua.on("registered", () => { + this.isRegistered = true; + this.registrationTime = Date.now(); // 璁板綍娉ㄥ唽鎴愬姛鏃堕棿 + console.log(this.registrationTime, "娉ㄥ唽鏃堕棿"); + this.updateStatus("registered", "宸叉敞鍐�"); }); this.ua.on("registrationFailed", (e) => { + this.isRegistered = false; this.updateStatus("failed", `娉ㄥ唽澶辫触: ${e.cause}`); }); - this.ua.on("disconnected", () => { - this.updateStatus("disconnected", "杩炴帴鏂紑"); + this.ua.on("unregistered", () => { + this.isRegistered = false; + let registrationTime = Date.now(); // 璁板綍娉ㄩ攢鎴愬姛鏃堕棿 + console.log(registrationTime, "娉ㄩ攢鏃堕棿"); + this.updateStatus("disconnected", "宸叉敞閿�"); }); - - this.ua.on("connected", () => { - this.updateStatus("connecting", "閲嶆柊杩炴帴涓�..."); - }); - - // 鐩戝惉鏉ョ數 - this.ua.on("newRTCSession", (data) => { - this.handleIncomingCall(data.session); - }); + 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; } } - handleIncomingCall(session) { - if (session.direction === "incoming") { - console.log("鏉ョ數:", session.remote_identity.uri.toString()); - // 鍙互鍦ㄨ繖閲岃Е鍙� UI 閫氱煡 - if (this.onIncomingCall) { - this.onIncomingCall(session); - } + // 鏂板鏂规硶锛氭鏌ユ槸鍚﹀彲浠ュ懠鍙� + canMakeCall(minDelay = 2000) { + if (!this.isRegistered) { + return { canCall: false, reason: "SIP鏈敞鍐岋紝鏃犳硶鍛煎彨" }; } - } - // 鏇存柊鐘舵�佸苟閫氱煡UI - updateStatus(type, text) { - console.log(`SIP鐘舵�佹洿鏂�: ${type} - ${text}`); - if (this.onStatusChange) { - this.onStatusChange({ type, text }); - } - } + const now = Date.now(); + const timeSinceRegistration = now - this.registrationTime; - // 涓�閿嫧鍙� - 澧炲姞娉ㄥ唽鐘舵�佹鏌� + if (timeSinceRegistration < minDelay) { + const remaining = minDelay - timeSinceRegistration; + return { + canCall: false, + reason: `娉ㄥ唽鎴愬姛锛岃绛夊緟 ${Math.ceil(remaining / 1000)} 绉掑悗鍐嶅懠鍙玚, + }; + } + + return { canCall: true, reason: "" }; + } makeCall(targetNumber) { - if (!this.ua) { - throw new Error("SIP瀹㈡埛绔湭鍒濆鍖�"); + const { canCall, reason } = this.canMakeCall(); + if (!canCall) { + return Promise.reject(new Error(reason)); } + return new Promise((resolve, reject) => { + try { + if (!this.ua) { + throw new Error("SIP瀹㈡埛绔湭鍒濆鍖�"); + } - if (!this.ua.isRegistered()) { - throw new Error("SIP鏈敞鍐岋紝鏃犳硶鍛煎彨"); - } + if (!this.ua.isRegistered()) { + throw new Error("SIP鏈敞鍐岋紝鏃犳硶鍛煎彨"); + } - const options = { - sessionTimers: true, - sessionTimersExpires: 300, - extraHeaders: [ - "Min-SE: 120", - "Route: <sip:@192.168.100.6>", - "Accept: application/sdp", - "Supported: replaces, timer", - "Allow: INVITE, ACK, BYE, CANCEL, OPTIONS", - ], - eventHandlers: { - progress: (e) => { - this.updateCallStatus("calling", "鍛煎彨涓�..."); - }, - failed: (e) => { - this.updateCallStatus("ended", `鍛煎彨澶辫触: ${e.cause}`); - }, - ended: (e) => { - this.updateCallStatus("ended", "閫氳瘽缁撴潫"); - }, - confirmed: (e) => { - this.updateCallStatus("connected", "閫氳瘽宸叉帴閫�"); - }, - }, - mediaConstraints: { - audio: true, - video: false, - }, - rtcOfferConstraints: { - offerToReceiveAudio: 1, - offerToReceiveVideo: 0, - mandatory: { - OfferToReceiveAudio: true, - OfferToReceiveVideo: false, - }, - }, - pcConfig: { - iceServers: [{ urls: "stun:stun.l.google.com:19302" }], - iceTransportPolicy: "all", - bundlePolicy: "balanced", - rtcpMuxPolicy: "require", - codecs: { - audio: [ - { name: "PCMU", clockRate: 8000, payloadType: 0 }, - { name: "PCMA", clockRate: 8000, payloadType: 8 }, - ], - video: [], - }, - }, - }; + 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.100.6`, - options - ); - // 鍦ㄤ細璇濆垱寤哄悗淇敼 SDP - this.currentSession.on("peerconnection", (pc) => { - this.updateCallStatus('calling', '鍛煎彨涓�...'); - pc.createOffer = (offerOptions) => { - return RTCPeerConnection.prototype.createOffer - .call(pc, offerOptions) - .then((offer) => { - const modifiedSdp = offer.sdp - .replace(/c=IN IP4 192\.168\.100\.10/g, "c=IN IP4 192.168.100.6") - .replace(/m=audio \d+ RTP\/AVP.*/, "m=audio 7078 RTP/AVP 0 8"); - return new RTCSessionDescription({ - type: "offer", - sdp: modifiedSdp, - }); - }); + this.currentSession = this.ua.call( + `sip:${targetNumber}@1192.170.66.107`, + 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; + } }; }); - this.currentSession.on('failed', (e) => { - this.updateCallStatus('failed', `鍛煎彨澶辫触2: ${e}`); - }); - - this.currentSession.on('ended', () => { - this.updateCallStatus('ended', '閫氳瘽宸茬粨鏉�'); - }); - - this.currentSession.on('confirmed', () => { - this.updateCallStatus('connected', '閫氳瘽宸叉帴閫�'); - }); - this.setupAudio(this.currentSession); } + + normalizeSDP(offer) { + let sdp = offer.sdp; + + // 鏍囧噯鍖朣DP + 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("鏍囧噯鍖栧悗鐨凷DP:", 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 = "濯掍綋鍗忓晢澶辫触锛岃妫�鏌ョ紪瑙g爜鍣ㄩ厤缃�"; + break; + case "488": + case "606": + errorMessage = "瀵规柟璁惧涓嶆敮鎸佸綋鍓嶅獟浣撻厤缃�"; + break; + case "422": + errorMessage = "浼氳瘽鍙傛暟涓嶆弧瓒虫湇鍔″櫒瑕佹眰"; + break; + default: + errorMessage = `鍛煎彨澶辫触3: ${e.cause || e.message}`; + } + + this.updateCallStatus("failed55", errorMessage); + reject(new Error(errorMessage)); + } + setupAudio(session) { session.connection.addEventListener("addstream", (e) => { const audioElement = document.getElementById("remoteAudio"); @@ -180,21 +217,37 @@ } }); } - // 鎸傛柇褰撳墠閫氳瘽 + endCall() { - if (this.currentSession) { + if (this.currentSession) { this.currentSession.terminate(); - this.updateCallStatus('ended', '閫氳瘽宸茬粨鏉�'); + 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(); diff --git a/src/views/followvisit/record/detailpage/index.vue b/src/views/followvisit/record/detailpage/index.vue index d6164c8..cc8990c 100644 --- a/src/views/followvisit/record/detailpage/index.vue +++ b/src/views/followvisit/record/detailpage/index.vue @@ -1779,7 +1779,7 @@ // 杩欓噷璋冪敤浣犵殑鐭俊鍙戦�� API // 鍋囪 API 涓� sendMsg锛屽弬鏁板彲鑳介渶瑕佹牴鎹疄闄呮儏鍐佃皟鏁� sendMsg({ - phone: this.userform.telcode, // 纭繚鐢佃瘽鍙风爜瀛楁姝g‘ + phone: this.userform.telcode, // 纭繚鐢佃瘽鍙风爜瀛楁姝g‘ content: this.smsContent, }) .then((res) => { @@ -2152,6 +2152,16 @@ this.getTaskservelist(); }, }, + // deactivated() { + // console.log(11); + // }, + beforeRouteLeave(to, from, next) { + this.$refs.callButton.cleanupResources(); + next(); // 纭繚璋冪敤 nex + }, + // beforeRouteUpdate() { + // console.log(33); + // }, }; </script> @@ -2554,7 +2564,8 @@ @media screen and (max-width: 1200px), (min-resolution: 1.1dppx) { flex-direction: column; - .call-action, .manual-action { + .call-action, + .manual-action { width: 100% !important; } } @@ -2659,7 +2670,8 @@ margin: 10px; padding: 15px; - .topic-dev, .scriptTopic-dev { + .topic-dev, + .scriptTopic-dev { margin-bottom: 15px; } } @@ -2692,7 +2704,8 @@ /* 涓虹Щ鍔ㄨ澶囦紭鍖栨粴鍔ㄤ綋楠� */ @media screen and (max-width: 768px) { - .Followuserinfo, .Followuserinfos { + .Followuserinfo, + .Followuserinfos { padding: 15px; margin: 5px; } @@ -2705,18 +2718,20 @@ /* 缂╂斁妫�娴嬫牱寮� */ @media screen and (min-resolution: 1.1dppx), - screen and (-webkit-min-device-pixel-ratio: 1.1), - screen and (max-width: 1200px) { + screen and (-webkit-min-device-pixel-ratio: 1.1), + screen and (max-width: 1200px) { .action-container { flex-direction: column; } - .call-action, .manual-action { + .call-action, + .manual-action { width: 100%; } /* 璋冩暣鍐呴儴鍏冪礌闂磋窛 */ - .call-container, .Followuserinfos { + .call-container, + .Followuserinfos { margin-bottom: 20px; } } -- Gitblit v1.9.3