react-jssip-kit 0.6.9 → 0.7.1
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 +147 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +147 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -41,6 +41,18 @@ type SipConfiguration = Omit<UAConfiguration, "password" | "uri"> & {
|
|
|
41
41
|
* Enable JsSIP debug logging. If string, treated as debug pattern.
|
|
42
42
|
*/
|
|
43
43
|
debug?: boolean | string;
|
|
44
|
+
/**
|
|
45
|
+
* Enable automatic microphone recovery for sessions.
|
|
46
|
+
*/
|
|
47
|
+
enableMicRecovery?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Interval between recovery attempts in milliseconds.
|
|
50
|
+
*/
|
|
51
|
+
micRecoveryIntervalMs?: number;
|
|
52
|
+
/**
|
|
53
|
+
* Maximum number of recovery attempts per session.
|
|
54
|
+
*/
|
|
55
|
+
micRecoveryMaxRetries?: number;
|
|
44
56
|
/**
|
|
45
57
|
* Maximum allowed concurrent sessions. Additional sessions are rejected.
|
|
46
58
|
*/
|
|
@@ -161,6 +173,10 @@ type SipClientOptions = {
|
|
|
161
173
|
errorHandler?: SipErrorHandler;
|
|
162
174
|
debug?: boolean | string;
|
|
163
175
|
};
|
|
176
|
+
type MicrophoneRecoveryOptions = {
|
|
177
|
+
intervalMs?: number;
|
|
178
|
+
maxRetries?: number;
|
|
179
|
+
};
|
|
164
180
|
declare class SipClient extends EventTargetEmitter<JsSIPEventMap> {
|
|
165
181
|
readonly userAgent: SipUserAgent;
|
|
166
182
|
readonly stateStore: SipStateStore;
|
|
@@ -172,6 +188,9 @@ declare class SipClient extends EventTargetEmitter<JsSIPEventMap> {
|
|
|
172
188
|
private maxSessionCount;
|
|
173
189
|
private sessionManager;
|
|
174
190
|
private lifecycle;
|
|
191
|
+
private micRecovery;
|
|
192
|
+
private micRecoveryEnabled;
|
|
193
|
+
private micRecoveryDefaults;
|
|
175
194
|
private unloadHandler?;
|
|
176
195
|
private stateLogOff?;
|
|
177
196
|
get state(): SipState;
|
|
@@ -210,6 +229,8 @@ declare class SipClient extends EventTargetEmitter<JsSIPEventMap> {
|
|
|
210
229
|
sendDTMFSession(sessionId: string, tones: string | number, options?: DTMFOptions): boolean;
|
|
211
230
|
transferSession(sessionId: string, target: string, options?: ReferOptions): boolean;
|
|
212
231
|
setSessionMedia(sessionId: string, stream: MediaStream): void;
|
|
232
|
+
enableMicrophoneRecovery(sessionId: string, options?: MicrophoneRecoveryOptions): () => void;
|
|
233
|
+
disableMicrophoneRecovery(sessionId: string): boolean;
|
|
213
234
|
switchCameraSession(sessionId: string, track: MediaStreamTrack): false | Promise<boolean>;
|
|
214
235
|
enableVideoSession(sessionId: string): boolean;
|
|
215
236
|
disableVideoSession(sessionId: string): boolean;
|
|
@@ -225,6 +246,7 @@ declare class SipClient extends EventTargetEmitter<JsSIPEventMap> {
|
|
|
225
246
|
private toggleStateLogger;
|
|
226
247
|
private diffState;
|
|
227
248
|
private getPersistedDebug;
|
|
249
|
+
private requestMicrophoneStreamInternal;
|
|
228
250
|
}
|
|
229
251
|
declare function createSipClientInstance(options?: SipClientOptions): SipClient;
|
|
230
252
|
declare function createSipEventManager(client: SipClient): SipEventManager;
|
|
@@ -252,6 +274,8 @@ declare function useSipActions(): {
|
|
|
252
274
|
session: jssip_src_RTCSession.RTCSession;
|
|
253
275
|
}[];
|
|
254
276
|
setSessionMedia: (sessionId: string, stream: MediaStream) => void;
|
|
277
|
+
enableMicrophoneRecovery: (sessionId: string, options?: MicrophoneRecoveryOptions | undefined) => () => void;
|
|
278
|
+
disableMicrophoneRecovery: (sessionId: string) => boolean;
|
|
255
279
|
switchCamera: (sessionId: string, track: MediaStreamTrack) => false | Promise<boolean>;
|
|
256
280
|
enableVideo: (sessionId: string) => boolean;
|
|
257
281
|
disableVideo: (sessionId: string) => boolean;
|
package/dist/index.js
CHANGED
|
@@ -674,6 +674,23 @@ var WebRTCSessionController = class {
|
|
|
674
674
|
old.stop();
|
|
675
675
|
return true;
|
|
676
676
|
}
|
|
677
|
+
async replaceAudioTrack(nextAudioTrack) {
|
|
678
|
+
const pc = this.getPC();
|
|
679
|
+
if (!pc)
|
|
680
|
+
return false;
|
|
681
|
+
if (!this.mediaStream)
|
|
682
|
+
this.mediaStream = new MediaStream();
|
|
683
|
+
const old = this.mediaStream.getAudioTracks()[0];
|
|
684
|
+
this.mediaStream.addTrack(nextAudioTrack);
|
|
685
|
+
if (old)
|
|
686
|
+
this.mediaStream.removeTrack(old);
|
|
687
|
+
const sender = pc.getSenders?.().find((s) => s.track?.kind === "audio");
|
|
688
|
+
if (sender)
|
|
689
|
+
await sender.replaceTrack(nextAudioTrack);
|
|
690
|
+
if (old && old !== nextAudioTrack)
|
|
691
|
+
old.stop();
|
|
692
|
+
return true;
|
|
693
|
+
}
|
|
677
694
|
};
|
|
678
695
|
|
|
679
696
|
// src/jssip-lib/sip/sessionManager.ts
|
|
@@ -881,6 +898,12 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
881
898
|
this.sessionHandlers = /* @__PURE__ */ new Map();
|
|
882
899
|
this.maxSessionCount = Infinity;
|
|
883
900
|
this.sessionManager = new SessionManager();
|
|
901
|
+
this.micRecovery = /* @__PURE__ */ new Map();
|
|
902
|
+
this.micRecoveryEnabled = false;
|
|
903
|
+
this.micRecoveryDefaults = {
|
|
904
|
+
intervalMs: 2e3,
|
|
905
|
+
maxRetries: Infinity
|
|
906
|
+
};
|
|
884
907
|
this.errorHandler = options.errorHandler ?? new SipErrorHandler({
|
|
885
908
|
formatter: options.formatError,
|
|
886
909
|
messages: options.errorMessages
|
|
@@ -914,11 +937,21 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
914
937
|
this.stateStore.setState({ sipStatus: SipStatus.Connecting });
|
|
915
938
|
const {
|
|
916
939
|
debug: cfgDebug,
|
|
940
|
+
enableMicRecovery,
|
|
941
|
+
micRecoveryIntervalMs,
|
|
942
|
+
micRecoveryMaxRetries,
|
|
917
943
|
maxSessionCount,
|
|
918
944
|
pendingMediaTtlMs,
|
|
919
945
|
...uaCfg
|
|
920
946
|
} = config;
|
|
921
947
|
this.maxSessionCount = typeof maxSessionCount === "number" ? maxSessionCount : Infinity;
|
|
948
|
+
this.micRecoveryEnabled = Boolean(enableMicRecovery);
|
|
949
|
+
if (typeof micRecoveryIntervalMs === "number") {
|
|
950
|
+
this.micRecoveryDefaults.intervalMs = micRecoveryIntervalMs;
|
|
951
|
+
}
|
|
952
|
+
if (typeof micRecoveryMaxRetries === "number") {
|
|
953
|
+
this.micRecoveryDefaults.maxRetries = micRecoveryMaxRetries;
|
|
954
|
+
}
|
|
922
955
|
this.sessionManager.setPendingMediaTtl(pendingMediaTtlMs);
|
|
923
956
|
const debug = cfgDebug ?? this.getPersistedDebug() ?? this.debugPattern;
|
|
924
957
|
this.userAgent.start(uri, password, uaCfg, { debug });
|
|
@@ -1007,6 +1040,9 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
1007
1040
|
if (h)
|
|
1008
1041
|
session.on(ev, h);
|
|
1009
1042
|
});
|
|
1043
|
+
if (this.micRecoveryEnabled) {
|
|
1044
|
+
this.enableMicrophoneRecovery(sessionId);
|
|
1045
|
+
}
|
|
1010
1046
|
}
|
|
1011
1047
|
detachSessionHandlers(sessionId, session) {
|
|
1012
1048
|
const handlers = this.sessionHandlers.get(sessionId);
|
|
@@ -1032,11 +1068,14 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
1032
1068
|
cleanupSession(sessionId, session) {
|
|
1033
1069
|
const targetSession = session ?? this.sessionManager.getSession(sessionId) ?? this.sessionManager.getRtc(sessionId)?.currentSession;
|
|
1034
1070
|
this.detachSessionHandlers(sessionId, targetSession);
|
|
1071
|
+
this.disableMicrophoneRecovery(sessionId);
|
|
1035
1072
|
this.sessionManager.cleanupSession(sessionId);
|
|
1036
1073
|
removeSessionState(this.stateStore, sessionId);
|
|
1037
1074
|
}
|
|
1038
1075
|
cleanupAllSessions() {
|
|
1039
1076
|
this.sessionManager.cleanupAllSessions();
|
|
1077
|
+
this.micRecovery.forEach((entry) => entry.stop());
|
|
1078
|
+
this.micRecovery.clear();
|
|
1040
1079
|
this.sessionHandlers.clear();
|
|
1041
1080
|
this.stateStore.setState({
|
|
1042
1081
|
sessions: [],
|
|
@@ -1098,6 +1137,9 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
1098
1137
|
if (!sessionId || !this.sessionExists(sessionId))
|
|
1099
1138
|
return false;
|
|
1100
1139
|
const opts = this.ensureMediaConstraints(options);
|
|
1140
|
+
if (opts.mediaStream) {
|
|
1141
|
+
this.sessionManager.setSessionMedia(sessionId, opts.mediaStream);
|
|
1142
|
+
}
|
|
1101
1143
|
return this.sessionManager.answer(sessionId, opts);
|
|
1102
1144
|
}
|
|
1103
1145
|
hangupSession(sessionId, options) {
|
|
@@ -1155,6 +1197,92 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
1155
1197
|
setSessionMedia(sessionId, stream) {
|
|
1156
1198
|
this.sessionManager.setSessionMedia(sessionId, stream);
|
|
1157
1199
|
}
|
|
1200
|
+
enableMicrophoneRecovery(sessionId, options = {}) {
|
|
1201
|
+
const resolved = this.resolveExistingSessionId(sessionId);
|
|
1202
|
+
if (!resolved)
|
|
1203
|
+
return () => {
|
|
1204
|
+
};
|
|
1205
|
+
this.disableMicrophoneRecovery(resolved);
|
|
1206
|
+
const intervalMs = options.intervalMs ?? this.micRecoveryDefaults.intervalMs;
|
|
1207
|
+
const maxRetries = options.maxRetries ?? this.micRecoveryDefaults.maxRetries;
|
|
1208
|
+
let retries = 0;
|
|
1209
|
+
let stopped = false;
|
|
1210
|
+
const tick = async () => {
|
|
1211
|
+
if (stopped || retries >= maxRetries)
|
|
1212
|
+
return;
|
|
1213
|
+
const rtc = this.sessionManager.getRtc(resolved);
|
|
1214
|
+
const session2 = this.sessionManager.getSession(resolved);
|
|
1215
|
+
if (!rtc || !session2)
|
|
1216
|
+
return;
|
|
1217
|
+
const sessionState = this.stateStore.getState().sessions.find((s) => s.id === resolved);
|
|
1218
|
+
if (sessionState?.muted)
|
|
1219
|
+
return;
|
|
1220
|
+
const stream = rtc.mediaStream;
|
|
1221
|
+
const track = stream?.getAudioTracks?.()[0];
|
|
1222
|
+
const sender = session2?.connection?.getSenders?.().find((s) => s.track?.kind === "audio");
|
|
1223
|
+
const trackLive = track?.readyState === "live";
|
|
1224
|
+
const senderLive = sender?.track?.readyState === "live";
|
|
1225
|
+
if (trackLive && senderLive)
|
|
1226
|
+
return;
|
|
1227
|
+
this.emitError(
|
|
1228
|
+
{
|
|
1229
|
+
cause: "microphone dropped",
|
|
1230
|
+
trackLive,
|
|
1231
|
+
senderLive
|
|
1232
|
+
},
|
|
1233
|
+
"MICROPHONE_DROPPED",
|
|
1234
|
+
"microphone dropped"
|
|
1235
|
+
);
|
|
1236
|
+
retries += 1;
|
|
1237
|
+
if (trackLive && !senderLive && track) {
|
|
1238
|
+
await rtc.replaceAudioTrack(track);
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1241
|
+
let nextStream;
|
|
1242
|
+
try {
|
|
1243
|
+
const deviceId = track?.getSettings?.().deviceId ?? sender?.track?.getSettings?.().deviceId;
|
|
1244
|
+
nextStream = await this.requestMicrophoneStreamInternal(deviceId);
|
|
1245
|
+
} catch (err) {
|
|
1246
|
+
console.warn("[sip] mic recovery failed to get stream", err);
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1249
|
+
const nextTrack = nextStream.getAudioTracks()[0];
|
|
1250
|
+
if (!nextTrack)
|
|
1251
|
+
return;
|
|
1252
|
+
await rtc.replaceAudioTrack(nextTrack);
|
|
1253
|
+
this.sessionManager.setSessionMedia(resolved, nextStream);
|
|
1254
|
+
};
|
|
1255
|
+
const timer = setInterval(() => {
|
|
1256
|
+
void tick();
|
|
1257
|
+
}, intervalMs);
|
|
1258
|
+
void tick();
|
|
1259
|
+
const session = this.sessionManager.getSession(resolved);
|
|
1260
|
+
const pc = session?.connection;
|
|
1261
|
+
const onIceChange = () => {
|
|
1262
|
+
const state = pc?.iceConnectionState;
|
|
1263
|
+
if (state === "failed" || state === "disconnected")
|
|
1264
|
+
void tick();
|
|
1265
|
+
};
|
|
1266
|
+
pc?.addEventListener?.("iceconnectionstatechange", onIceChange);
|
|
1267
|
+
const stop = () => {
|
|
1268
|
+
stopped = true;
|
|
1269
|
+
clearInterval(timer);
|
|
1270
|
+
pc?.removeEventListener?.("iceconnectionstatechange", onIceChange);
|
|
1271
|
+
};
|
|
1272
|
+
this.micRecovery.set(resolved, { stop });
|
|
1273
|
+
return stop;
|
|
1274
|
+
}
|
|
1275
|
+
disableMicrophoneRecovery(sessionId) {
|
|
1276
|
+
const resolved = this.resolveExistingSessionId(sessionId);
|
|
1277
|
+
if (!resolved)
|
|
1278
|
+
return false;
|
|
1279
|
+
const entry = this.micRecovery.get(resolved);
|
|
1280
|
+
if (!entry)
|
|
1281
|
+
return false;
|
|
1282
|
+
entry.stop();
|
|
1283
|
+
this.micRecovery.delete(resolved);
|
|
1284
|
+
return true;
|
|
1285
|
+
}
|
|
1158
1286
|
switchCameraSession(sessionId, track) {
|
|
1159
1287
|
if (!this.sessionExists(sessionId))
|
|
1160
1288
|
return false;
|
|
@@ -1246,13 +1374,28 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
1246
1374
|
const persisted = window.sessionStorage.getItem(SESSION_DEBUG_KEY);
|
|
1247
1375
|
if (!persisted)
|
|
1248
1376
|
return void 0;
|
|
1249
|
-
if (persisted === "true")
|
|
1250
|
-
return true;
|
|
1251
1377
|
return persisted;
|
|
1252
1378
|
} catch {
|
|
1253
1379
|
return void 0;
|
|
1254
1380
|
}
|
|
1255
1381
|
}
|
|
1382
|
+
async requestMicrophoneStreamInternal(deviceId) {
|
|
1383
|
+
if (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia) {
|
|
1384
|
+
throw new Error("getUserMedia not available");
|
|
1385
|
+
}
|
|
1386
|
+
const audio = deviceId && deviceId !== "default" ? { deviceId: { exact: deviceId } } : true;
|
|
1387
|
+
try {
|
|
1388
|
+
return await navigator.mediaDevices.getUserMedia({ audio });
|
|
1389
|
+
} catch (err) {
|
|
1390
|
+
const cause = err?.name || "getUserMedia failed";
|
|
1391
|
+
this.emitError(
|
|
1392
|
+
{ raw: err, cause },
|
|
1393
|
+
"MICROPHONE_UNAVAILABLE",
|
|
1394
|
+
"microphone unavailable"
|
|
1395
|
+
);
|
|
1396
|
+
throw err;
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1256
1399
|
};
|
|
1257
1400
|
function createSipClientInstance(options) {
|
|
1258
1401
|
return new SipClient(options);
|
|
@@ -1305,6 +1448,8 @@ function useSipActions() {
|
|
|
1305
1448
|
getSessionIds: () => client.getSessionIds(),
|
|
1306
1449
|
getSessions: () => client.getSessions(),
|
|
1307
1450
|
setSessionMedia: (...args) => client.setSessionMedia(...args),
|
|
1451
|
+
enableMicrophoneRecovery: (...args) => client.enableMicrophoneRecovery(...args),
|
|
1452
|
+
disableMicrophoneRecovery: (...args) => client.disableMicrophoneRecovery(...args),
|
|
1308
1453
|
switchCamera: (...args) => client.switchCameraSession(...args),
|
|
1309
1454
|
enableVideo: (...args) => client.enableVideoSession(...args),
|
|
1310
1455
|
disableVideo: (...args) => client.disableVideoSession(...args)
|