react-jssip-kit 0.7.5 → 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 -162
- 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 -162
- 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 {
|
|
@@ -490,7 +489,6 @@ function createSessionHandlers(deps) {
|
|
|
490
489
|
},
|
|
491
490
|
accepted: (e) => {
|
|
492
491
|
emitter.emit("accepted", e);
|
|
493
|
-
onSessionAccepted?.(sessionId);
|
|
494
492
|
state.batchSet({
|
|
495
493
|
sessions: state.getState().sessions.map(
|
|
496
494
|
(s) => s.id === sessionId ? {
|
|
@@ -503,7 +501,7 @@ function createSessionHandlers(deps) {
|
|
|
503
501
|
},
|
|
504
502
|
confirmed: (e) => {
|
|
505
503
|
emitter.emit("confirmed", e);
|
|
506
|
-
|
|
504
|
+
deps.enableMicrophoneRecovery?.(sessionId);
|
|
507
505
|
},
|
|
508
506
|
ended: (e) => {
|
|
509
507
|
emitter.emit("ended", e);
|
|
@@ -615,8 +613,8 @@ var WebRTCSessionController = class {
|
|
|
615
613
|
}
|
|
616
614
|
cleanup(stopTracks = true) {
|
|
617
615
|
const pc = this.getPC();
|
|
616
|
+
const isClosed = pc?.connectionState === "closed" || pc?.signalingState === "closed";
|
|
618
617
|
if (pc && typeof pc.getSenders === "function") {
|
|
619
|
-
const isClosed = pc.connectionState === "closed" || pc.signalingState === "closed";
|
|
620
618
|
if (!isClosed) {
|
|
621
619
|
for (const s of pc.getSenders()) {
|
|
622
620
|
try {
|
|
@@ -627,8 +625,14 @@ var WebRTCSessionController = class {
|
|
|
627
625
|
}
|
|
628
626
|
}
|
|
629
627
|
if (stopTracks && this.mediaStream) {
|
|
630
|
-
|
|
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;
|
|
631
634
|
t.stop();
|
|
635
|
+
}
|
|
632
636
|
}
|
|
633
637
|
this.mediaStream = null;
|
|
634
638
|
this.currentSession = null;
|
|
@@ -707,29 +711,14 @@ var WebRTCSessionController = class {
|
|
|
707
711
|
var SessionManager = class {
|
|
708
712
|
constructor() {
|
|
709
713
|
this.entries = /* @__PURE__ */ new Map();
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
enqueueOutgoingMedia(stream) {
|
|
718
|
-
this.pendingMediaQueue.push({ stream, addedAt: Date.now() });
|
|
719
|
-
}
|
|
720
|
-
dequeueOutgoingMedia() {
|
|
721
|
-
const now = Date.now();
|
|
722
|
-
while (this.pendingMediaQueue.length) {
|
|
723
|
-
const next = this.pendingMediaQueue.shift();
|
|
724
|
-
if (!next)
|
|
725
|
-
break;
|
|
726
|
-
if (now - next.addedAt <= this.pendingMediaTtlMs) {
|
|
727
|
-
return next.stream;
|
|
728
|
-
} else {
|
|
729
|
-
next.stream.getTracks().forEach((t) => t.stop());
|
|
730
|
-
}
|
|
714
|
+
}
|
|
715
|
+
stopMediaStream(stream) {
|
|
716
|
+
if (!stream)
|
|
717
|
+
return;
|
|
718
|
+
for (const t of stream.getTracks()) {
|
|
719
|
+
if (t.readyState !== "ended")
|
|
720
|
+
t.stop();
|
|
731
721
|
}
|
|
732
|
-
return null;
|
|
733
722
|
}
|
|
734
723
|
getOrCreateRtc(sessionId, session) {
|
|
735
724
|
let entry = this.entries.get(sessionId);
|
|
@@ -771,6 +760,9 @@ var SessionManager = class {
|
|
|
771
760
|
session: null,
|
|
772
761
|
media: null
|
|
773
762
|
};
|
|
763
|
+
if (entry.media && entry.media !== stream) {
|
|
764
|
+
this.stopMediaStream(entry.media);
|
|
765
|
+
}
|
|
774
766
|
entry.media = stream;
|
|
775
767
|
entry.rtc.setMediaStream(stream);
|
|
776
768
|
this.entries.set(sessionId, entry);
|
|
@@ -800,15 +792,16 @@ var SessionManager = class {
|
|
|
800
792
|
const entry = this.entries.get(sessionId);
|
|
801
793
|
if (entry) {
|
|
802
794
|
entry.rtc.cleanup();
|
|
795
|
+
this.stopMediaStream(entry.media);
|
|
803
796
|
this.entries.delete(sessionId);
|
|
804
797
|
}
|
|
805
798
|
}
|
|
806
799
|
cleanupAllSessions() {
|
|
807
800
|
for (const [, entry] of this.entries.entries()) {
|
|
808
801
|
entry.rtc.cleanup();
|
|
802
|
+
this.stopMediaStream(entry.media);
|
|
809
803
|
}
|
|
810
804
|
this.entries.clear();
|
|
811
|
-
this.pendingMediaQueue = [];
|
|
812
805
|
}
|
|
813
806
|
answer(sessionId, options) {
|
|
814
807
|
const rtc = this.getRtc(sessionId);
|
|
@@ -856,35 +849,102 @@ var SessionLifecycle = class {
|
|
|
856
849
|
}
|
|
857
850
|
handleNewRTCSession(e) {
|
|
858
851
|
const session = e.session;
|
|
859
|
-
const sessionId = String(
|
|
852
|
+
const sessionId = String(
|
|
853
|
+
session?.id ?? crypto.randomUUID?.() ?? Date.now()
|
|
854
|
+
);
|
|
860
855
|
const currentSessions = this.state.getState().sessions;
|
|
861
856
|
if (currentSessions.length >= this.getMaxSessionCount()) {
|
|
862
857
|
try {
|
|
863
|
-
session.terminate?.({
|
|
858
|
+
session.terminate?.({
|
|
859
|
+
status_code: 486,
|
|
860
|
+
reason_phrase: "Busy Here"
|
|
861
|
+
});
|
|
864
862
|
} catch {
|
|
865
863
|
}
|
|
866
864
|
if (e.originator === "remote") {
|
|
867
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
|
+
);
|
|
868
872
|
}
|
|
869
|
-
this.emitError("max session count reached", "MAX_SESSIONS_REACHED", "max session count reached");
|
|
870
873
|
return;
|
|
871
874
|
}
|
|
872
|
-
const outgoingMedia = e.originator === "local" ? this.sessionManager.dequeueOutgoingMedia() : null;
|
|
873
|
-
if (outgoingMedia)
|
|
874
|
-
this.sessionManager.setSessionMedia(sessionId, outgoingMedia);
|
|
875
875
|
const rtc = this.sessionManager.getOrCreateRtc(sessionId, session);
|
|
876
|
-
if (outgoingMedia)
|
|
877
|
-
rtc.setMediaStream(outgoingMedia);
|
|
878
876
|
this.sessionManager.setSession(sessionId, session);
|
|
879
877
|
this.attachSessionHandlers(sessionId, session);
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
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);
|
|
886
940
|
}
|
|
887
|
-
|
|
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
|
+
});
|
|
888
948
|
const sdpHasVideo = e.request?.body && e.request.body.toString().includes("m=video") || session?.connection?.getReceivers?.()?.some((r) => r.track?.kind === "video");
|
|
889
949
|
upsertSessionState(this.state, sessionId, {
|
|
890
950
|
direction: e.originator,
|
|
@@ -898,6 +958,126 @@ var SessionLifecycle = class {
|
|
|
898
958
|
}
|
|
899
959
|
};
|
|
900
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
|
+
|
|
901
1081
|
// src/jssip-lib/sip/client.ts
|
|
902
1082
|
var SESSION_DEBUG_KEY = "sip-debug-enabled";
|
|
903
1083
|
var SipClient = class extends EventTargetEmitter {
|
|
@@ -908,12 +1088,6 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
908
1088
|
this.sessionHandlers = /* @__PURE__ */ new Map();
|
|
909
1089
|
this.maxSessionCount = Infinity;
|
|
910
1090
|
this.sessionManager = new SessionManager();
|
|
911
|
-
this.micRecovery = /* @__PURE__ */ new Map();
|
|
912
|
-
this.micRecoveryEnabled = false;
|
|
913
|
-
this.micRecoveryDefaults = {
|
|
914
|
-
intervalMs: 2e3,
|
|
915
|
-
maxRetries: Infinity
|
|
916
|
-
};
|
|
917
1091
|
this.errorHandler = options.errorHandler ?? new SipErrorHandler({
|
|
918
1092
|
formatter: options.formatError,
|
|
919
1093
|
messages: options.errorMessages
|
|
@@ -935,6 +1109,14 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
935
1109
|
attachSessionHandlers: (sessionId, session) => this.attachSessionHandlers(sessionId, session),
|
|
936
1110
|
getMaxSessionCount: () => this.maxSessionCount
|
|
937
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
|
+
});
|
|
938
1120
|
if (typeof window !== "undefined") {
|
|
939
1121
|
window.sipDebugBridge = (debug) => this.setDebug(debug ?? true);
|
|
940
1122
|
}
|
|
@@ -951,18 +1133,14 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
951
1133
|
micRecoveryIntervalMs,
|
|
952
1134
|
micRecoveryMaxRetries,
|
|
953
1135
|
maxSessionCount,
|
|
954
|
-
pendingMediaTtlMs,
|
|
955
1136
|
...uaCfg
|
|
956
1137
|
} = config;
|
|
957
1138
|
this.maxSessionCount = typeof maxSessionCount === "number" ? maxSessionCount : Infinity;
|
|
958
|
-
this.
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
this.micRecoveryDefaults.maxRetries = micRecoveryMaxRetries;
|
|
964
|
-
}
|
|
965
|
-
this.sessionManager.setPendingMediaTtl(pendingMediaTtlMs);
|
|
1139
|
+
this.micRecovery.configure({
|
|
1140
|
+
enabled: Boolean(enableMicRecovery),
|
|
1141
|
+
intervalMs: micRecoveryIntervalMs,
|
|
1142
|
+
maxRetries: micRecoveryMaxRetries
|
|
1143
|
+
});
|
|
966
1144
|
const debug = cfgDebug ?? this.getPersistedDebug() ?? this.debugPattern;
|
|
967
1145
|
this.userAgent.start(uri, password, uaCfg, { debug });
|
|
968
1146
|
this.attachUAHandlers();
|
|
@@ -982,10 +1160,15 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
982
1160
|
call(target, callOptions = {}) {
|
|
983
1161
|
try {
|
|
984
1162
|
const opts = this.ensureMediaConstraints(callOptions);
|
|
985
|
-
if (opts.mediaStream)
|
|
986
|
-
this.sessionManager.enqueueOutgoingMedia(opts.mediaStream);
|
|
987
1163
|
const ua = this.userAgent.getUA();
|
|
988
|
-
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
|
+
}
|
|
989
1172
|
} catch (e) {
|
|
990
1173
|
const err = this.emitError(e, "CALL_FAILED", "call failed");
|
|
991
1174
|
this.cleanupAllSessions();
|
|
@@ -1075,14 +1258,13 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
1075
1258
|
cleanupSession(sessionId, session) {
|
|
1076
1259
|
const targetSession = session ?? this.sessionManager.getSession(sessionId) ?? this.sessionManager.getRtc(sessionId)?.currentSession;
|
|
1077
1260
|
this.detachSessionHandlers(sessionId, targetSession);
|
|
1078
|
-
this.
|
|
1261
|
+
this.micRecovery.disable(sessionId);
|
|
1079
1262
|
this.sessionManager.cleanupSession(sessionId);
|
|
1080
1263
|
removeSessionState(this.stateStore, sessionId);
|
|
1081
1264
|
}
|
|
1082
1265
|
cleanupAllSessions() {
|
|
1083
1266
|
this.sessionManager.cleanupAllSessions();
|
|
1084
|
-
this.micRecovery.
|
|
1085
|
-
this.micRecovery.clear();
|
|
1267
|
+
this.micRecovery.cleanupAll();
|
|
1086
1268
|
this.sessionHandlers.clear();
|
|
1087
1269
|
this.stateStore.setState({
|
|
1088
1270
|
sessions: [],
|
|
@@ -1096,7 +1278,9 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
1096
1278
|
state: this.stateStore,
|
|
1097
1279
|
rtc,
|
|
1098
1280
|
detachSessionHandlers: () => this.cleanupSession(sessionId, session),
|
|
1281
|
+
emitError: (raw, code, fallback) => this.emitError(raw, code, fallback),
|
|
1099
1282
|
onSessionFailed: (err, event) => this.onSessionFailed(err, event),
|
|
1283
|
+
enableMicrophoneRecovery: (confirmedSessionId) => this.micRecovery.enable(confirmedSessionId),
|
|
1100
1284
|
sessionId
|
|
1101
1285
|
});
|
|
1102
1286
|
}
|
|
@@ -1204,102 +1388,6 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
1204
1388
|
setSessionMedia(sessionId, stream) {
|
|
1205
1389
|
this.sessionManager.setSessionMedia(sessionId, stream);
|
|
1206
1390
|
}
|
|
1207
|
-
enableMicrophoneRecovery(sessionId, options = {}) {
|
|
1208
|
-
const resolved = this.resolveExistingSessionId(sessionId);
|
|
1209
|
-
if (!resolved)
|
|
1210
|
-
return () => {
|
|
1211
|
-
};
|
|
1212
|
-
this.disableMicrophoneRecovery(resolved);
|
|
1213
|
-
const intervalMs = options.intervalMs ?? this.micRecoveryDefaults.intervalMs;
|
|
1214
|
-
const maxRetries = options.maxRetries ?? this.micRecoveryDefaults.maxRetries;
|
|
1215
|
-
let retries = 0;
|
|
1216
|
-
let stopped = false;
|
|
1217
|
-
const startedAt = Date.now();
|
|
1218
|
-
const warmupMs = Math.max(intervalMs * 2, 2e3);
|
|
1219
|
-
const tick = async () => {
|
|
1220
|
-
if (stopped || retries >= maxRetries)
|
|
1221
|
-
return;
|
|
1222
|
-
const rtc = this.sessionManager.getRtc(resolved);
|
|
1223
|
-
const session2 = this.sessionManager.getSession(resolved);
|
|
1224
|
-
if (!rtc || !session2)
|
|
1225
|
-
return;
|
|
1226
|
-
const sessionState = this.stateStore.getState().sessions.find((s) => s.id === resolved);
|
|
1227
|
-
if (sessionState?.muted)
|
|
1228
|
-
return;
|
|
1229
|
-
const stream = rtc.mediaStream;
|
|
1230
|
-
const track = stream?.getAudioTracks?.()[0];
|
|
1231
|
-
const pc2 = session2?.connection;
|
|
1232
|
-
const sender = pc2?.getSenders?.().find((s) => s.track?.kind === "audio");
|
|
1233
|
-
if (!track && !sender)
|
|
1234
|
-
return;
|
|
1235
|
-
if (Date.now() - startedAt < warmupMs)
|
|
1236
|
-
return;
|
|
1237
|
-
if (pc2?.connectionState === "new" || pc2?.connectionState === "connecting" || pc2?.iceConnectionState === "new" || pc2?.iceConnectionState === "checking") {
|
|
1238
|
-
return;
|
|
1239
|
-
}
|
|
1240
|
-
const trackLive = track?.readyState === "live";
|
|
1241
|
-
const senderLive = sender?.track?.readyState === "live";
|
|
1242
|
-
if (trackLive && senderLive)
|
|
1243
|
-
return;
|
|
1244
|
-
this.emitError(
|
|
1245
|
-
{
|
|
1246
|
-
cause: "microphone dropped",
|
|
1247
|
-
trackLive,
|
|
1248
|
-
senderLive
|
|
1249
|
-
},
|
|
1250
|
-
"MICROPHONE_DROPPED",
|
|
1251
|
-
"microphone dropped"
|
|
1252
|
-
);
|
|
1253
|
-
retries += 1;
|
|
1254
|
-
if (trackLive && !senderLive && track) {
|
|
1255
|
-
await rtc.replaceAudioTrack(track);
|
|
1256
|
-
return;
|
|
1257
|
-
}
|
|
1258
|
-
let nextStream;
|
|
1259
|
-
try {
|
|
1260
|
-
const deviceId = track?.getSettings?.().deviceId ?? sender?.track?.getSettings?.().deviceId;
|
|
1261
|
-
nextStream = await this.requestMicrophoneStreamInternal(deviceId);
|
|
1262
|
-
} catch (err) {
|
|
1263
|
-
console.warn("[sip] mic recovery failed to get stream", err);
|
|
1264
|
-
return;
|
|
1265
|
-
}
|
|
1266
|
-
const nextTrack = nextStream.getAudioTracks()[0];
|
|
1267
|
-
if (!nextTrack)
|
|
1268
|
-
return;
|
|
1269
|
-
await rtc.replaceAudioTrack(nextTrack);
|
|
1270
|
-
this.sessionManager.setSessionMedia(resolved, nextStream);
|
|
1271
|
-
};
|
|
1272
|
-
const timer = setInterval(() => {
|
|
1273
|
-
void tick();
|
|
1274
|
-
}, intervalMs);
|
|
1275
|
-
void tick();
|
|
1276
|
-
const session = this.sessionManager.getSession(resolved);
|
|
1277
|
-
const pc = session?.connection;
|
|
1278
|
-
const onIceChange = () => {
|
|
1279
|
-
const state = pc?.iceConnectionState;
|
|
1280
|
-
if (state === "failed" || state === "disconnected")
|
|
1281
|
-
void tick();
|
|
1282
|
-
};
|
|
1283
|
-
pc?.addEventListener?.("iceconnectionstatechange", onIceChange);
|
|
1284
|
-
const stop = () => {
|
|
1285
|
-
stopped = true;
|
|
1286
|
-
clearInterval(timer);
|
|
1287
|
-
pc?.removeEventListener?.("iceconnectionstatechange", onIceChange);
|
|
1288
|
-
};
|
|
1289
|
-
this.micRecovery.set(resolved, { stop });
|
|
1290
|
-
return stop;
|
|
1291
|
-
}
|
|
1292
|
-
disableMicrophoneRecovery(sessionId) {
|
|
1293
|
-
const resolved = this.resolveExistingSessionId(sessionId);
|
|
1294
|
-
if (!resolved)
|
|
1295
|
-
return false;
|
|
1296
|
-
const entry = this.micRecovery.get(resolved);
|
|
1297
|
-
if (!entry)
|
|
1298
|
-
return false;
|
|
1299
|
-
entry.stop();
|
|
1300
|
-
this.micRecovery.delete(resolved);
|
|
1301
|
-
return true;
|
|
1302
|
-
}
|
|
1303
1391
|
switchCameraSession(sessionId, track) {
|
|
1304
1392
|
if (!this.sessionExists(sessionId))
|
|
1305
1393
|
return false;
|
|
@@ -1465,8 +1553,6 @@ function useSipActions() {
|
|
|
1465
1553
|
getSessionIds: () => client.getSessionIds(),
|
|
1466
1554
|
getSessions: () => client.getSessions(),
|
|
1467
1555
|
setSessionMedia: (...args) => client.setSessionMedia(...args),
|
|
1468
|
-
enableMicrophoneRecovery: (...args) => client.enableMicrophoneRecovery(...args),
|
|
1469
|
-
disableMicrophoneRecovery: (...args) => client.disableMicrophoneRecovery(...args),
|
|
1470
1556
|
switchCamera: (...args) => client.switchCameraSession(...args),
|
|
1471
1557
|
enableVideo: (...args) => client.enableVideoSession(...args),
|
|
1472
1558
|
disableVideo: (...args) => client.disableVideoSession(...args)
|
|
@@ -1540,6 +1626,7 @@ function createCallPlayer(audioEl) {
|
|
|
1540
1626
|
return () => session.off("peerconnection", onPeer);
|
|
1541
1627
|
};
|
|
1542
1628
|
function bindToSession(session) {
|
|
1629
|
+
clearAudioStream(audioEl.srcObject);
|
|
1543
1630
|
if (session?.direction === "outgoing" && session.connection instanceof RTCPeerConnection) {
|
|
1544
1631
|
cleanupTrackListener = dispose(cleanupTrackListener);
|
|
1545
1632
|
cleanupTrackListener = attachTracks(session.connection);
|
|
@@ -1556,6 +1643,7 @@ function createCallPlayer(audioEl) {
|
|
|
1556
1643
|
const e = payload?.data;
|
|
1557
1644
|
cleanupSessionPeerListener = dispose(cleanupSessionPeerListener);
|
|
1558
1645
|
cleanupTrackListener = dispose(cleanupTrackListener);
|
|
1646
|
+
clearAudioStream(audioEl.srcObject);
|
|
1559
1647
|
if (!e?.session)
|
|
1560
1648
|
return;
|
|
1561
1649
|
cleanupSessionPeerListener = listenSessionPeerconnection(e.session);
|