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