react-jssip-kit 0.7.7 → 0.7.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +589 -89
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +589 -89
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -837,6 +837,165 @@ var SessionManager = class {
|
|
|
837
837
|
}
|
|
838
838
|
};
|
|
839
839
|
|
|
840
|
+
// src/jssip-lib/sip/debugLogging.ts
|
|
841
|
+
var describePc = (pc) => ({
|
|
842
|
+
connectionState: pc?.connectionState,
|
|
843
|
+
signalingState: pc?.signalingState,
|
|
844
|
+
iceConnectionState: pc?.iceConnectionState
|
|
845
|
+
});
|
|
846
|
+
var SipDebugLogger = class {
|
|
847
|
+
constructor() {
|
|
848
|
+
this.enabled = false;
|
|
849
|
+
this.statsStops = /* @__PURE__ */ new Map();
|
|
850
|
+
}
|
|
851
|
+
setEnabled(enabled) {
|
|
852
|
+
this.enabled = enabled;
|
|
853
|
+
if (!enabled) {
|
|
854
|
+
this.statsStops.forEach((stop) => stop());
|
|
855
|
+
this.statsStops.clear();
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
isEnabled() {
|
|
859
|
+
return this.enabled;
|
|
860
|
+
}
|
|
861
|
+
logLocalAudioError(sessionId, message, pc, extra) {
|
|
862
|
+
if (!this.enabled)
|
|
863
|
+
return;
|
|
864
|
+
console.error(message, {
|
|
865
|
+
sessionId,
|
|
866
|
+
pc: describePc(pc),
|
|
867
|
+
...extra
|
|
868
|
+
});
|
|
869
|
+
void this.logOutboundStats(sessionId, pc, message);
|
|
870
|
+
}
|
|
871
|
+
logRemoteAudioError(sessionId, message, pc, extra) {
|
|
872
|
+
if (!this.enabled)
|
|
873
|
+
return;
|
|
874
|
+
console.error(message, {
|
|
875
|
+
sessionId,
|
|
876
|
+
pc: describePc(pc),
|
|
877
|
+
...extra
|
|
878
|
+
});
|
|
879
|
+
void this.logInboundStats(sessionId, pc, message);
|
|
880
|
+
}
|
|
881
|
+
logMicRecoveryDrop(payload) {
|
|
882
|
+
if (!this.enabled)
|
|
883
|
+
return;
|
|
884
|
+
console.error("[sip] microphone dropped", payload);
|
|
885
|
+
}
|
|
886
|
+
startCallStatsLogging(sessionId, session) {
|
|
887
|
+
if (!this.enabled || this.statsStops.has(sessionId))
|
|
888
|
+
return;
|
|
889
|
+
let pc = session?.connection ?? null;
|
|
890
|
+
const onPeer = (data) => {
|
|
891
|
+
pc = data.peerconnection;
|
|
892
|
+
};
|
|
893
|
+
session.on?.("peerconnection", onPeer);
|
|
894
|
+
const intervalMs = 3e3;
|
|
895
|
+
const logStats = async () => {
|
|
896
|
+
if (!this.enabled || !pc?.getStats)
|
|
897
|
+
return;
|
|
898
|
+
try {
|
|
899
|
+
const report = await pc.getStats();
|
|
900
|
+
const { outboundAudio, inboundAudio } = collectAudioStats(report);
|
|
901
|
+
console.info("[sip] call stats", {
|
|
902
|
+
sessionId,
|
|
903
|
+
pc: describePc(pc),
|
|
904
|
+
outboundAudio,
|
|
905
|
+
inboundAudio
|
|
906
|
+
});
|
|
907
|
+
} catch (err) {
|
|
908
|
+
console.error("[sip] call stats failed", { sessionId, error: err });
|
|
909
|
+
}
|
|
910
|
+
};
|
|
911
|
+
const timer = setInterval(() => {
|
|
912
|
+
void logStats();
|
|
913
|
+
}, intervalMs);
|
|
914
|
+
void logStats();
|
|
915
|
+
const stop = () => {
|
|
916
|
+
clearInterval(timer);
|
|
917
|
+
session.off?.("peerconnection", onPeer);
|
|
918
|
+
this.statsStops.delete(sessionId);
|
|
919
|
+
};
|
|
920
|
+
this.statsStops.set(sessionId, stop);
|
|
921
|
+
}
|
|
922
|
+
stopCallStatsLogging(sessionId) {
|
|
923
|
+
const stop = this.statsStops.get(sessionId);
|
|
924
|
+
if (stop)
|
|
925
|
+
stop();
|
|
926
|
+
}
|
|
927
|
+
async logOutboundStats(sessionId, pc, context) {
|
|
928
|
+
if (!pc?.getStats)
|
|
929
|
+
return;
|
|
930
|
+
try {
|
|
931
|
+
const report = await pc.getStats();
|
|
932
|
+
const { outboundAudio } = collectAudioStats(report);
|
|
933
|
+
if (outboundAudio.length) {
|
|
934
|
+
console.info("[sip] outgoing audio stats", {
|
|
935
|
+
sessionId,
|
|
936
|
+
context,
|
|
937
|
+
outboundAudio
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
} catch (err) {
|
|
941
|
+
console.error("[sip] outgoing audio stats failed", {
|
|
942
|
+
sessionId,
|
|
943
|
+
context,
|
|
944
|
+
error: err
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
async logInboundStats(sessionId, pc, context) {
|
|
949
|
+
if (!pc?.getStats)
|
|
950
|
+
return;
|
|
951
|
+
try {
|
|
952
|
+
const report = await pc.getStats();
|
|
953
|
+
const { inboundAudio } = collectAudioStats(report);
|
|
954
|
+
if (inboundAudio.length) {
|
|
955
|
+
console.error("[sip] incoming audio stats", {
|
|
956
|
+
sessionId,
|
|
957
|
+
context,
|
|
958
|
+
inboundAudio
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
} catch (err) {
|
|
962
|
+
console.error("[sip] incoming audio stats failed", {
|
|
963
|
+
sessionId,
|
|
964
|
+
context,
|
|
965
|
+
error: err
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
var sipDebugLogger = new SipDebugLogger();
|
|
971
|
+
function collectAudioStats(report) {
|
|
972
|
+
const outboundAudio = [];
|
|
973
|
+
const inboundAudio = [];
|
|
974
|
+
report.forEach((stat) => {
|
|
975
|
+
const kind = stat.kind ?? stat.mediaType;
|
|
976
|
+
if (stat.type === "outbound-rtp" && kind === "audio") {
|
|
977
|
+
outboundAudio.push({
|
|
978
|
+
id: stat.id,
|
|
979
|
+
packetsSent: stat.packetsSent,
|
|
980
|
+
bytesSent: stat.bytesSent,
|
|
981
|
+
jitter: stat.jitter,
|
|
982
|
+
roundTripTime: stat.roundTripTime
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
if (stat.type === "inbound-rtp" && kind === "audio") {
|
|
986
|
+
inboundAudio.push({
|
|
987
|
+
id: stat.id,
|
|
988
|
+
packetsReceived: stat.packetsReceived,
|
|
989
|
+
packetsLost: stat.packetsLost,
|
|
990
|
+
bytesReceived: stat.bytesReceived,
|
|
991
|
+
jitter: stat.jitter,
|
|
992
|
+
roundTripTime: stat.roundTripTime
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
});
|
|
996
|
+
return { outboundAudio, inboundAudio };
|
|
997
|
+
}
|
|
998
|
+
|
|
840
999
|
// src/jssip-lib/sip/sessionLifecycle.ts
|
|
841
1000
|
var SessionLifecycle = class {
|
|
842
1001
|
constructor(deps) {
|
|
@@ -847,6 +1006,9 @@ var SessionLifecycle = class {
|
|
|
847
1006
|
this.attachSessionHandlers = deps.attachSessionHandlers;
|
|
848
1007
|
this.getMaxSessionCount = deps.getMaxSessionCount;
|
|
849
1008
|
}
|
|
1009
|
+
setDebugEnabled(enabled) {
|
|
1010
|
+
sipDebugLogger.setEnabled(enabled);
|
|
1011
|
+
}
|
|
850
1012
|
handleNewRTCSession(e) {
|
|
851
1013
|
const session = e.session;
|
|
852
1014
|
const sessionId = String(
|
|
@@ -875,71 +1037,12 @@ var SessionLifecycle = class {
|
|
|
875
1037
|
const rtc = this.sessionManager.getOrCreateRtc(sessionId, session);
|
|
876
1038
|
this.sessionManager.setSession(sessionId, session);
|
|
877
1039
|
this.attachSessionHandlers(sessionId, session);
|
|
1040
|
+
this.attachCallStatsLogging(sessionId, session);
|
|
878
1041
|
if (e.originator === "local" && !rtc.mediaStream) {
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
let retryTimer = null;
|
|
884
|
-
let stopped = false;
|
|
885
|
-
const tryBindFromPc = (pc) => {
|
|
886
|
-
if (stopped || !pc || this.sessionManager.getRtc(sessionId)?.mediaStream) {
|
|
887
|
-
return false;
|
|
888
|
-
}
|
|
889
|
-
const audioSender = pc?.getSenders?.()?.find((s) => s.track?.kind === "audio");
|
|
890
|
-
const audioTrack = audioSender?.track;
|
|
891
|
-
if (!audioTrack)
|
|
892
|
-
return false;
|
|
893
|
-
const outgoingStream = new MediaStream([audioTrack]);
|
|
894
|
-
this.sessionManager.setSessionMedia(sessionId, outgoingStream);
|
|
895
|
-
return true;
|
|
896
|
-
};
|
|
897
|
-
const scheduleRetry = (pc) => {
|
|
898
|
-
if (stopped || retryScheduled || attempts >= maxAttempts) {
|
|
899
|
-
session.off?.("peerconnection", onPeer);
|
|
900
|
-
return;
|
|
901
|
-
}
|
|
902
|
-
retryScheduled = true;
|
|
903
|
-
attempts += 1;
|
|
904
|
-
retryTimer = setTimeout(() => {
|
|
905
|
-
retryScheduled = false;
|
|
906
|
-
retryTimer = null;
|
|
907
|
-
if (tryBindFromPc(pc)) {
|
|
908
|
-
session.off?.("peerconnection", onPeer);
|
|
909
|
-
return;
|
|
910
|
-
}
|
|
911
|
-
scheduleRetry(pc);
|
|
912
|
-
}, retryDelayMs);
|
|
913
|
-
};
|
|
914
|
-
const onPeer = (data) => {
|
|
915
|
-
if (stopped)
|
|
916
|
-
return;
|
|
917
|
-
if (tryBindFromPc(data.peerconnection)) {
|
|
918
|
-
session.off?.("peerconnection", onPeer);
|
|
919
|
-
return;
|
|
920
|
-
}
|
|
921
|
-
scheduleRetry(data.peerconnection);
|
|
922
|
-
};
|
|
923
|
-
const stopRetry = () => {
|
|
924
|
-
if (stopped)
|
|
925
|
-
return;
|
|
926
|
-
stopped = true;
|
|
927
|
-
if (retryTimer) {
|
|
928
|
-
clearTimeout(retryTimer);
|
|
929
|
-
retryTimer = null;
|
|
930
|
-
}
|
|
931
|
-
session.off?.("peerconnection", onPeer);
|
|
932
|
-
session.off?.("ended", stopRetry);
|
|
933
|
-
session.off?.("failed", stopRetry);
|
|
934
|
-
};
|
|
935
|
-
const existingPc = session?.connection;
|
|
936
|
-
if (!tryBindFromPc(existingPc)) {
|
|
937
|
-
if (existingPc)
|
|
938
|
-
scheduleRetry(existingPc);
|
|
939
|
-
session.on?.("peerconnection", onPeer);
|
|
940
|
-
}
|
|
941
|
-
session.on?.("ended", stopRetry);
|
|
942
|
-
session.on?.("failed", stopRetry);
|
|
1042
|
+
this.bindLocalOutgoingAudio(sessionId, session);
|
|
1043
|
+
}
|
|
1044
|
+
if (e.originator === "remote") {
|
|
1045
|
+
this.bindRemoteIncomingAudio(sessionId, session);
|
|
943
1046
|
}
|
|
944
1047
|
holdOtherSessions(this.state, sessionId, (id) => {
|
|
945
1048
|
const otherRtc = this.sessionManager.getRtc(id);
|
|
@@ -956,6 +1059,414 @@ var SessionLifecycle = class {
|
|
|
956
1059
|
});
|
|
957
1060
|
this.emit("newRTCSession", e);
|
|
958
1061
|
}
|
|
1062
|
+
bindLocalOutgoingAudio(sessionId, session) {
|
|
1063
|
+
const maxAttempts = 50;
|
|
1064
|
+
const retryDelayMs = 500;
|
|
1065
|
+
let attempts = 0;
|
|
1066
|
+
let retryScheduled = false;
|
|
1067
|
+
let retryTimer = null;
|
|
1068
|
+
let stopped = false;
|
|
1069
|
+
let exhausted = false;
|
|
1070
|
+
let exhaustedCheckUsed = false;
|
|
1071
|
+
let attachedPc = null;
|
|
1072
|
+
const logLocalAudioError = (message, pc, extra) => {
|
|
1073
|
+
sipDebugLogger.logLocalAudioError(sessionId, message, pc, extra);
|
|
1074
|
+
};
|
|
1075
|
+
const tryBindFromPc = (pc) => {
|
|
1076
|
+
if (stopped || !pc || this.sessionManager.getRtc(sessionId)?.mediaStream) {
|
|
1077
|
+
return false;
|
|
1078
|
+
}
|
|
1079
|
+
const audioSender = pc?.getSenders?.()?.find((s) => s.track?.kind === "audio");
|
|
1080
|
+
const audioTrack = audioSender?.track;
|
|
1081
|
+
if (!audioTrack) {
|
|
1082
|
+
logLocalAudioError(
|
|
1083
|
+
"[sip] outgoing audio bind failed: no audio track",
|
|
1084
|
+
pc
|
|
1085
|
+
);
|
|
1086
|
+
return false;
|
|
1087
|
+
}
|
|
1088
|
+
const outgoingStream = new MediaStream([audioTrack]);
|
|
1089
|
+
this.sessionManager.setSessionMedia(sessionId, outgoingStream);
|
|
1090
|
+
return true;
|
|
1091
|
+
};
|
|
1092
|
+
const onPcStateChange = () => {
|
|
1093
|
+
if (stopped)
|
|
1094
|
+
return;
|
|
1095
|
+
if (exhausted) {
|
|
1096
|
+
if (exhaustedCheckUsed)
|
|
1097
|
+
return;
|
|
1098
|
+
exhaustedCheckUsed = true;
|
|
1099
|
+
if (tryBindFromPc(attachedPc))
|
|
1100
|
+
stopRetry();
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
if (tryBindFromPc(attachedPc))
|
|
1104
|
+
stopRetry();
|
|
1105
|
+
};
|
|
1106
|
+
const attachPcListeners = (pc) => {
|
|
1107
|
+
if (!pc || pc === attachedPc)
|
|
1108
|
+
return;
|
|
1109
|
+
if (attachedPc) {
|
|
1110
|
+
attachedPc.removeEventListener?.(
|
|
1111
|
+
"signalingstatechange",
|
|
1112
|
+
onPcStateChange
|
|
1113
|
+
);
|
|
1114
|
+
attachedPc.removeEventListener?.(
|
|
1115
|
+
"connectionstatechange",
|
|
1116
|
+
onPcStateChange
|
|
1117
|
+
);
|
|
1118
|
+
attachedPc.removeEventListener?.(
|
|
1119
|
+
"iceconnectionstatechange",
|
|
1120
|
+
onPcStateChange
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
attachedPc = pc;
|
|
1124
|
+
attachedPc.addEventListener?.("signalingstatechange", onPcStateChange);
|
|
1125
|
+
attachedPc.addEventListener?.("connectionstatechange", onPcStateChange);
|
|
1126
|
+
attachedPc.addEventListener?.("iceconnectionstatechange", onPcStateChange);
|
|
1127
|
+
};
|
|
1128
|
+
const clearRetryTimer = () => {
|
|
1129
|
+
if (!retryTimer)
|
|
1130
|
+
return;
|
|
1131
|
+
clearTimeout(retryTimer);
|
|
1132
|
+
retryTimer = null;
|
|
1133
|
+
};
|
|
1134
|
+
const stopRetry = () => {
|
|
1135
|
+
if (stopped)
|
|
1136
|
+
return;
|
|
1137
|
+
stopped = true;
|
|
1138
|
+
clearRetryTimer();
|
|
1139
|
+
if (attachedPc) {
|
|
1140
|
+
attachedPc.removeEventListener?.(
|
|
1141
|
+
"signalingstatechange",
|
|
1142
|
+
onPcStateChange
|
|
1143
|
+
);
|
|
1144
|
+
attachedPc.removeEventListener?.(
|
|
1145
|
+
"connectionstatechange",
|
|
1146
|
+
onPcStateChange
|
|
1147
|
+
);
|
|
1148
|
+
attachedPc.removeEventListener?.(
|
|
1149
|
+
"iceconnectionstatechange",
|
|
1150
|
+
onPcStateChange
|
|
1151
|
+
);
|
|
1152
|
+
attachedPc = null;
|
|
1153
|
+
}
|
|
1154
|
+
session.off?.("peerconnection", onPeer);
|
|
1155
|
+
session.off?.("confirmed", onConfirmed);
|
|
1156
|
+
session.off?.("ended", stopRetry);
|
|
1157
|
+
session.off?.("failed", stopRetry);
|
|
1158
|
+
};
|
|
1159
|
+
const scheduleRetry = (pc) => {
|
|
1160
|
+
if (stopped || retryScheduled || exhausted)
|
|
1161
|
+
return;
|
|
1162
|
+
if (attempts >= maxAttempts) {
|
|
1163
|
+
logLocalAudioError(
|
|
1164
|
+
"[sip] outgoing audio bind failed: max retries reached",
|
|
1165
|
+
pc,
|
|
1166
|
+
{ attempts }
|
|
1167
|
+
);
|
|
1168
|
+
exhausted = true;
|
|
1169
|
+
clearRetryTimer();
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
if (!pc) {
|
|
1173
|
+
logLocalAudioError(
|
|
1174
|
+
"[sip] outgoing audio bind failed: missing peerconnection",
|
|
1175
|
+
pc
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1178
|
+
retryScheduled = true;
|
|
1179
|
+
attempts += 1;
|
|
1180
|
+
retryTimer = setTimeout(() => {
|
|
1181
|
+
retryScheduled = false;
|
|
1182
|
+
retryTimer = null;
|
|
1183
|
+
if (tryBindFromPc(pc)) {
|
|
1184
|
+
stopRetry();
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
scheduleRetry(pc);
|
|
1188
|
+
}, retryDelayMs);
|
|
1189
|
+
};
|
|
1190
|
+
const onPeer = (data) => {
|
|
1191
|
+
if (stopped)
|
|
1192
|
+
return;
|
|
1193
|
+
attachPcListeners(data.peerconnection);
|
|
1194
|
+
if (exhausted) {
|
|
1195
|
+
if (exhaustedCheckUsed)
|
|
1196
|
+
return;
|
|
1197
|
+
exhaustedCheckUsed = true;
|
|
1198
|
+
if (tryBindFromPc(data.peerconnection))
|
|
1199
|
+
stopRetry();
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
if (tryBindFromPc(data.peerconnection)) {
|
|
1203
|
+
stopRetry();
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
scheduleRetry(data.peerconnection);
|
|
1207
|
+
};
|
|
1208
|
+
const onConfirmed = () => {
|
|
1209
|
+
if (stopped)
|
|
1210
|
+
return;
|
|
1211
|
+
const currentPc = session?.connection ?? attachedPc;
|
|
1212
|
+
if (exhausted) {
|
|
1213
|
+
if (exhaustedCheckUsed)
|
|
1214
|
+
return;
|
|
1215
|
+
exhaustedCheckUsed = true;
|
|
1216
|
+
if (tryBindFromPc(currentPc))
|
|
1217
|
+
stopRetry();
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
if (tryBindFromPc(currentPc)) {
|
|
1221
|
+
stopRetry();
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
scheduleRetry(currentPc);
|
|
1225
|
+
};
|
|
1226
|
+
const existingPc = session?.connection;
|
|
1227
|
+
if (!tryBindFromPc(existingPc)) {
|
|
1228
|
+
if (existingPc) {
|
|
1229
|
+
attachPcListeners(existingPc);
|
|
1230
|
+
scheduleRetry(existingPc);
|
|
1231
|
+
}
|
|
1232
|
+
session.on?.("peerconnection", onPeer);
|
|
1233
|
+
}
|
|
1234
|
+
session.on?.("confirmed", onConfirmed);
|
|
1235
|
+
session.on?.("ended", stopRetry);
|
|
1236
|
+
session.on?.("failed", stopRetry);
|
|
1237
|
+
}
|
|
1238
|
+
bindRemoteIncomingAudio(sessionId, session) {
|
|
1239
|
+
const maxAttempts = 50;
|
|
1240
|
+
const retryDelayMs = 500;
|
|
1241
|
+
let attempts = 0;
|
|
1242
|
+
let retryScheduled = false;
|
|
1243
|
+
let retryTimer = null;
|
|
1244
|
+
let stopped = false;
|
|
1245
|
+
let exhausted = false;
|
|
1246
|
+
let exhaustedCheckUsed = false;
|
|
1247
|
+
let attachedPc = null;
|
|
1248
|
+
let attachedTrack = null;
|
|
1249
|
+
const logRemoteAudioError = (message, pc, extra) => {
|
|
1250
|
+
sipDebugLogger.logRemoteAudioError(sessionId, message, pc, extra);
|
|
1251
|
+
};
|
|
1252
|
+
const logMissingReceiver = (pc, note) => {
|
|
1253
|
+
logRemoteAudioError(
|
|
1254
|
+
"[sip] incoming audio bind failed: no remote track",
|
|
1255
|
+
pc,
|
|
1256
|
+
{ note }
|
|
1257
|
+
);
|
|
1258
|
+
};
|
|
1259
|
+
const getRemoteAudioTrack = (pc) => {
|
|
1260
|
+
const receiver = pc?.getReceivers?.()?.find((r) => r.track?.kind === "audio");
|
|
1261
|
+
return receiver?.track ?? null;
|
|
1262
|
+
};
|
|
1263
|
+
const attachTrackListeners = (track) => {
|
|
1264
|
+
if (!track || track === attachedTrack)
|
|
1265
|
+
return;
|
|
1266
|
+
if (attachedTrack) {
|
|
1267
|
+
attachedTrack.removeEventListener?.("ended", onRemoteEnded);
|
|
1268
|
+
attachedTrack.removeEventListener?.("mute", onRemoteMuted);
|
|
1269
|
+
}
|
|
1270
|
+
attachedTrack = track;
|
|
1271
|
+
attachedTrack.addEventListener?.("ended", onRemoteEnded);
|
|
1272
|
+
attachedTrack.addEventListener?.("mute", onRemoteMuted);
|
|
1273
|
+
};
|
|
1274
|
+
const checkRemoteTrack = (pc) => {
|
|
1275
|
+
if (stopped || !pc)
|
|
1276
|
+
return false;
|
|
1277
|
+
const track = getRemoteAudioTrack(pc);
|
|
1278
|
+
if (!track)
|
|
1279
|
+
return false;
|
|
1280
|
+
attachTrackListeners(track);
|
|
1281
|
+
if (track.readyState !== "live") {
|
|
1282
|
+
logRemoteAudioError("[sip] incoming audio track not live", pc, {
|
|
1283
|
+
trackState: track.readyState
|
|
1284
|
+
});
|
|
1285
|
+
}
|
|
1286
|
+
return true;
|
|
1287
|
+
};
|
|
1288
|
+
const onRemoteEnded = () => {
|
|
1289
|
+
logRemoteAudioError("[sip] incoming audio track ended", attachedPc);
|
|
1290
|
+
};
|
|
1291
|
+
const onRemoteMuted = () => {
|
|
1292
|
+
logRemoteAudioError("[sip] incoming audio track muted", attachedPc);
|
|
1293
|
+
};
|
|
1294
|
+
const onPcStateChange = () => {
|
|
1295
|
+
if (stopped)
|
|
1296
|
+
return;
|
|
1297
|
+
if (exhausted) {
|
|
1298
|
+
if (exhaustedCheckUsed)
|
|
1299
|
+
return;
|
|
1300
|
+
exhaustedCheckUsed = true;
|
|
1301
|
+
if (checkRemoteTrack(attachedPc))
|
|
1302
|
+
stopRetry();
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
if (checkRemoteTrack(attachedPc))
|
|
1306
|
+
stopRetry();
|
|
1307
|
+
};
|
|
1308
|
+
const attachPcListeners = (pc) => {
|
|
1309
|
+
if (!pc || pc === attachedPc)
|
|
1310
|
+
return;
|
|
1311
|
+
if (attachedPc) {
|
|
1312
|
+
attachedPc.removeEventListener?.(
|
|
1313
|
+
"signalingstatechange",
|
|
1314
|
+
onPcStateChange
|
|
1315
|
+
);
|
|
1316
|
+
attachedPc.removeEventListener?.(
|
|
1317
|
+
"connectionstatechange",
|
|
1318
|
+
onPcStateChange
|
|
1319
|
+
);
|
|
1320
|
+
attachedPc.removeEventListener?.(
|
|
1321
|
+
"iceconnectionstatechange",
|
|
1322
|
+
onPcStateChange
|
|
1323
|
+
);
|
|
1324
|
+
attachedPc.removeEventListener?.("track", onTrack);
|
|
1325
|
+
}
|
|
1326
|
+
attachedPc = pc;
|
|
1327
|
+
attachedPc.addEventListener?.("signalingstatechange", onPcStateChange);
|
|
1328
|
+
attachedPc.addEventListener?.("connectionstatechange", onPcStateChange);
|
|
1329
|
+
attachedPc.addEventListener?.("iceconnectionstatechange", onPcStateChange);
|
|
1330
|
+
attachedPc.addEventListener?.("track", onTrack);
|
|
1331
|
+
};
|
|
1332
|
+
const clearRetryTimer = () => {
|
|
1333
|
+
if (!retryTimer)
|
|
1334
|
+
return;
|
|
1335
|
+
clearTimeout(retryTimer);
|
|
1336
|
+
retryTimer = null;
|
|
1337
|
+
};
|
|
1338
|
+
const stopRetry = () => {
|
|
1339
|
+
if (stopped)
|
|
1340
|
+
return;
|
|
1341
|
+
stopped = true;
|
|
1342
|
+
clearRetryTimer();
|
|
1343
|
+
if (attachedPc) {
|
|
1344
|
+
attachedPc.removeEventListener?.(
|
|
1345
|
+
"signalingstatechange",
|
|
1346
|
+
onPcStateChange
|
|
1347
|
+
);
|
|
1348
|
+
attachedPc.removeEventListener?.(
|
|
1349
|
+
"connectionstatechange",
|
|
1350
|
+
onPcStateChange
|
|
1351
|
+
);
|
|
1352
|
+
attachedPc.removeEventListener?.(
|
|
1353
|
+
"iceconnectionstatechange",
|
|
1354
|
+
onPcStateChange
|
|
1355
|
+
);
|
|
1356
|
+
attachedPc.removeEventListener?.("track", onTrack);
|
|
1357
|
+
attachedPc = null;
|
|
1358
|
+
}
|
|
1359
|
+
if (attachedTrack) {
|
|
1360
|
+
attachedTrack.removeEventListener?.("ended", onRemoteEnded);
|
|
1361
|
+
attachedTrack.removeEventListener?.("mute", onRemoteMuted);
|
|
1362
|
+
attachedTrack = null;
|
|
1363
|
+
}
|
|
1364
|
+
session.off?.("peerconnection", onPeer);
|
|
1365
|
+
session.off?.("confirmed", onConfirmed);
|
|
1366
|
+
session.off?.("ended", stopRetry);
|
|
1367
|
+
session.off?.("failed", stopRetry);
|
|
1368
|
+
};
|
|
1369
|
+
const scheduleRetry = (pc) => {
|
|
1370
|
+
if (stopped || retryScheduled || exhausted)
|
|
1371
|
+
return;
|
|
1372
|
+
if (attempts >= maxAttempts) {
|
|
1373
|
+
logRemoteAudioError(
|
|
1374
|
+
"[sip] incoming audio bind failed: max retries reached",
|
|
1375
|
+
pc,
|
|
1376
|
+
{ attempts }
|
|
1377
|
+
);
|
|
1378
|
+
exhausted = true;
|
|
1379
|
+
clearRetryTimer();
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
retryScheduled = true;
|
|
1383
|
+
attempts += 1;
|
|
1384
|
+
retryTimer = setTimeout(() => {
|
|
1385
|
+
retryScheduled = false;
|
|
1386
|
+
retryTimer = null;
|
|
1387
|
+
if (checkRemoteTrack(pc)) {
|
|
1388
|
+
stopRetry();
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
if (!pc)
|
|
1392
|
+
logMissingReceiver(pc, "missing peerconnection");
|
|
1393
|
+
scheduleRetry(pc);
|
|
1394
|
+
}, retryDelayMs);
|
|
1395
|
+
};
|
|
1396
|
+
const onTrack = () => {
|
|
1397
|
+
if (stopped)
|
|
1398
|
+
return;
|
|
1399
|
+
if (exhausted) {
|
|
1400
|
+
if (exhaustedCheckUsed)
|
|
1401
|
+
return;
|
|
1402
|
+
exhaustedCheckUsed = true;
|
|
1403
|
+
if (checkRemoteTrack(attachedPc))
|
|
1404
|
+
stopRetry();
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
if (checkRemoteTrack(attachedPc))
|
|
1408
|
+
stopRetry();
|
|
1409
|
+
};
|
|
1410
|
+
const onPeer = (data) => {
|
|
1411
|
+
if (stopped)
|
|
1412
|
+
return;
|
|
1413
|
+
attachPcListeners(data.peerconnection);
|
|
1414
|
+
if (exhausted) {
|
|
1415
|
+
if (exhaustedCheckUsed)
|
|
1416
|
+
return;
|
|
1417
|
+
exhaustedCheckUsed = true;
|
|
1418
|
+
if (checkRemoteTrack(data.peerconnection))
|
|
1419
|
+
stopRetry();
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
if (checkRemoteTrack(data.peerconnection)) {
|
|
1423
|
+
stopRetry();
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1426
|
+
scheduleRetry(data.peerconnection);
|
|
1427
|
+
};
|
|
1428
|
+
const onConfirmed = () => {
|
|
1429
|
+
if (stopped)
|
|
1430
|
+
return;
|
|
1431
|
+
const currentPc = session?.connection ?? attachedPc;
|
|
1432
|
+
if (exhausted) {
|
|
1433
|
+
if (exhaustedCheckUsed)
|
|
1434
|
+
return;
|
|
1435
|
+
exhaustedCheckUsed = true;
|
|
1436
|
+
if (checkRemoteTrack(currentPc))
|
|
1437
|
+
stopRetry();
|
|
1438
|
+
return;
|
|
1439
|
+
}
|
|
1440
|
+
if (checkRemoteTrack(currentPc)) {
|
|
1441
|
+
stopRetry();
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
logMissingReceiver(currentPc, "confirmed without remote track");
|
|
1445
|
+
scheduleRetry(currentPc);
|
|
1446
|
+
};
|
|
1447
|
+
const existingPc = session?.connection;
|
|
1448
|
+
if (!checkRemoteTrack(existingPc)) {
|
|
1449
|
+
if (existingPc) {
|
|
1450
|
+
attachPcListeners(existingPc);
|
|
1451
|
+
scheduleRetry(existingPc);
|
|
1452
|
+
}
|
|
1453
|
+
session.on?.("peerconnection", onPeer);
|
|
1454
|
+
}
|
|
1455
|
+
session.on?.("confirmed", onConfirmed);
|
|
1456
|
+
session.on?.("ended", stopRetry);
|
|
1457
|
+
session.on?.("failed", stopRetry);
|
|
1458
|
+
}
|
|
1459
|
+
attachCallStatsLogging(sessionId, session) {
|
|
1460
|
+
const onConfirmed = () => {
|
|
1461
|
+
sipDebugLogger.startCallStatsLogging(sessionId, session);
|
|
1462
|
+
};
|
|
1463
|
+
const onEnd = () => {
|
|
1464
|
+
sipDebugLogger.stopCallStatsLogging(sessionId);
|
|
1465
|
+
};
|
|
1466
|
+
session.on?.("confirmed", onConfirmed);
|
|
1467
|
+
session.on?.("ended", onEnd);
|
|
1468
|
+
session.on?.("failed", onEnd);
|
|
1469
|
+
}
|
|
959
1470
|
};
|
|
960
1471
|
|
|
961
1472
|
// src/jssip-lib/sip/micRecovery.ts
|
|
@@ -1016,15 +1527,11 @@ var MicRecoveryManager = class {
|
|
|
1016
1527
|
const senderLive = sender?.track?.readyState === "live";
|
|
1017
1528
|
if (trackLive && senderLive)
|
|
1018
1529
|
return;
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
},
|
|
1025
|
-
"MICROPHONE_DROPPED",
|
|
1026
|
-
"microphone dropped"
|
|
1027
|
-
);
|
|
1530
|
+
sipDebugLogger.logMicRecoveryDrop({
|
|
1531
|
+
sessionId,
|
|
1532
|
+
trackLive,
|
|
1533
|
+
senderLive
|
|
1534
|
+
});
|
|
1028
1535
|
retries += 1;
|
|
1029
1536
|
if (trackLive && !senderLive && track) {
|
|
1030
1537
|
await rtc.replaceAudioTrack(track);
|
|
@@ -1143,6 +1650,7 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
1143
1650
|
});
|
|
1144
1651
|
const debug = cfgDebug ?? this.getPersistedDebug() ?? this.debugPattern;
|
|
1145
1652
|
this.userAgent.start(uri, password, uaCfg, { debug });
|
|
1653
|
+
this.lifecycle.setDebugEnabled(Boolean(debug));
|
|
1146
1654
|
this.attachUAHandlers();
|
|
1147
1655
|
this.attachBeforeUnload();
|
|
1148
1656
|
this.syncDebugInspector(debug);
|
|
@@ -1223,6 +1731,7 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
1223
1731
|
setDebug(debug) {
|
|
1224
1732
|
this.debugPattern = debug;
|
|
1225
1733
|
this.userAgent.setDebug(debug);
|
|
1734
|
+
this.lifecycle.setDebugEnabled(Boolean(debug));
|
|
1226
1735
|
this.syncDebugInspector(debug);
|
|
1227
1736
|
}
|
|
1228
1737
|
attachSessionHandlers(sessionId, session) {
|
|
@@ -1436,14 +1945,17 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
1436
1945
|
syncDebugInspector(debug) {
|
|
1437
1946
|
if (typeof window === "undefined")
|
|
1438
1947
|
return;
|
|
1439
|
-
this.
|
|
1948
|
+
const persisted = this.getPersistedDebug();
|
|
1949
|
+
const effectiveDebug = debug ?? persisted ?? this.debugPattern;
|
|
1950
|
+
this.lifecycle.setDebugEnabled(Boolean(effectiveDebug));
|
|
1951
|
+
this.toggleStateLogger(Boolean(effectiveDebug));
|
|
1440
1952
|
const win = window;
|
|
1441
1953
|
const disabledInspector = () => {
|
|
1442
1954
|
console.warn("SIP debug inspector disabled; enable debug to inspect.");
|
|
1443
1955
|
return null;
|
|
1444
1956
|
};
|
|
1445
|
-
win.sipState = () =>
|
|
1446
|
-
win.sipSessions = () =>
|
|
1957
|
+
win.sipState = () => effectiveDebug ? this.stateStore.getState() : disabledInspector();
|
|
1958
|
+
win.sipSessions = () => effectiveDebug ? this.getSessions() : disabledInspector();
|
|
1447
1959
|
}
|
|
1448
1960
|
toggleStateLogger(enabled) {
|
|
1449
1961
|
if (!enabled) {
|
|
@@ -1456,22 +1968,10 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
1456
1968
|
let prev = this.stateStore.getState();
|
|
1457
1969
|
console.info("[sip][state]", { initial: true }, prev);
|
|
1458
1970
|
this.stateLogOff = this.stateStore.onChange((next) => {
|
|
1459
|
-
|
|
1460
|
-
if (changes) {
|
|
1461
|
-
console.info("[sip][state]", changes, next);
|
|
1462
|
-
}
|
|
1971
|
+
console.info("[sip][state]", next);
|
|
1463
1972
|
prev = next;
|
|
1464
1973
|
});
|
|
1465
1974
|
}
|
|
1466
|
-
diffState(prev, next) {
|
|
1467
|
-
const changed = {};
|
|
1468
|
-
for (const key of Object.keys(next)) {
|
|
1469
|
-
if (prev[key] !== next[key]) {
|
|
1470
|
-
changed[key] = { from: prev[key], to: next[key] };
|
|
1471
|
-
}
|
|
1472
|
-
}
|
|
1473
|
-
return Object.keys(changed).length ? changed : null;
|
|
1474
|
-
}
|
|
1475
1975
|
getPersistedDebug() {
|
|
1476
1976
|
if (typeof window === "undefined")
|
|
1477
1977
|
return void 0;
|