From e8c62bd430b9697d3f954125b7ec9f61f18347a2 Mon Sep 17 00:00:00 2001
From: WXL (wul) <wl_5969728@163.com>
Date: 星期一, 20 十月 2025 11:19:34 +0800
Subject: [PATCH] 测试完成
---
src/utils/sipService.js | 280 ++++++++++++++++++++++++++++++++++++++++++++-----------
1 files changed, 223 insertions(+), 57 deletions(-)
diff --git a/src/utils/sipService.js b/src/utils/sipService.js
index 95ac836..a8ce592 100644
--- a/src/utils/sipService.js
+++ b/src/utils/sipService.js
@@ -1,87 +1,253 @@
-import JsSIP from 'jssip'
+import JsSIP from "jssip";
class SipService {
constructor() {
- this.ua = null
- this.currentSession = null
+ this.ua = null;
+ this.currentSession = null;
+ this.onStatusChange = null;
+ this.onCallStatusChange = null;
+ this.onIncomingCall = null;
+ this.isRegistered = false; // 鏂板娉ㄥ唽鐘舵�佹爣蹇�
+ this.registrationTime = null; // 鏂板娉ㄥ唽鎴愬姛鏃堕棿鎴�
}
- // 鍒濆鍖朣IP瀹㈡埛绔�
init(config) {
- this.ua = new JsSIP.UA({
- sockets: [new JsSIP.WebSocketInterface(config.wsUrl)],
- uri: config.sipUri,
- password: config.password,
- display_name: config.displayName,
- realm: config.realm,
- ha1: config.ha1,
- register: true
- })
+ try {
+ this.updateStatus("connecting", "杩炴帴涓�;...");
- this.ua.start()
+ 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.on('registered', () => {
- console.log('SIP娉ㄥ唽鎴愬姛')
- })
+ this.ua.start();
- this.ua.on('registrationFailed', (e) => {
- console.error('SIP娉ㄥ唽澶辫触:', e)
- })
+ // 浜嬩欢鐩戝惉
+ this.ua.on("registered", () => {
+ this.isRegistered = true;
+ this.registrationTime = Date.now(); // 璁板綍娉ㄥ唽鎴愬姛鏃堕棿
+ console.log(this.registrationTime, "娉ㄥ唽鏃堕棿");
- // 鐩戝惉鏉ョ數
- this.ua.on('newRTCSession', (data) => {
- this.handleIncomingCall(data.session)
- })
+ 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) {
- if (!this.ua) {
- console.error('SIP瀹㈡埛绔湭鍒濆鍖�')
- return
+ 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瀹㈡埛绔湭鍒濆鍖�");
+ }
- const options = {
- eventHandlers: {
- progress: (e) => console.log('鍛煎彨涓�...'),
- failed: (e) => console.error('鍛煎彨澶辫触:', e),
- ended: (e) => console.log('閫氳瘽缁撴潫'),
- confirmed: (e) => console.log('閫氳瘽宸叉帴閫�')
- },
- mediaConstraints: { audio: true, video: false },
- rtcOfferConstraints: { offerToReceiveAudio: 1 }
- }
+ if (!this.ua.isRegistered()) {
+ throw new Error("SIP鏈敞鍐岋紝鏃犳硶鍛煎彨");
+ }
- this.currentSession = this.ua.call(`sip:${targetNumber}`, options)
- this.setupAudio(this.currentSession)
+ 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;
+
+ // 鏍囧噯鍖朣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.currentSession = null
+ this.currentSession.terminate();
+ this.updateCallStatus("ended", "閫氳瘽宸茬粨鏉�");
+ this.currentSession = null;
}
}
- // 澶勭悊闊抽娴�
- setupAudio(session) {
- session.connection.addEventListener('addstream', (e) => {
- const audioElement = document.getElementById('remoteAudio')
- if (audioElement) {
- audioElement.srcObject = e.stream
- }
- })
+ 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())
- // 杩欓噷鍙互瑙﹀彂UI閫氱煡
+ if (session.direction === "incoming") {
+ console.log("鏉ョ數:", session.remote_identity.uri.toString());
+ if (this.onIncomingCall) {
+ this.onIncomingCall(session);
+ }
}
}
}
-export default new SipService()
+export default new SipService();
--
Gitblit v1.9.3