react-jssip-kit 0.7.6 → 0.7.7
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 +250 -166
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +0 -14
- package/dist/index.d.ts +0 -14
- package/dist/index.js +250 -166
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -481,7 +481,6 @@ function createSessionHandlers(deps) {
|
|
|
481
481
|
rtc,
|
|
482
482
|
detachSessionHandlers,
|
|
483
483
|
onSessionFailed,
|
|
484
|
-
onSessionConfirmed,
|
|
485
484
|
sessionId
|
|
486
485
|
} = deps;
|
|
487
486
|
return {
|
|
@@ -502,7 +501,7 @@ function createSessionHandlers(deps) {
|
|
|
502
501
|
},
|
|
503
502
|
confirmed: (e) => {
|
|
504
503
|
emitter.emit("confirmed", e);
|
|
505
|
-
|
|
504
|
+
deps.enableMicrophoneRecovery?.(sessionId);
|
|
506
505
|
},
|
|
507
506
|
ended: (e) => {
|
|
508
507
|
emitter.emit("ended", e);
|
|
@@ -614,8 +613,8 @@ var WebRTCSessionController = class {
|
|
|
614
613
|
}
|
|
615
614
|
cleanup(stopTracks = true) {
|
|
616
615
|
const pc = this.getPC();
|
|
616
|
+
const isClosed = pc?.connectionState === "closed" || pc?.signalingState === "closed";
|
|
617
617
|
if (pc && typeof pc.getSenders === "function") {
|
|
618
|
-
const isClosed = pc.connectionState === "closed" || pc.signalingState === "closed";
|
|
619
618
|
if (!isClosed) {
|
|
620
619
|
for (const s of pc.getSenders()) {
|
|
621
620
|
try {
|
|
@@ -626,8 +625,14 @@ var WebRTCSessionController = class {
|
|
|
626
625
|
}
|
|
627
626
|
}
|
|
628
627
|
if (stopTracks && this.mediaStream) {
|
|
629
|
-
|
|
628
|
+
const senderTracks = pc && !isClosed ? new Set(
|
|
629
|
+
pc.getSenders().map((s) => s.track).filter((t) => Boolean(t))
|
|
630
|
+
) : null;
|
|
631
|
+
for (const t of this.mediaStream.getTracks()) {
|
|
632
|
+
if (senderTracks?.has(t))
|
|
633
|
+
continue;
|
|
630
634
|
t.stop();
|
|
635
|
+
}
|
|
631
636
|
}
|
|
632
637
|
this.mediaStream = null;
|
|
633
638
|
this.currentSession = null;
|
|
@@ -706,29 +711,14 @@ var WebRTCSessionController = class {
|
|
|
706
711
|
var SessionManager = class {
|
|
707
712
|
constructor() {
|
|
708
713
|
this.entries = /* @__PURE__ */ new Map();
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
enqueueOutgoingMedia(stream) {
|
|
717
|
-
this.pendingMediaQueue.push({ stream, addedAt: Date.now() });
|
|
718
|
-
}
|
|
719
|
-
dequeueOutgoingMedia() {
|
|
720
|
-
const now = Date.now();
|
|
721
|
-
while (this.pendingMediaQueue.length) {
|
|
722
|
-
const next = this.pendingMediaQueue.shift();
|
|
723
|
-
if (!next)
|
|
724
|
-
break;
|
|
725
|
-
if (now - next.addedAt <= this.pendingMediaTtlMs) {
|
|
726
|
-
return next.stream;
|
|
727
|
-
} else {
|
|
728
|
-
next.stream.getTracks().forEach((t) => t.stop());
|
|
729
|
-
}
|
|
714
|
+
}
|
|
715
|
+
stopMediaStream(stream) {
|
|
716
|
+
if (!stream)
|
|
717
|
+
return;
|
|
718
|
+
for (const t of stream.getTracks()) {
|
|
719
|
+
if (t.readyState !== "ended")
|
|
720
|
+
t.stop();
|
|
730
721
|
}
|
|
731
|
-
return null;
|
|
732
722
|
}
|
|
733
723
|
getOrCreateRtc(sessionId, session) {
|
|
734
724
|
let entry = this.entries.get(sessionId);
|
|
@@ -770,6 +760,9 @@ var SessionManager = class {
|
|
|
770
760
|
session: null,
|
|
771
761
|
media: null
|
|
772
762
|
};
|
|
763
|
+
if (entry.media && entry.media !== stream) {
|
|
764
|
+
this.stopMediaStream(entry.media);
|
|
765
|
+
}
|
|
773
766
|
entry.media = stream;
|
|
774
767
|
entry.rtc.setMediaStream(stream);
|
|
775
768
|
this.entries.set(sessionId, entry);
|
|
@@ -799,15 +792,16 @@ var SessionManager = class {
|
|
|
799
792
|
const entry = this.entries.get(sessionId);
|
|
800
793
|
if (entry) {
|
|
801
794
|
entry.rtc.cleanup();
|
|
795
|
+
this.stopMediaStream(entry.media);
|
|
802
796
|
this.entries.delete(sessionId);
|
|
803
797
|
}
|
|
804
798
|
}
|
|
805
799
|
cleanupAllSessions() {
|
|
806
800
|
for (const [, entry] of this.entries.entries()) {
|
|
807
801
|
entry.rtc.cleanup();
|
|
802
|
+
this.stopMediaStream(entry.media);
|
|
808
803
|
}
|
|
809
804
|
this.entries.clear();
|
|
810
|
-
this.pendingMediaQueue = [];
|
|
811
805
|
}
|
|
812
806
|
answer(sessionId, options) {
|
|
813
807
|
const rtc = this.getRtc(sessionId);
|
|
@@ -855,35 +849,102 @@ var SessionLifecycle = class {
|
|
|
855
849
|
}
|
|
856
850
|
handleNewRTCSession(e) {
|
|
857
851
|
const session = e.session;
|
|
858
|
-
const sessionId = String(
|
|
852
|
+
const sessionId = String(
|
|
853
|
+
session?.id ?? crypto.randomUUID?.() ?? Date.now()
|
|
854
|
+
);
|
|
859
855
|
const currentSessions = this.state.getState().sessions;
|
|
860
856
|
if (currentSessions.length >= this.getMaxSessionCount()) {
|
|
861
857
|
try {
|
|
862
|
-
session.terminate?.({
|
|
858
|
+
session.terminate?.({
|
|
859
|
+
status_code: 486,
|
|
860
|
+
reason_phrase: "Busy Here"
|
|
861
|
+
});
|
|
863
862
|
} catch {
|
|
864
863
|
}
|
|
865
864
|
if (e.originator === "remote") {
|
|
866
865
|
this.emit("missed", e);
|
|
866
|
+
} else {
|
|
867
|
+
this.emitError(
|
|
868
|
+
"max session count reached",
|
|
869
|
+
"MAX_SESSIONS_REACHED",
|
|
870
|
+
"max session count reached"
|
|
871
|
+
);
|
|
867
872
|
}
|
|
868
|
-
this.emitError("max session count reached", "MAX_SESSIONS_REACHED", "max session count reached");
|
|
869
873
|
return;
|
|
870
874
|
}
|
|
871
|
-
const outgoingMedia = e.originator === "local" ? this.sessionManager.dequeueOutgoingMedia() : null;
|
|
872
|
-
if (outgoingMedia)
|
|
873
|
-
this.sessionManager.setSessionMedia(sessionId, outgoingMedia);
|
|
874
875
|
const rtc = this.sessionManager.getOrCreateRtc(sessionId, session);
|
|
875
|
-
if (outgoingMedia)
|
|
876
|
-
rtc.setMediaStream(outgoingMedia);
|
|
877
876
|
this.sessionManager.setSession(sessionId, session);
|
|
878
877
|
this.attachSessionHandlers(sessionId, session);
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
878
|
+
if (e.originator === "local" && !rtc.mediaStream) {
|
|
879
|
+
const maxAttempts = 5;
|
|
880
|
+
const retryDelayMs = 500;
|
|
881
|
+
let attempts = 0;
|
|
882
|
+
let retryScheduled = false;
|
|
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);
|
|
885
940
|
}
|
|
886
|
-
|
|
941
|
+
session.on?.("ended", stopRetry);
|
|
942
|
+
session.on?.("failed", stopRetry);
|
|
943
|
+
}
|
|
944
|
+
holdOtherSessions(this.state, sessionId, (id) => {
|
|
945
|
+
const otherRtc = this.sessionManager.getRtc(id);
|
|
946
|
+
otherRtc?.hold();
|
|
947
|
+
});
|
|
887
948
|
const sdpHasVideo = e.request?.body && e.request.body.toString().includes("m=video") || session?.connection?.getReceivers?.()?.some((r) => r.track?.kind === "video");
|
|
888
949
|
upsertSessionState(this.state, sessionId, {
|
|
889
950
|
direction: e.originator,
|
|
@@ -897,6 +958,126 @@ var SessionLifecycle = class {
|
|
|
897
958
|
}
|
|
898
959
|
};
|
|
899
960
|
|
|
961
|
+
// src/jssip-lib/sip/micRecovery.ts
|
|
962
|
+
var MicRecoveryManager = class {
|
|
963
|
+
constructor(deps) {
|
|
964
|
+
this.enabled = false;
|
|
965
|
+
this.defaults = {
|
|
966
|
+
intervalMs: 2e3,
|
|
967
|
+
maxRetries: Infinity
|
|
968
|
+
};
|
|
969
|
+
this.active = /* @__PURE__ */ new Map();
|
|
970
|
+
this.deps = deps;
|
|
971
|
+
}
|
|
972
|
+
configure(config) {
|
|
973
|
+
if (typeof config.enabled === "boolean") {
|
|
974
|
+
this.enabled = config.enabled;
|
|
975
|
+
}
|
|
976
|
+
if (typeof config.intervalMs === "number") {
|
|
977
|
+
this.defaults.intervalMs = config.intervalMs;
|
|
978
|
+
}
|
|
979
|
+
if (typeof config.maxRetries === "number") {
|
|
980
|
+
this.defaults.maxRetries = config.maxRetries;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
enable(sessionId, options = {}) {
|
|
984
|
+
if (!this.enabled)
|
|
985
|
+
return () => {
|
|
986
|
+
};
|
|
987
|
+
this.disable(sessionId);
|
|
988
|
+
const intervalMs = options.intervalMs ?? this.defaults.intervalMs;
|
|
989
|
+
const maxRetries = options.maxRetries ?? this.defaults.maxRetries;
|
|
990
|
+
let retries = 0;
|
|
991
|
+
let stopped = false;
|
|
992
|
+
const startedAt = Date.now();
|
|
993
|
+
const warmupMs = Math.max(intervalMs * 2, 2e3);
|
|
994
|
+
const tick = async () => {
|
|
995
|
+
if (stopped || retries >= maxRetries)
|
|
996
|
+
return;
|
|
997
|
+
const rtc = this.deps.getRtc(sessionId);
|
|
998
|
+
const session2 = this.deps.getSession(sessionId);
|
|
999
|
+
if (!rtc || !session2)
|
|
1000
|
+
return;
|
|
1001
|
+
const sessionState = this.deps.getSessionState(sessionId);
|
|
1002
|
+
if (sessionState?.muted)
|
|
1003
|
+
return;
|
|
1004
|
+
const stream = rtc.mediaStream;
|
|
1005
|
+
const track = stream?.getAudioTracks?.()[0];
|
|
1006
|
+
const pc2 = session2?.connection;
|
|
1007
|
+
const sender = pc2?.getSenders?.()?.find((s) => s.track?.kind === "audio");
|
|
1008
|
+
if (!track && !sender)
|
|
1009
|
+
return;
|
|
1010
|
+
if (Date.now() - startedAt < warmupMs)
|
|
1011
|
+
return;
|
|
1012
|
+
if (pc2?.connectionState === "new" || pc2?.connectionState === "connecting" || pc2?.iceConnectionState === "new" || pc2?.iceConnectionState === "checking") {
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
const trackLive = track?.readyState === "live";
|
|
1016
|
+
const senderLive = sender?.track?.readyState === "live";
|
|
1017
|
+
if (trackLive && senderLive)
|
|
1018
|
+
return;
|
|
1019
|
+
this.deps.emitError(
|
|
1020
|
+
{
|
|
1021
|
+
cause: "microphone dropped",
|
|
1022
|
+
trackLive,
|
|
1023
|
+
senderLive
|
|
1024
|
+
},
|
|
1025
|
+
"MICROPHONE_DROPPED",
|
|
1026
|
+
"microphone dropped"
|
|
1027
|
+
);
|
|
1028
|
+
retries += 1;
|
|
1029
|
+
if (trackLive && !senderLive && track) {
|
|
1030
|
+
await rtc.replaceAudioTrack(track);
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
let nextStream;
|
|
1034
|
+
try {
|
|
1035
|
+
const deviceId = track?.getSettings?.().deviceId ?? sender?.track?.getSettings?.().deviceId;
|
|
1036
|
+
nextStream = await this.deps.requestMicrophoneStream(deviceId);
|
|
1037
|
+
} catch (err) {
|
|
1038
|
+
console.warn("[sip] mic recovery failed to get stream", err);
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
const nextTrack = nextStream.getAudioTracks()[0];
|
|
1042
|
+
if (!nextTrack)
|
|
1043
|
+
return;
|
|
1044
|
+
await rtc.replaceAudioTrack(nextTrack);
|
|
1045
|
+
this.deps.setSessionMedia(sessionId, nextStream);
|
|
1046
|
+
};
|
|
1047
|
+
const timer = setInterval(() => {
|
|
1048
|
+
void tick();
|
|
1049
|
+
}, intervalMs);
|
|
1050
|
+
void tick();
|
|
1051
|
+
const session = this.deps.getSession(sessionId);
|
|
1052
|
+
const pc = session?.connection;
|
|
1053
|
+
const onIceChange = () => {
|
|
1054
|
+
const state = pc?.iceConnectionState;
|
|
1055
|
+
if (state === "failed" || state === "disconnected")
|
|
1056
|
+
void tick();
|
|
1057
|
+
};
|
|
1058
|
+
pc?.addEventListener?.("iceconnectionstatechange", onIceChange);
|
|
1059
|
+
const stop = () => {
|
|
1060
|
+
stopped = true;
|
|
1061
|
+
clearInterval(timer);
|
|
1062
|
+
pc?.removeEventListener?.("iceconnectionstatechange", onIceChange);
|
|
1063
|
+
};
|
|
1064
|
+
this.active.set(sessionId, { stop });
|
|
1065
|
+
return stop;
|
|
1066
|
+
}
|
|
1067
|
+
disable(sessionId) {
|
|
1068
|
+
const entry = this.active.get(sessionId);
|
|
1069
|
+
if (!entry)
|
|
1070
|
+
return false;
|
|
1071
|
+
entry.stop();
|
|
1072
|
+
this.active.delete(sessionId);
|
|
1073
|
+
return true;
|
|
1074
|
+
}
|
|
1075
|
+
cleanupAll() {
|
|
1076
|
+
this.active.forEach((entry) => entry.stop());
|
|
1077
|
+
this.active.clear();
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1080
|
+
|
|
900
1081
|
// src/jssip-lib/sip/client.ts
|
|
901
1082
|
var SESSION_DEBUG_KEY = "sip-debug-enabled";
|
|
902
1083
|
var SipClient = class extends EventTargetEmitter {
|
|
@@ -907,12 +1088,6 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
907
1088
|
this.sessionHandlers = /* @__PURE__ */ new Map();
|
|
908
1089
|
this.maxSessionCount = Infinity;
|
|
909
1090
|
this.sessionManager = new SessionManager();
|
|
910
|
-
this.micRecovery = /* @__PURE__ */ new Map();
|
|
911
|
-
this.micRecoveryEnabled = false;
|
|
912
|
-
this.micRecoveryDefaults = {
|
|
913
|
-
intervalMs: 2e3,
|
|
914
|
-
maxRetries: Infinity
|
|
915
|
-
};
|
|
916
1091
|
this.errorHandler = options.errorHandler ?? new SipErrorHandler({
|
|
917
1092
|
formatter: options.formatError,
|
|
918
1093
|
messages: options.errorMessages
|
|
@@ -934,6 +1109,14 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
934
1109
|
attachSessionHandlers: (sessionId, session) => this.attachSessionHandlers(sessionId, session),
|
|
935
1110
|
getMaxSessionCount: () => this.maxSessionCount
|
|
936
1111
|
});
|
|
1112
|
+
this.micRecovery = new MicRecoveryManager({
|
|
1113
|
+
getRtc: (sessionId) => this.sessionManager.getRtc(sessionId),
|
|
1114
|
+
getSession: (sessionId) => this.sessionManager.getSession(sessionId),
|
|
1115
|
+
getSessionState: (sessionId) => this.stateStore.getState().sessions.find((s) => s.id === sessionId),
|
|
1116
|
+
setSessionMedia: (sessionId, stream) => this.sessionManager.setSessionMedia(sessionId, stream),
|
|
1117
|
+
emitError: (raw, code, fallback) => this.emitError(raw, code, fallback),
|
|
1118
|
+
requestMicrophoneStream: (deviceId) => this.requestMicrophoneStreamInternal(deviceId)
|
|
1119
|
+
});
|
|
937
1120
|
if (typeof window !== "undefined") {
|
|
938
1121
|
window.sipDebugBridge = (debug) => this.setDebug(debug ?? true);
|
|
939
1122
|
}
|
|
@@ -950,18 +1133,14 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
950
1133
|
micRecoveryIntervalMs,
|
|
951
1134
|
micRecoveryMaxRetries,
|
|
952
1135
|
maxSessionCount,
|
|
953
|
-
pendingMediaTtlMs,
|
|
954
1136
|
...uaCfg
|
|
955
1137
|
} = config;
|
|
956
1138
|
this.maxSessionCount = typeof maxSessionCount === "number" ? maxSessionCount : Infinity;
|
|
957
|
-
this.
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
this.micRecoveryDefaults.maxRetries = micRecoveryMaxRetries;
|
|
963
|
-
}
|
|
964
|
-
this.sessionManager.setPendingMediaTtl(pendingMediaTtlMs);
|
|
1139
|
+
this.micRecovery.configure({
|
|
1140
|
+
enabled: Boolean(enableMicRecovery),
|
|
1141
|
+
intervalMs: micRecoveryIntervalMs,
|
|
1142
|
+
maxRetries: micRecoveryMaxRetries
|
|
1143
|
+
});
|
|
965
1144
|
const debug = cfgDebug ?? this.getPersistedDebug() ?? this.debugPattern;
|
|
966
1145
|
this.userAgent.start(uri, password, uaCfg, { debug });
|
|
967
1146
|
this.attachUAHandlers();
|
|
@@ -981,10 +1160,15 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
981
1160
|
call(target, callOptions = {}) {
|
|
982
1161
|
try {
|
|
983
1162
|
const opts = this.ensureMediaConstraints(callOptions);
|
|
984
|
-
if (opts.mediaStream)
|
|
985
|
-
this.sessionManager.enqueueOutgoingMedia(opts.mediaStream);
|
|
986
1163
|
const ua = this.userAgent.getUA();
|
|
987
|
-
ua?.call(target, opts);
|
|
1164
|
+
const session = ua?.call(target, opts);
|
|
1165
|
+
if (session && opts.mediaStream) {
|
|
1166
|
+
const sessionId = String(session?.id ?? "");
|
|
1167
|
+
if (sessionId) {
|
|
1168
|
+
this.sessionManager.setSessionMedia(sessionId, opts.mediaStream);
|
|
1169
|
+
this.sessionManager.setSession(sessionId, session);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
988
1172
|
} catch (e) {
|
|
989
1173
|
const err = this.emitError(e, "CALL_FAILED", "call failed");
|
|
990
1174
|
this.cleanupAllSessions();
|
|
@@ -1074,14 +1258,13 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
1074
1258
|
cleanupSession(sessionId, session) {
|
|
1075
1259
|
const targetSession = session ?? this.sessionManager.getSession(sessionId) ?? this.sessionManager.getRtc(sessionId)?.currentSession;
|
|
1076
1260
|
this.detachSessionHandlers(sessionId, targetSession);
|
|
1077
|
-
this.
|
|
1261
|
+
this.micRecovery.disable(sessionId);
|
|
1078
1262
|
this.sessionManager.cleanupSession(sessionId);
|
|
1079
1263
|
removeSessionState(this.stateStore, sessionId);
|
|
1080
1264
|
}
|
|
1081
1265
|
cleanupAllSessions() {
|
|
1082
1266
|
this.sessionManager.cleanupAllSessions();
|
|
1083
|
-
this.micRecovery.
|
|
1084
|
-
this.micRecovery.clear();
|
|
1267
|
+
this.micRecovery.cleanupAll();
|
|
1085
1268
|
this.sessionHandlers.clear();
|
|
1086
1269
|
this.stateStore.setState({
|
|
1087
1270
|
sessions: [],
|
|
@@ -1095,12 +1278,9 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
1095
1278
|
state: this.stateStore,
|
|
1096
1279
|
rtc,
|
|
1097
1280
|
detachSessionHandlers: () => this.cleanupSession(sessionId, session),
|
|
1281
|
+
emitError: (raw, code, fallback) => this.emitError(raw, code, fallback),
|
|
1098
1282
|
onSessionFailed: (err, event) => this.onSessionFailed(err, event),
|
|
1099
|
-
|
|
1100
|
-
if (this.micRecoveryEnabled) {
|
|
1101
|
-
this.enableMicrophoneRecovery(confirmedSessionId);
|
|
1102
|
-
}
|
|
1103
|
-
},
|
|
1283
|
+
enableMicrophoneRecovery: (confirmedSessionId) => this.micRecovery.enable(confirmedSessionId),
|
|
1104
1284
|
sessionId
|
|
1105
1285
|
});
|
|
1106
1286
|
}
|
|
@@ -1208,102 +1388,6 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
1208
1388
|
setSessionMedia(sessionId, stream) {
|
|
1209
1389
|
this.sessionManager.setSessionMedia(sessionId, stream);
|
|
1210
1390
|
}
|
|
1211
|
-
enableMicrophoneRecovery(sessionId, options = {}) {
|
|
1212
|
-
const resolved = this.resolveExistingSessionId(sessionId);
|
|
1213
|
-
if (!resolved)
|
|
1214
|
-
return () => {
|
|
1215
|
-
};
|
|
1216
|
-
this.disableMicrophoneRecovery(resolved);
|
|
1217
|
-
const intervalMs = options.intervalMs ?? this.micRecoveryDefaults.intervalMs;
|
|
1218
|
-
const maxRetries = options.maxRetries ?? this.micRecoveryDefaults.maxRetries;
|
|
1219
|
-
let retries = 0;
|
|
1220
|
-
let stopped = false;
|
|
1221
|
-
const startedAt = Date.now();
|
|
1222
|
-
const warmupMs = Math.max(intervalMs * 2, 2e3);
|
|
1223
|
-
const tick = async () => {
|
|
1224
|
-
if (stopped || retries >= maxRetries)
|
|
1225
|
-
return;
|
|
1226
|
-
const rtc = this.sessionManager.getRtc(resolved);
|
|
1227
|
-
const session2 = this.sessionManager.getSession(resolved);
|
|
1228
|
-
if (!rtc || !session2)
|
|
1229
|
-
return;
|
|
1230
|
-
const sessionState = this.stateStore.getState().sessions.find((s) => s.id === resolved);
|
|
1231
|
-
if (sessionState?.muted)
|
|
1232
|
-
return;
|
|
1233
|
-
const stream = rtc.mediaStream;
|
|
1234
|
-
const track = stream?.getAudioTracks?.()[0];
|
|
1235
|
-
const pc2 = session2?.connection;
|
|
1236
|
-
const sender = pc2?.getSenders?.().find((s) => s.track?.kind === "audio");
|
|
1237
|
-
if (!track && !sender)
|
|
1238
|
-
return;
|
|
1239
|
-
if (Date.now() - startedAt < warmupMs)
|
|
1240
|
-
return;
|
|
1241
|
-
if (pc2?.connectionState === "new" || pc2?.connectionState === "connecting" || pc2?.iceConnectionState === "new" || pc2?.iceConnectionState === "checking") {
|
|
1242
|
-
return;
|
|
1243
|
-
}
|
|
1244
|
-
const trackLive = track?.readyState === "live";
|
|
1245
|
-
const senderLive = sender?.track?.readyState === "live";
|
|
1246
|
-
if (trackLive && senderLive)
|
|
1247
|
-
return;
|
|
1248
|
-
this.emitError(
|
|
1249
|
-
{
|
|
1250
|
-
cause: "microphone dropped",
|
|
1251
|
-
trackLive,
|
|
1252
|
-
senderLive
|
|
1253
|
-
},
|
|
1254
|
-
"MICROPHONE_DROPPED",
|
|
1255
|
-
"microphone dropped"
|
|
1256
|
-
);
|
|
1257
|
-
retries += 1;
|
|
1258
|
-
if (trackLive && !senderLive && track) {
|
|
1259
|
-
await rtc.replaceAudioTrack(track);
|
|
1260
|
-
return;
|
|
1261
|
-
}
|
|
1262
|
-
let nextStream;
|
|
1263
|
-
try {
|
|
1264
|
-
const deviceId = track?.getSettings?.().deviceId ?? sender?.track?.getSettings?.().deviceId;
|
|
1265
|
-
nextStream = await this.requestMicrophoneStreamInternal(deviceId);
|
|
1266
|
-
} catch (err) {
|
|
1267
|
-
console.warn("[sip] mic recovery failed to get stream", err);
|
|
1268
|
-
return;
|
|
1269
|
-
}
|
|
1270
|
-
const nextTrack = nextStream.getAudioTracks()[0];
|
|
1271
|
-
if (!nextTrack)
|
|
1272
|
-
return;
|
|
1273
|
-
await rtc.replaceAudioTrack(nextTrack);
|
|
1274
|
-
this.sessionManager.setSessionMedia(resolved, nextStream);
|
|
1275
|
-
};
|
|
1276
|
-
const timer = setInterval(() => {
|
|
1277
|
-
void tick();
|
|
1278
|
-
}, intervalMs);
|
|
1279
|
-
void tick();
|
|
1280
|
-
const session = this.sessionManager.getSession(resolved);
|
|
1281
|
-
const pc = session?.connection;
|
|
1282
|
-
const onIceChange = () => {
|
|
1283
|
-
const state = pc?.iceConnectionState;
|
|
1284
|
-
if (state === "failed" || state === "disconnected")
|
|
1285
|
-
void tick();
|
|
1286
|
-
};
|
|
1287
|
-
pc?.addEventListener?.("iceconnectionstatechange", onIceChange);
|
|
1288
|
-
const stop = () => {
|
|
1289
|
-
stopped = true;
|
|
1290
|
-
clearInterval(timer);
|
|
1291
|
-
pc?.removeEventListener?.("iceconnectionstatechange", onIceChange);
|
|
1292
|
-
};
|
|
1293
|
-
this.micRecovery.set(resolved, { stop });
|
|
1294
|
-
return stop;
|
|
1295
|
-
}
|
|
1296
|
-
disableMicrophoneRecovery(sessionId) {
|
|
1297
|
-
const resolved = this.resolveExistingSessionId(sessionId);
|
|
1298
|
-
if (!resolved)
|
|
1299
|
-
return false;
|
|
1300
|
-
const entry = this.micRecovery.get(resolved);
|
|
1301
|
-
if (!entry)
|
|
1302
|
-
return false;
|
|
1303
|
-
entry.stop();
|
|
1304
|
-
this.micRecovery.delete(resolved);
|
|
1305
|
-
return true;
|
|
1306
|
-
}
|
|
1307
1391
|
switchCameraSession(sessionId, track) {
|
|
1308
1392
|
if (!this.sessionExists(sessionId))
|
|
1309
1393
|
return false;
|
|
@@ -1469,8 +1553,6 @@ function useSipActions() {
|
|
|
1469
1553
|
getSessionIds: () => client.getSessionIds(),
|
|
1470
1554
|
getSessions: () => client.getSessions(),
|
|
1471
1555
|
setSessionMedia: (...args) => client.setSessionMedia(...args),
|
|
1472
|
-
enableMicrophoneRecovery: (...args) => client.enableMicrophoneRecovery(...args),
|
|
1473
|
-
disableMicrophoneRecovery: (...args) => client.disableMicrophoneRecovery(...args),
|
|
1474
1556
|
switchCamera: (...args) => client.switchCameraSession(...args),
|
|
1475
1557
|
enableVideo: (...args) => client.enableVideoSession(...args),
|
|
1476
1558
|
disableVideo: (...args) => client.disableVideoSession(...args)
|
|
@@ -1544,6 +1626,7 @@ function createCallPlayer(audioEl) {
|
|
|
1544
1626
|
return () => session.off("peerconnection", onPeer);
|
|
1545
1627
|
};
|
|
1546
1628
|
function bindToSession(session) {
|
|
1629
|
+
clearAudioStream(audioEl.srcObject);
|
|
1547
1630
|
if (session?.direction === "outgoing" && session.connection instanceof RTCPeerConnection) {
|
|
1548
1631
|
cleanupTrackListener = dispose(cleanupTrackListener);
|
|
1549
1632
|
cleanupTrackListener = attachTracks(session.connection);
|
|
@@ -1560,6 +1643,7 @@ function createCallPlayer(audioEl) {
|
|
|
1560
1643
|
const e = payload?.data;
|
|
1561
1644
|
cleanupSessionPeerListener = dispose(cleanupSessionPeerListener);
|
|
1562
1645
|
cleanupTrackListener = dispose(cleanupTrackListener);
|
|
1646
|
+
clearAudioStream(audioEl.srcObject);
|
|
1563
1647
|
if (!e?.session)
|
|
1564
1648
|
return;
|
|
1565
1649
|
cleanupSessionPeerListener = listenSessionPeerconnection(e.session);
|