import JsSIP from "jssip";
|
|
class SipService {
|
constructor() {
|
this.ua = null;
|
this.currentSession = null;
|
this.onStatusChange = null; // 状态变化回调
|
}
|
|
// 初始化SIP客户端
|
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:[],
|
// 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) => console.log("呼叫中..."),
|
failed: (e) => console.error("呼叫失败:", e),
|
ended: (e) => console.log("通话结束"),
|
confirmed: (e) => console.log("通话已接通"),
|
},
|
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) => {
|
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.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.currentSession = null;
|
}
|
}
|
|
}
|
|
export default new SipService();
|