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 CHANGED
@@ -679,6 +679,23 @@ var WebRTCSessionController = class {
679
679
  old.stop();
680
680
  return true;
681
681
  }
682
+ async replaceAudioTrack(nextAudioTrack) {
683
+ const pc = this.getPC();
684
+ if (!pc)
685
+ return false;
686
+ if (!this.mediaStream)
687
+ this.mediaStream = new MediaStream();
688
+ const old = this.mediaStream.getAudioTracks()[0];
689
+ this.mediaStream.addTrack(nextAudioTrack);
690
+ if (old)
691
+ this.mediaStream.removeTrack(old);
692
+ const sender = pc.getSenders?.().find((s) => s.track?.kind === "audio");
693
+ if (sender)
694
+ await sender.replaceTrack(nextAudioTrack);
695
+ if (old && old !== nextAudioTrack)
696
+ old.stop();
697
+ return true;
698
+ }
682
699
  };
683
700
 
684
701
  // src/jssip-lib/sip/sessionManager.ts
@@ -886,6 +903,12 @@ var SipClient = class extends EventTargetEmitter {
886
903
  this.sessionHandlers = /* @__PURE__ */ new Map();
887
904
  this.maxSessionCount = Infinity;
888
905
  this.sessionManager = new SessionManager();
906
+ this.micRecovery = /* @__PURE__ */ new Map();
907
+ this.micRecoveryEnabled = false;
908
+ this.micRecoveryDefaults = {
909
+ intervalMs: 2e3,
910
+ maxRetries: Infinity
911
+ };
889
912
  this.errorHandler = options.errorHandler ?? new SipErrorHandler({
890
913
  formatter: options.formatError,
891
914
  messages: options.errorMessages
@@ -919,11 +942,21 @@ var SipClient = class extends EventTargetEmitter {
919
942
  this.stateStore.setState({ sipStatus: SipStatus.Connecting });
920
943
  const {
921
944
  debug: cfgDebug,
945
+ enableMicRecovery,
946
+ micRecoveryIntervalMs,
947
+ micRecoveryMaxRetries,
922
948
  maxSessionCount,
923
949
  pendingMediaTtlMs,
924
950
  ...uaCfg
925
951
  } = config;
926
952
  this.maxSessionCount = typeof maxSessionCount === "number" ? maxSessionCount : Infinity;
953
+ this.micRecoveryEnabled = Boolean(enableMicRecovery);
954
+ if (typeof micRecoveryIntervalMs === "number") {
955
+ this.micRecoveryDefaults.intervalMs = micRecoveryIntervalMs;
956
+ }
957
+ if (typeof micRecoveryMaxRetries === "number") {
958
+ this.micRecoveryDefaults.maxRetries = micRecoveryMaxRetries;
959
+ }
927
960
  this.sessionManager.setPendingMediaTtl(pendingMediaTtlMs);
928
961
  const debug = cfgDebug ?? this.getPersistedDebug() ?? this.debugPattern;
929
962
  this.userAgent.start(uri, password, uaCfg, { debug });
@@ -1012,6 +1045,9 @@ var SipClient = class extends EventTargetEmitter {
1012
1045
  if (h)
1013
1046
  session.on(ev, h);
1014
1047
  });
1048
+ if (this.micRecoveryEnabled) {
1049
+ this.enableMicrophoneRecovery(sessionId);
1050
+ }
1015
1051
  }
1016
1052
  detachSessionHandlers(sessionId, session) {
1017
1053
  const handlers = this.sessionHandlers.get(sessionId);
@@ -1037,11 +1073,14 @@ var SipClient = class extends EventTargetEmitter {
1037
1073
  cleanupSession(sessionId, session) {
1038
1074
  const targetSession = session ?? this.sessionManager.getSession(sessionId) ?? this.sessionManager.getRtc(sessionId)?.currentSession;
1039
1075
  this.detachSessionHandlers(sessionId, targetSession);
1076
+ this.disableMicrophoneRecovery(sessionId);
1040
1077
  this.sessionManager.cleanupSession(sessionId);
1041
1078
  removeSessionState(this.stateStore, sessionId);
1042
1079
  }
1043
1080
  cleanupAllSessions() {
1044
1081
  this.sessionManager.cleanupAllSessions();
1082
+ this.micRecovery.forEach((entry) => entry.stop());
1083
+ this.micRecovery.clear();
1045
1084
  this.sessionHandlers.clear();
1046
1085
  this.stateStore.setState({
1047
1086
  sessions: [],
@@ -1103,6 +1142,9 @@ var SipClient = class extends EventTargetEmitter {
1103
1142
  if (!sessionId || !this.sessionExists(sessionId))
1104
1143
  return false;
1105
1144
  const opts = this.ensureMediaConstraints(options);
1145
+ if (opts.mediaStream) {
1146
+ this.sessionManager.setSessionMedia(sessionId, opts.mediaStream);
1147
+ }
1106
1148
  return this.sessionManager.answer(sessionId, opts);
1107
1149
  }
1108
1150
  hangupSession(sessionId, options) {
@@ -1160,6 +1202,92 @@ var SipClient = class extends EventTargetEmitter {
1160
1202
  setSessionMedia(sessionId, stream) {
1161
1203
  this.sessionManager.setSessionMedia(sessionId, stream);
1162
1204
  }
1205
+ enableMicrophoneRecovery(sessionId, options = {}) {
1206
+ const resolved = this.resolveExistingSessionId(sessionId);
1207
+ if (!resolved)
1208
+ return () => {
1209
+ };
1210
+ this.disableMicrophoneRecovery(resolved);
1211
+ const intervalMs = options.intervalMs ?? this.micRecoveryDefaults.intervalMs;
1212
+ const maxRetries = options.maxRetries ?? this.micRecoveryDefaults.maxRetries;
1213
+ let retries = 0;
1214
+ let stopped = false;
1215
+ const tick = async () => {
1216
+ if (stopped || retries >= maxRetries)
1217
+ return;
1218
+ const rtc = this.sessionManager.getRtc(resolved);
1219
+ const session2 = this.sessionManager.getSession(resolved);
1220
+ if (!rtc || !session2)
1221
+ return;
1222
+ const sessionState = this.stateStore.getState().sessions.find((s) => s.id === resolved);
1223
+ if (sessionState?.muted)
1224
+ return;
1225
+ const stream = rtc.mediaStream;
1226
+ const track = stream?.getAudioTracks?.()[0];
1227
+ const sender = session2?.connection?.getSenders?.().find((s) => s.track?.kind === "audio");
1228
+ const trackLive = track?.readyState === "live";
1229
+ const senderLive = sender?.track?.readyState === "live";
1230
+ if (trackLive && senderLive)
1231
+ return;
1232
+ this.emitError(
1233
+ {
1234
+ cause: "microphone dropped",
1235
+ trackLive,
1236
+ senderLive
1237
+ },
1238
+ "MICROPHONE_DROPPED",
1239
+ "microphone dropped"
1240
+ );
1241
+ retries += 1;
1242
+ if (trackLive && !senderLive && track) {
1243
+ await rtc.replaceAudioTrack(track);
1244
+ return;
1245
+ }
1246
+ let nextStream;
1247
+ try {
1248
+ const deviceId = track?.getSettings?.().deviceId ?? sender?.track?.getSettings?.().deviceId;
1249
+ nextStream = await this.requestMicrophoneStreamInternal(deviceId);
1250
+ } catch (err) {
1251
+ console.warn("[sip] mic recovery failed to get stream", err);
1252
+ return;
1253
+ }
1254
+ const nextTrack = nextStream.getAudioTracks()[0];
1255
+ if (!nextTrack)
1256
+ return;
1257
+ await rtc.replaceAudioTrack(nextTrack);
1258
+ this.sessionManager.setSessionMedia(resolved, nextStream);
1259
+ };
1260
+ const timer = setInterval(() => {
1261
+ void tick();
1262
+ }, intervalMs);
1263
+ void tick();
1264
+ const session = this.sessionManager.getSession(resolved);
1265
+ const pc = session?.connection;
1266
+ const onIceChange = () => {
1267
+ const state = pc?.iceConnectionState;
1268
+ if (state === "failed" || state === "disconnected")
1269
+ void tick();
1270
+ };
1271
+ pc?.addEventListener?.("iceconnectionstatechange", onIceChange);
1272
+ const stop = () => {
1273
+ stopped = true;
1274
+ clearInterval(timer);
1275
+ pc?.removeEventListener?.("iceconnectionstatechange", onIceChange);
1276
+ };
1277
+ this.micRecovery.set(resolved, { stop });
1278
+ return stop;
1279
+ }
1280
+ disableMicrophoneRecovery(sessionId) {
1281
+ const resolved = this.resolveExistingSessionId(sessionId);
1282
+ if (!resolved)
1283
+ return false;
1284
+ const entry = this.micRecovery.get(resolved);
1285
+ if (!entry)
1286
+ return false;
1287
+ entry.stop();
1288
+ this.micRecovery.delete(resolved);
1289
+ return true;
1290
+ }
1163
1291
  switchCameraSession(sessionId, track) {
1164
1292
  if (!this.sessionExists(sessionId))
1165
1293
  return false;
@@ -1251,13 +1379,28 @@ var SipClient = class extends EventTargetEmitter {
1251
1379
  const persisted = window.sessionStorage.getItem(SESSION_DEBUG_KEY);
1252
1380
  if (!persisted)
1253
1381
  return void 0;
1254
- if (persisted === "true")
1255
- return true;
1256
1382
  return persisted;
1257
1383
  } catch {
1258
1384
  return void 0;
1259
1385
  }
1260
1386
  }
1387
+ async requestMicrophoneStreamInternal(deviceId) {
1388
+ if (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia) {
1389
+ throw new Error("getUserMedia not available");
1390
+ }
1391
+ const audio = deviceId && deviceId !== "default" ? { deviceId: { exact: deviceId } } : true;
1392
+ try {
1393
+ return await navigator.mediaDevices.getUserMedia({ audio });
1394
+ } catch (err) {
1395
+ const cause = err?.name || "getUserMedia failed";
1396
+ this.emitError(
1397
+ { raw: err, cause },
1398
+ "MICROPHONE_UNAVAILABLE",
1399
+ "microphone unavailable"
1400
+ );
1401
+ throw err;
1402
+ }
1403
+ }
1261
1404
  };
1262
1405
  function createSipClientInstance(options) {
1263
1406
  return new SipClient(options);
@@ -1310,6 +1453,8 @@ function useSipActions() {
1310
1453
  getSessionIds: () => client.getSessionIds(),
1311
1454
  getSessions: () => client.getSessions(),
1312
1455
  setSessionMedia: (...args) => client.setSessionMedia(...args),
1456
+ enableMicrophoneRecovery: (...args) => client.enableMicrophoneRecovery(...args),
1457
+ disableMicrophoneRecovery: (...args) => client.disableMicrophoneRecovery(...args),
1313
1458
  switchCamera: (...args) => client.switchCameraSession(...args),
1314
1459
  enableVideo: (...args) => client.enableVideoSession(...args),
1315
1460
  disableVideo: (...args) => client.disableVideoSession(...args)