@signalwire/js 4.0.0-beta.11 → 4.0.0-beta.12
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/browser.mjs +1171 -563
- package/dist/browser.mjs.map +1 -1
- package/dist/browser.umd.js +1173 -563
- package/dist/browser.umd.js.map +1 -1
- package/dist/index.cjs +994 -385
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +408 -68
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +408 -68
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +985 -378
- package/dist/index.mjs.map +1 -1
- package/dist/operators/index.cjs +1 -1
- package/dist/operators/index.mjs +1 -1
- package/dist/{operators-B1xH6k06.mjs → operators-CX_lCCJm.mjs} +2 -2
- package/dist/{operators-B1xH6k06.mjs.map → operators-CX_lCCJm.mjs.map} +1 -1
- package/dist/{operators-BT3jl--r.cjs → operators-D6a2J1KA.cjs} +2 -2
- package/dist/{operators-BT3jl--r.cjs.map → operators-D6a2J1KA.cjs.map} +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_operators = require('./operators-
|
|
1
|
+
const require_operators = require('./operators-D6a2J1KA.cjs');
|
|
2
2
|
let jwt_decode = require("jwt-decode");
|
|
3
3
|
let rxjs = require("rxjs");
|
|
4
4
|
let uuid = require("uuid");
|
|
@@ -131,7 +131,7 @@ const asyncRetry = async ({ asyncCallable, maxRetries: retries = DEFAULT_MAX_RET
|
|
|
131
131
|
|
|
132
132
|
//#endregion
|
|
133
133
|
//#region src/controllers/HTTPRequestController.ts
|
|
134
|
-
const logger$
|
|
134
|
+
const logger$30 = require_operators.getLogger();
|
|
135
135
|
const GET_PARAMS = {
|
|
136
136
|
method: "GET",
|
|
137
137
|
headers: { Accept: "application/json" }
|
|
@@ -143,7 +143,7 @@ const POST_PARAMS = {
|
|
|
143
143
|
"Content-Type": "application/json"
|
|
144
144
|
}
|
|
145
145
|
};
|
|
146
|
-
var HTTPRequestController = class HTTPRequestController {
|
|
146
|
+
var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
147
147
|
static {
|
|
148
148
|
this.defaultMaxRetries = 3;
|
|
149
149
|
}
|
|
@@ -164,11 +164,12 @@ var HTTPRequestController = class HTTPRequestController {
|
|
|
164
164
|
]);
|
|
165
165
|
}
|
|
166
166
|
constructor(baseURL, getCredential, options = {}) {
|
|
167
|
+
super();
|
|
167
168
|
this.baseURL = baseURL;
|
|
168
169
|
this.getCredential = getCredential;
|
|
169
|
-
this._responses$ =
|
|
170
|
-
this._errors$ =
|
|
171
|
-
this._status$ =
|
|
170
|
+
this._responses$ = this.createSubject();
|
|
171
|
+
this._errors$ = this.createSubject();
|
|
172
|
+
this._status$ = this.createBehaviorSubject("idle");
|
|
172
173
|
this.maxRetries = options.maxRetries ?? HTTPRequestController.defaultMaxRetries;
|
|
173
174
|
this.retryDelayMin = options.retryDelayMin ?? HTTPRequestController.defaultRetryDelayMinMs;
|
|
174
175
|
this.retryDelayMax = options.retryDelayMax ?? HTTPRequestController.defaultRetryDelayMaxMs;
|
|
@@ -194,7 +195,7 @@ var HTTPRequestController = class HTTPRequestController {
|
|
|
194
195
|
this._responses$.next(response);
|
|
195
196
|
return response;
|
|
196
197
|
} catch (error) {
|
|
197
|
-
logger$
|
|
198
|
+
logger$30.error("[HTTPRequestController] Request error:", error);
|
|
198
199
|
this._status$.next("error");
|
|
199
200
|
const err = error instanceof Error ? error : new Error("HTTP request failed", { cause: error });
|
|
200
201
|
this._errors$.next(err);
|
|
@@ -221,7 +222,7 @@ var HTTPRequestController = class HTTPRequestController {
|
|
|
221
222
|
const url = this.buildURL(request.url);
|
|
222
223
|
const headers = this.buildHeaders(request.headers);
|
|
223
224
|
const timeout$4 = request.timeout ?? this.requestTimeout;
|
|
224
|
-
logger$
|
|
225
|
+
logger$30.debug("[HTTPRequestController] Executing request:", {
|
|
225
226
|
method: request.method,
|
|
226
227
|
url,
|
|
227
228
|
headers: Object.keys(headers).reduce((acc, key) => {
|
|
@@ -241,7 +242,7 @@ var HTTPRequestController = class HTTPRequestController {
|
|
|
241
242
|
});
|
|
242
243
|
clearTimeout(timeoutId);
|
|
243
244
|
const httpResponse = await this.convertResponse(response);
|
|
244
|
-
logger$
|
|
245
|
+
logger$30.debug("[HTTPRequestController] Response received:", {
|
|
245
246
|
status: response.status,
|
|
246
247
|
statusText: response.statusText,
|
|
247
248
|
headers: [...response.headers.entries()],
|
|
@@ -251,7 +252,7 @@ var HTTPRequestController = class HTTPRequestController {
|
|
|
251
252
|
} catch (error) {
|
|
252
253
|
clearTimeout(timeoutId);
|
|
253
254
|
if (error instanceof Error && error.name === "AbortError") throw new require_operators.RequestTimeoutError(`Request timeout after ${timeout$4}ms`, { cause: error });
|
|
254
|
-
logger$
|
|
255
|
+
logger$30.error("[HTTPRequestController] Request failed:", error);
|
|
255
256
|
throw error;
|
|
256
257
|
}
|
|
257
258
|
}
|
|
@@ -265,8 +266,8 @@ var HTTPRequestController = class HTTPRequestController {
|
|
|
265
266
|
const credential = this.getCredential();
|
|
266
267
|
if (credential.token) {
|
|
267
268
|
headers.Authorization = `Bearer ${credential.token}`;
|
|
268
|
-
logger$
|
|
269
|
-
} else logger$
|
|
269
|
+
logger$30.debug("[HTTPRequestController] Using Bearer token auth, token length:", credential.token.length);
|
|
270
|
+
} else logger$30.warn("[HTTPRequestController] No credentials available for authentication");
|
|
270
271
|
return headers;
|
|
271
272
|
}
|
|
272
273
|
/**
|
|
@@ -478,6 +479,18 @@ const DEFAULT_ICE_DISCONNECTED_GRACE_PERIOD_MS = 3e3;
|
|
|
478
479
|
const DEFAULT_ICE_RESTART_TIMEOUT_MS$1 = 5e3;
|
|
479
480
|
/** Maximum recovery attempts before emitting 'max_attempts_reached'. */
|
|
480
481
|
const DEFAULT_MAX_RECOVERY_ATTEMPTS = 3;
|
|
482
|
+
/** Upper bound in ms for waiting on iceGatheringState === 'complete' after an ICE restart. */
|
|
483
|
+
const ICE_GATHERING_COMPLETE_TIMEOUT_MS = 1e4;
|
|
484
|
+
/** Upper bound in ms for waiting on RTCPeerConnection.connectionState === 'connected' after a recovery ICE restart. */
|
|
485
|
+
const PEER_CONNECTION_RECOVERY_WAIT_MS = 5e3;
|
|
486
|
+
/** Polling interval in ms while waiting for RTCPeerConnection.connectionState to transition. */
|
|
487
|
+
const PEER_CONNECTION_RECOVERY_POLL_MS = 100;
|
|
488
|
+
/** Polling interval for LocalAudioPipeline.level$ (ms). ~30fps is smooth for meters. */
|
|
489
|
+
const AUDIO_LEVEL_POLL_INTERVAL_MS = 33;
|
|
490
|
+
/** RMS level threshold (0..1) above which the local participant is considered speaking. */
|
|
491
|
+
const VAD_THRESHOLD = .03;
|
|
492
|
+
/** Hold window in ms below the threshold before speaking$ flips back to false. */
|
|
493
|
+
const VAD_HOLD_MS = 250;
|
|
481
494
|
/** Whether to persist device selections to storage by default. */
|
|
482
495
|
const DEFAULT_PERSIST_DEVICE_SELECTION = true;
|
|
483
496
|
/** Whether to auto-apply device changes to active calls by default. */
|
|
@@ -526,7 +539,7 @@ function fromMsToSec(milliseconds) {
|
|
|
526
539
|
|
|
527
540
|
//#endregion
|
|
528
541
|
//#region src/containers/PreferencesContainer.ts
|
|
529
|
-
const logger$
|
|
542
|
+
const logger$29 = require_operators.getLogger();
|
|
530
543
|
var PreferencesContainer = class PreferencesContainer {
|
|
531
544
|
static get instance() {
|
|
532
545
|
this._instance ??= new PreferencesContainer();
|
|
@@ -1188,7 +1201,7 @@ var ClientPreferences = class {
|
|
|
1188
1201
|
if (!this._storage) return;
|
|
1189
1202
|
const data = collectStoredPreferences();
|
|
1190
1203
|
this._storage.setItem(PREFERENCES_STORAGE_KEY, data, "local").catch((error) => {
|
|
1191
|
-
logger$
|
|
1204
|
+
logger$29.error(`[ClientPreferences] Failed to save preferences: ${String(error)}`);
|
|
1192
1205
|
});
|
|
1193
1206
|
}
|
|
1194
1207
|
/** Loads preferences from storage and applies them to the container. */
|
|
@@ -1197,7 +1210,7 @@ var ClientPreferences = class {
|
|
|
1197
1210
|
this._storage.getItem(PREFERENCES_STORAGE_KEY, "local").then((stored) => {
|
|
1198
1211
|
if (stored) applyStoredPreferences(stored);
|
|
1199
1212
|
}).catch((error) => {
|
|
1200
|
-
logger$
|
|
1213
|
+
logger$29.error(`[ClientPreferences] Failed to load preferences: ${String(error)}`);
|
|
1201
1214
|
});
|
|
1202
1215
|
}
|
|
1203
1216
|
};
|
|
@@ -1218,7 +1231,7 @@ function toError(value) {
|
|
|
1218
1231
|
|
|
1219
1232
|
//#endregion
|
|
1220
1233
|
//#region src/controllers/NavigatorDeviceController.ts
|
|
1221
|
-
const logger$
|
|
1234
|
+
const logger$28 = require_operators.getLogger();
|
|
1222
1235
|
/** Maps a device kind to its storage key. */
|
|
1223
1236
|
const DEVICE_STORAGE_KEYS = {
|
|
1224
1237
|
audioinput: DEVICE_STORAGE_KEY_AUDIO_INPUT,
|
|
@@ -1240,7 +1253,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1240
1253
|
super();
|
|
1241
1254
|
this.webRTCApiProvider = webRTCApiProvider;
|
|
1242
1255
|
this.deviceChangeHandler = () => {
|
|
1243
|
-
logger$
|
|
1256
|
+
logger$28.debug("[DeviceController] Device change detected");
|
|
1244
1257
|
this.enumerateDevices();
|
|
1245
1258
|
};
|
|
1246
1259
|
this._devicesState$ = this.createBehaviorSubject(initialDevicesState);
|
|
@@ -1305,13 +1318,13 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1305
1318
|
return this.cachedObservable("videoInputDevices$", () => this._devicesState$.pipe((0, rxjs.map)((state) => state.videoinput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$)));
|
|
1306
1319
|
}
|
|
1307
1320
|
get selectedAudioInputDevice$() {
|
|
1308
|
-
return this.cachedObservable("selectedAudioInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, rxjs.map)((state) => state.audioinput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.tap)((info) => logger$
|
|
1321
|
+
return this.cachedObservable("selectedAudioInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, rxjs.map)((state) => state.audioinput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.tap)((info) => logger$28.debug("[DeviceController] Selected audio input device changed:", info))));
|
|
1309
1322
|
}
|
|
1310
1323
|
get selectedAudioOutputDevice$() {
|
|
1311
|
-
return this.cachedObservable("selectedAudioOutputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, rxjs.map)((state) => state.audiooutput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.tap)((info) => logger$
|
|
1324
|
+
return this.cachedObservable("selectedAudioOutputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, rxjs.map)((state) => state.audiooutput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.tap)((info) => logger$28.debug("[DeviceController] Selected audio output device changed:", info))));
|
|
1312
1325
|
}
|
|
1313
1326
|
get selectedVideoInputDevice$() {
|
|
1314
|
-
return this.cachedObservable("selectedVideoInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, rxjs.map)((state) => state.videoinput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.tap)((info) => logger$
|
|
1327
|
+
return this.cachedObservable("selectedVideoInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, rxjs.map)((state) => state.videoinput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.tap)((info) => logger$28.debug("[DeviceController] Selected video input device changed:", info))));
|
|
1315
1328
|
}
|
|
1316
1329
|
get selectedAudioInputDevice() {
|
|
1317
1330
|
if (this._audioInputDisabled$.value) return null;
|
|
@@ -1386,7 +1399,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1386
1399
|
if (device) this.persistDeviceSelection("audioinput", device);
|
|
1387
1400
|
}
|
|
1388
1401
|
selectVideoInputDevice(device) {
|
|
1389
|
-
logger$
|
|
1402
|
+
logger$28.debug("[DeviceController] Setting selected video input device:", device);
|
|
1390
1403
|
if (this._videoInputDisabled$.value && device) this._videoInputDisabled$.next(false);
|
|
1391
1404
|
const previous = this._selectedDevicesState$.value.videoinput;
|
|
1392
1405
|
if (previous && previous.deviceId !== device?.deviceId) this._deviceHistory.push("videoinput", previous);
|
|
@@ -1443,7 +1456,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1443
1456
|
}
|
|
1444
1457
|
const fromHistory = this._deviceHistory.findInHistory(kind, devices);
|
|
1445
1458
|
if (fromHistory) {
|
|
1446
|
-
logger$
|
|
1459
|
+
logger$28.debug(`[DeviceController] Device disappeared, falling back to history: ${fromHistory.label}`);
|
|
1447
1460
|
this.emitDeviceRecovered(kind, selected, fromHistory, "device_disconnected");
|
|
1448
1461
|
return fromHistory;
|
|
1449
1462
|
}
|
|
@@ -1496,7 +1509,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1496
1509
|
try {
|
|
1497
1510
|
await this._storageManager.setItem(DEVICE_STORAGE_KEYS[kind], stored, "local");
|
|
1498
1511
|
} catch (error) {
|
|
1499
|
-
logger$
|
|
1512
|
+
logger$28.error(`[DeviceController] Failed to persist device selection for ${kind}:`, error);
|
|
1500
1513
|
}
|
|
1501
1514
|
}
|
|
1502
1515
|
async loadPersistedDevices() {
|
|
@@ -1512,7 +1525,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1512
1525
|
[kind]: stored
|
|
1513
1526
|
};
|
|
1514
1527
|
} catch (error) {
|
|
1515
|
-
logger$
|
|
1528
|
+
logger$28.error(`[DeviceController] Failed to load persisted device for ${kind}:`, error);
|
|
1516
1529
|
}
|
|
1517
1530
|
}
|
|
1518
1531
|
/** Clears device history, persisted selections, and re-enumerates devices. */
|
|
@@ -1530,7 +1543,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1530
1543
|
this.disableDeviceMonitoring();
|
|
1531
1544
|
this.webRTCApiProvider.mediaDevices.addEventListener("devicechange", this.deviceChangeHandler);
|
|
1532
1545
|
if (PreferencesContainer.instance.devicePollingInterval > 0) this._devicesPoolingSubscription = (0, rxjs.interval)(PreferencesContainer.instance.devicePollingInterval).subscribe(() => {
|
|
1533
|
-
logger$
|
|
1546
|
+
logger$28.debug("[DeviceController] Polling devices due to interval");
|
|
1534
1547
|
this.enumerateDevices();
|
|
1535
1548
|
});
|
|
1536
1549
|
this.enumerateDevices();
|
|
@@ -1556,13 +1569,13 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1556
1569
|
videoinput: []
|
|
1557
1570
|
});
|
|
1558
1571
|
this._devicesState$.next(devicesByKind);
|
|
1559
|
-
logger$
|
|
1572
|
+
logger$28.debug("[DeviceController] Devices enumerated:", {
|
|
1560
1573
|
audioInputs: devicesByKind.audioinput.length,
|
|
1561
1574
|
audioOutputs: devicesByKind.audiooutput.length,
|
|
1562
1575
|
videoInputs: devicesByKind.videoinput.length
|
|
1563
1576
|
});
|
|
1564
1577
|
} catch (error) {
|
|
1565
|
-
logger$
|
|
1578
|
+
logger$28.error("[DeviceController] Failed to enumerate devices:", error);
|
|
1566
1579
|
this._errors$.next(toError(error));
|
|
1567
1580
|
}
|
|
1568
1581
|
}
|
|
@@ -1578,7 +1591,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1578
1591
|
stream.getTracks().forEach((t) => t.stop());
|
|
1579
1592
|
return capabilities;
|
|
1580
1593
|
} catch (error) {
|
|
1581
|
-
logger$
|
|
1594
|
+
logger$28.error("[DeviceController] Failed to get device capabilities:", error);
|
|
1582
1595
|
this._errors$.next(toError(error));
|
|
1583
1596
|
throw error;
|
|
1584
1597
|
}
|
|
@@ -1730,15 +1743,15 @@ var DependencyContainer = class {
|
|
|
1730
1743
|
this._baseURL = this.apiHost;
|
|
1731
1744
|
this._credential = {};
|
|
1732
1745
|
}
|
|
1733
|
-
get
|
|
1734
|
-
return this.
|
|
1746
|
+
get userId() {
|
|
1747
|
+
return this.user.id;
|
|
1735
1748
|
}
|
|
1736
|
-
get
|
|
1737
|
-
if (!this.
|
|
1738
|
-
return this.
|
|
1749
|
+
get user() {
|
|
1750
|
+
if (!this._user) throw new require_operators.DependencyError("User");
|
|
1751
|
+
return this._user;
|
|
1739
1752
|
}
|
|
1740
|
-
set
|
|
1741
|
-
this.
|
|
1753
|
+
set user(user) {
|
|
1754
|
+
this._user = user;
|
|
1742
1755
|
}
|
|
1743
1756
|
get storage() {
|
|
1744
1757
|
if (!this._storageManager) {
|
|
@@ -1784,16 +1797,16 @@ var DependencyContainer = class {
|
|
|
1784
1797
|
this._deviceController = void 0;
|
|
1785
1798
|
}
|
|
1786
1799
|
get authorizationStateKey() {
|
|
1787
|
-
return `sw:${this.
|
|
1800
|
+
return `sw:${this.userId}:as`;
|
|
1788
1801
|
}
|
|
1789
1802
|
get protocolKey() {
|
|
1790
|
-
return `sw:${this.
|
|
1803
|
+
return `sw:${this.userId}:pt`;
|
|
1791
1804
|
}
|
|
1792
1805
|
get attachedCallsKey() {
|
|
1793
|
-
return `sw:${this.
|
|
1806
|
+
return `sw:${this.userId}:att`;
|
|
1794
1807
|
}
|
|
1795
|
-
|
|
1796
|
-
return this.
|
|
1808
|
+
getUserFromAddressId() {
|
|
1809
|
+
return this.user.addresses[0]?.id ?? "";
|
|
1797
1810
|
}
|
|
1798
1811
|
set baseURL(baseURL) {
|
|
1799
1812
|
this._baseURL = baseURL;
|
|
@@ -1829,7 +1842,7 @@ var DependencyContainer = class {
|
|
|
1829
1842
|
|
|
1830
1843
|
//#endregion
|
|
1831
1844
|
//#region src/controllers/CryptoController.ts
|
|
1832
|
-
const logger$
|
|
1845
|
+
const logger$27 = require_operators.getLogger();
|
|
1833
1846
|
const DPOP_DB_NAME = "sw-dpop";
|
|
1834
1847
|
const DPOP_DB_VERSION = 1;
|
|
1835
1848
|
const DPOP_STORE_NAME = "keys";
|
|
@@ -1888,7 +1901,7 @@ async function loadKeyPairFromDB() {
|
|
|
1888
1901
|
tx.oncomplete = () => db.close();
|
|
1889
1902
|
});
|
|
1890
1903
|
} catch (error) {
|
|
1891
|
-
logger$
|
|
1904
|
+
logger$27.warn("[DPoP] Failed to load key pair from IndexedDB:", error);
|
|
1892
1905
|
return null;
|
|
1893
1906
|
}
|
|
1894
1907
|
}
|
|
@@ -1908,7 +1921,7 @@ async function saveKeyPairToDB(keyPair) {
|
|
|
1908
1921
|
};
|
|
1909
1922
|
});
|
|
1910
1923
|
} catch (error) {
|
|
1911
|
-
logger$
|
|
1924
|
+
logger$27.warn("[DPoP] Failed to save key pair to IndexedDB:", error);
|
|
1912
1925
|
}
|
|
1913
1926
|
}
|
|
1914
1927
|
async function deleteKeyPairFromDB() {
|
|
@@ -1927,7 +1940,7 @@ async function deleteKeyPairFromDB() {
|
|
|
1927
1940
|
};
|
|
1928
1941
|
});
|
|
1929
1942
|
} catch (error) {
|
|
1930
|
-
logger$
|
|
1943
|
+
logger$27.warn("[DPoP] Failed to delete key pair from IndexedDB:", error);
|
|
1931
1944
|
}
|
|
1932
1945
|
}
|
|
1933
1946
|
/**
|
|
@@ -1987,13 +2000,13 @@ var CryptoController = class {
|
|
|
1987
2000
|
this._publicJwk = await crypto.subtle.exportKey("jwk", stored.publicKey);
|
|
1988
2001
|
this._fingerprint = await computeJwkThumbprint(this._publicJwk);
|
|
1989
2002
|
this._initialized = true;
|
|
1990
|
-
logger$
|
|
2003
|
+
logger$27.debug("[DPoP] Key pair restored from IndexedDB, fingerprint:", this._fingerprint);
|
|
1991
2004
|
return this._fingerprint;
|
|
1992
2005
|
} catch (error) {
|
|
1993
|
-
logger$
|
|
2006
|
+
logger$27.warn("[DPoP] Stored key pair unusable, generating new one:", error);
|
|
1994
2007
|
await deleteKeyPairFromDB();
|
|
1995
2008
|
}
|
|
1996
|
-
logger$
|
|
2009
|
+
logger$27.debug("[DPoP] Generating RSA key pair");
|
|
1997
2010
|
this._keyPair = await crypto.subtle.generateKey({
|
|
1998
2011
|
name: "RSASSA-PKCS1-v1_5",
|
|
1999
2012
|
modulusLength: 2048,
|
|
@@ -2008,7 +2021,7 @@ var CryptoController = class {
|
|
|
2008
2021
|
this._fingerprint = await computeJwkThumbprint(this._publicJwk);
|
|
2009
2022
|
this._initialized = true;
|
|
2010
2023
|
await saveKeyPairToDB(this._keyPair);
|
|
2011
|
-
logger$
|
|
2024
|
+
logger$27.debug("[DPoP] Key pair generated and persisted, fingerprint:", this._fingerprint);
|
|
2012
2025
|
return this._fingerprint;
|
|
2013
2026
|
}
|
|
2014
2027
|
/**
|
|
@@ -2074,7 +2087,7 @@ var CryptoController = class {
|
|
|
2074
2087
|
this._fingerprint = null;
|
|
2075
2088
|
this._initialized = false;
|
|
2076
2089
|
deleteKeyPairFromDB();
|
|
2077
|
-
logger$
|
|
2090
|
+
logger$27.debug("[DPoP] Controller destroyed");
|
|
2078
2091
|
}
|
|
2079
2092
|
get publicJwk() {
|
|
2080
2093
|
if (!this._publicJwk) throw new require_operators.DPoPInitError("CryptoController not initialized. Call init() first.");
|
|
@@ -2097,7 +2110,7 @@ var CryptoController = class {
|
|
|
2097
2110
|
|
|
2098
2111
|
//#endregion
|
|
2099
2112
|
//#region src/controllers/NetworkMonitor.ts
|
|
2100
|
-
const logger$
|
|
2113
|
+
const logger$26 = require_operators.getLogger();
|
|
2101
2114
|
/**
|
|
2102
2115
|
* Safely check whether we are running in a browser environment
|
|
2103
2116
|
* with `window` and the relevant event targets.
|
|
@@ -2154,7 +2167,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
2154
2167
|
}
|
|
2155
2168
|
attachListeners() {
|
|
2156
2169
|
if (!hasBrowserNetworkEvents()) {
|
|
2157
|
-
logger$
|
|
2170
|
+
logger$26.debug("NetworkMonitor: no browser environment detected, skipping event listeners");
|
|
2158
2171
|
return;
|
|
2159
2172
|
}
|
|
2160
2173
|
window.addEventListener("online", this._onOnline);
|
|
@@ -2162,7 +2175,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
2162
2175
|
const connection = getNetworkConnection();
|
|
2163
2176
|
if (connection) connection.addEventListener("change", this._onConnectionChange);
|
|
2164
2177
|
this._listenersAttached = true;
|
|
2165
|
-
logger$
|
|
2178
|
+
logger$26.debug("NetworkMonitor: event listeners attached");
|
|
2166
2179
|
}
|
|
2167
2180
|
removeListeners() {
|
|
2168
2181
|
if (!this._listenersAttached) return;
|
|
@@ -2173,10 +2186,10 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
2173
2186
|
if (connection) connection.removeEventListener("change", this._onConnectionChange);
|
|
2174
2187
|
}
|
|
2175
2188
|
this._listenersAttached = false;
|
|
2176
|
-
logger$
|
|
2189
|
+
logger$26.debug("NetworkMonitor: event listeners removed");
|
|
2177
2190
|
}
|
|
2178
2191
|
handleOnline() {
|
|
2179
|
-
logger$
|
|
2192
|
+
logger$26.info("NetworkMonitor: browser went online");
|
|
2180
2193
|
this._isOnline$.next(true);
|
|
2181
2194
|
this._networkChange$.next({
|
|
2182
2195
|
type: "online",
|
|
@@ -2185,7 +2198,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
2185
2198
|
});
|
|
2186
2199
|
}
|
|
2187
2200
|
handleOffline() {
|
|
2188
|
-
logger$
|
|
2201
|
+
logger$26.info("NetworkMonitor: browser went offline");
|
|
2189
2202
|
this._isOnline$.next(false);
|
|
2190
2203
|
this._networkChange$.next({
|
|
2191
2204
|
type: "offline",
|
|
@@ -2194,7 +2207,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
2194
2207
|
}
|
|
2195
2208
|
handleConnectionChange() {
|
|
2196
2209
|
const networkType = getNetworkType();
|
|
2197
|
-
logger$
|
|
2210
|
+
logger$26.info(`NetworkMonitor: connection changed — effectiveType=${networkType ?? "unknown"}`);
|
|
2198
2211
|
this._networkChange$.next({
|
|
2199
2212
|
type: "connection_change",
|
|
2200
2213
|
timestamp: Date.now(),
|
|
@@ -2309,7 +2322,7 @@ function getNavigatorMediaDevices() {
|
|
|
2309
2322
|
|
|
2310
2323
|
//#endregion
|
|
2311
2324
|
//#region src/controllers/PreflightRunner.ts
|
|
2312
|
-
const logger$
|
|
2325
|
+
const logger$25 = require_operators.getLogger();
|
|
2313
2326
|
const DEFAULT_MEDIA_TEST_DURATION_S = 10;
|
|
2314
2327
|
const ICE_GATHERING_TIMEOUT_MS = 1e4;
|
|
2315
2328
|
const SIGNALING_RTT_TIMEOUT_MS = 5e3;
|
|
@@ -2358,7 +2371,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
2358
2371
|
if (!this._options.skipMediaTest) try {
|
|
2359
2372
|
bandwidth = await this.testMediaBandwidth(destination);
|
|
2360
2373
|
} catch (error) {
|
|
2361
|
-
logger$
|
|
2374
|
+
logger$25.warn("[PreflightRunner] Media bandwidth test failed:", error);
|
|
2362
2375
|
warnings.push("Media bandwidth test failed");
|
|
2363
2376
|
}
|
|
2364
2377
|
return {
|
|
@@ -2370,7 +2383,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
2370
2383
|
warnings
|
|
2371
2384
|
};
|
|
2372
2385
|
} catch (error) {
|
|
2373
|
-
logger$
|
|
2386
|
+
logger$25.error("[PreflightRunner] Preflight test failed:", error);
|
|
2374
2387
|
throw new require_operators.PreflightError("preflight", error instanceof Error ? error : new Error(String(error)));
|
|
2375
2388
|
} finally {
|
|
2376
2389
|
this.destroy();
|
|
@@ -2401,7 +2414,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
2401
2414
|
if (track.kind === "video" && track.readyState === "live") videoWorking = true;
|
|
2402
2415
|
}
|
|
2403
2416
|
} catch (error) {
|
|
2404
|
-
logger$
|
|
2417
|
+
logger$25.warn("[PreflightRunner] Device test failed:", error);
|
|
2405
2418
|
} finally {
|
|
2406
2419
|
if (audioStream) audioStream.getTracks().forEach((t) => t.stop());
|
|
2407
2420
|
}
|
|
@@ -2428,7 +2441,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
2428
2441
|
const candidateTypes = /* @__PURE__ */ new Set();
|
|
2429
2442
|
const startTime = Date.now();
|
|
2430
2443
|
const gatheringComplete = new Promise((resolve) => {
|
|
2431
|
-
const timer$
|
|
2444
|
+
const timer$3 = setTimeout(resolve, ICE_GATHERING_TIMEOUT_MS);
|
|
2432
2445
|
peerConnection.onicecandidate = (event) => {
|
|
2433
2446
|
if (event.candidate) {
|
|
2434
2447
|
const candidateStr = event.candidate.candidate;
|
|
@@ -2436,7 +2449,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
2436
2449
|
if (candidateStr.includes("typ srflx")) candidateTypes.add("srflx");
|
|
2437
2450
|
if (candidateStr.includes("typ relay")) candidateTypes.add("relay");
|
|
2438
2451
|
} else {
|
|
2439
|
-
clearTimeout(timer$
|
|
2452
|
+
clearTimeout(timer$3);
|
|
2440
2453
|
resolve();
|
|
2441
2454
|
}
|
|
2442
2455
|
};
|
|
@@ -2459,7 +2472,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
2459
2472
|
rttMs
|
|
2460
2473
|
};
|
|
2461
2474
|
} catch (error) {
|
|
2462
|
-
logger$
|
|
2475
|
+
logger$25.warn("[PreflightRunner] ICE connectivity test failed:", error);
|
|
2463
2476
|
return {
|
|
2464
2477
|
type: "failed",
|
|
2465
2478
|
turnReachable: false,
|
|
@@ -2506,7 +2519,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
2506
2519
|
|
|
2507
2520
|
//#endregion
|
|
2508
2521
|
//#region src/controllers/VisibilityController.ts
|
|
2509
|
-
const logger$
|
|
2522
|
+
const logger$24 = require_operators.getLogger();
|
|
2510
2523
|
/**
|
|
2511
2524
|
* Checks whether the document visibility API is available.
|
|
2512
2525
|
*/
|
|
@@ -2543,8 +2556,8 @@ var VisibilityController = class extends Destroyable {
|
|
|
2543
2556
|
this._boundHandler = this._handleVisibilityChange.bind(this);
|
|
2544
2557
|
if (this._hasVisibilityApi) {
|
|
2545
2558
|
document.addEventListener("visibilitychange", this._boundHandler);
|
|
2546
|
-
logger$
|
|
2547
|
-
} else logger$
|
|
2559
|
+
logger$24.debug("VisibilityController: listening for visibilitychange events");
|
|
2560
|
+
} else logger$24.debug("VisibilityController: document visibility API not available, defaulting to visible");
|
|
2548
2561
|
}
|
|
2549
2562
|
/**
|
|
2550
2563
|
* Observable of the current visibility state.
|
|
@@ -2569,7 +2582,7 @@ var VisibilityController = class extends Destroyable {
|
|
|
2569
2582
|
destroy() {
|
|
2570
2583
|
if (this._hasVisibilityApi) {
|
|
2571
2584
|
document.removeEventListener("visibilitychange", this._boundHandler);
|
|
2572
|
-
logger$
|
|
2585
|
+
logger$24.debug("VisibilityController: removed visibilitychange listener");
|
|
2573
2586
|
}
|
|
2574
2587
|
super.destroy();
|
|
2575
2588
|
}
|
|
@@ -2587,7 +2600,7 @@ var VisibilityController = class extends Destroyable {
|
|
|
2587
2600
|
timestamp: Date.now()
|
|
2588
2601
|
};
|
|
2589
2602
|
this._visibilityChange$.next(changeEvent);
|
|
2590
|
-
logger$
|
|
2603
|
+
logger$24.debug("VisibilityController: visibility changed", {
|
|
2591
2604
|
from: previousState,
|
|
2592
2605
|
to: newState
|
|
2593
2606
|
});
|
|
@@ -2619,14 +2632,14 @@ var Fetchable = class extends Destroyable {
|
|
|
2619
2632
|
};
|
|
2620
2633
|
|
|
2621
2634
|
//#endregion
|
|
2622
|
-
//#region src/core/entities/
|
|
2635
|
+
//#region src/core/entities/User.ts
|
|
2623
2636
|
/**
|
|
2624
|
-
* Authenticated
|
|
2637
|
+
* Authenticated user profile.
|
|
2625
2638
|
*
|
|
2626
2639
|
* Fetched automatically when a {@link SignalWire} connects.
|
|
2627
2640
|
* Contains identity, contact, and organization details.
|
|
2628
2641
|
*/
|
|
2629
|
-
var
|
|
2642
|
+
var User = class extends Fetchable {
|
|
2630
2643
|
constructor(http) {
|
|
2631
2644
|
super("/api/fabric/subscriber/info", http);
|
|
2632
2645
|
}
|
|
@@ -2790,20 +2803,18 @@ const RPCEventAckResponse = (id) => makeRPCResponse({
|
|
|
2790
2803
|
|
|
2791
2804
|
//#endregion
|
|
2792
2805
|
//#region src/managers/AttachManager.ts
|
|
2793
|
-
const logger$
|
|
2806
|
+
const logger$23 = require_operators.getLogger();
|
|
2794
2807
|
var AttachManager = class {
|
|
2795
2808
|
constructor(storage, deviceController, reconnectCallsTimeout, attachKey) {
|
|
2796
2809
|
this.storage = storage;
|
|
2797
2810
|
this.deviceController = deviceController;
|
|
2798
2811
|
this.reconnectCallsTimeout = reconnectCallsTimeout;
|
|
2799
2812
|
this.attachKey = attachKey;
|
|
2813
|
+
this.writeQueue = Promise.resolve();
|
|
2800
2814
|
}
|
|
2801
2815
|
async detachAll() {
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
id: callId,
|
|
2805
|
-
nodeId: attached[callId].nodeId,
|
|
2806
|
-
mediaDirections: attached[callId].mediaDirections
|
|
2816
|
+
await this.mutate((attached) => {
|
|
2817
|
+
return {};
|
|
2807
2818
|
});
|
|
2808
2819
|
}
|
|
2809
2820
|
setSession(session) {
|
|
@@ -2813,7 +2824,7 @@ var AttachManager = class {
|
|
|
2813
2824
|
try {
|
|
2814
2825
|
return await this.storage.getItem(this.attachKey) ?? {};
|
|
2815
2826
|
} catch (error) {
|
|
2816
|
-
logger$
|
|
2827
|
+
logger$23.warn("[AttachManager] Failed to retrieve attached calls from storage", error);
|
|
2817
2828
|
return {};
|
|
2818
2829
|
}
|
|
2819
2830
|
}
|
|
@@ -2821,34 +2832,50 @@ var AttachManager = class {
|
|
|
2821
2832
|
try {
|
|
2822
2833
|
await this.storage.setItem(this.attachKey, attached);
|
|
2823
2834
|
} catch (error) {
|
|
2824
|
-
logger$
|
|
2835
|
+
logger$23.warn("[AttachManager] Failed to write attached calls to storage", error);
|
|
2825
2836
|
}
|
|
2826
2837
|
}
|
|
2838
|
+
/**
|
|
2839
|
+
* Serialize a read-modify-write operation against the attached-calls
|
|
2840
|
+
* storage. The mutator receives the current state and returns the new
|
|
2841
|
+
* state. Concurrent calls queue behind the in-flight one so writes never
|
|
2842
|
+
* interleave.
|
|
2843
|
+
*/
|
|
2844
|
+
async mutate(mutator) {
|
|
2845
|
+
const next = this.writeQueue.then(async () => {
|
|
2846
|
+
const updated = await mutator(await this.readAttached());
|
|
2847
|
+
await this.writeAttached(updated);
|
|
2848
|
+
});
|
|
2849
|
+
this.writeQueue = next.catch(() => void 0);
|
|
2850
|
+
return next;
|
|
2851
|
+
}
|
|
2827
2852
|
async attach(call) {
|
|
2828
2853
|
if (!call.to) {
|
|
2829
|
-
logger$
|
|
2854
|
+
logger$23.warn("[AttachManager] Skip attach for calls with no destination");
|
|
2830
2855
|
return;
|
|
2831
2856
|
}
|
|
2857
|
+
const destination = call.to;
|
|
2832
2858
|
const attachment = {
|
|
2833
2859
|
nodeId: call.nodeId,
|
|
2834
|
-
destination
|
|
2860
|
+
destination,
|
|
2835
2861
|
mediaDirections: call.mediaDirections,
|
|
2836
2862
|
audioInputDevice: call.mediaDirections.audio !== "inactive" ? this.deviceController.selectedAudioInputDevice : null,
|
|
2837
2863
|
videoInputDevice: call.mediaDirections.video !== "inactive" ? this.deviceController.selectedVideoInputDevice : null,
|
|
2838
2864
|
attachedAt: Date.now()
|
|
2839
2865
|
};
|
|
2840
|
-
|
|
2841
|
-
...
|
|
2866
|
+
await this.mutate((attached) => ({
|
|
2867
|
+
...attached,
|
|
2842
2868
|
[call.id]: attachment
|
|
2843
|
-
};
|
|
2844
|
-
await this.writeAttached(updated);
|
|
2869
|
+
}));
|
|
2845
2870
|
}
|
|
2846
2871
|
async detach(call) {
|
|
2847
|
-
|
|
2848
|
-
|
|
2872
|
+
await this.mutate((attached) => {
|
|
2873
|
+
const { [call.id]: _, ...remaining } = attached;
|
|
2874
|
+
return remaining;
|
|
2875
|
+
});
|
|
2849
2876
|
}
|
|
2850
2877
|
async flush() {
|
|
2851
|
-
await this.
|
|
2878
|
+
await this.mutate(() => ({}));
|
|
2852
2879
|
}
|
|
2853
2880
|
/**
|
|
2854
2881
|
* Reattach to previously active calls by sending verto.invite with
|
|
@@ -2877,15 +2904,15 @@ var AttachManager = class {
|
|
|
2877
2904
|
callId,
|
|
2878
2905
|
...options
|
|
2879
2906
|
});
|
|
2880
|
-
logger$
|
|
2907
|
+
logger$23.info(`[AttachManager] Reattached call ${callId} (attempt ${attempt})`);
|
|
2881
2908
|
succeeded = true;
|
|
2882
2909
|
break;
|
|
2883
2910
|
} catch (error) {
|
|
2884
|
-
logger$
|
|
2911
|
+
logger$23.warn(`[AttachManager] Reattach attempt ${attempt}/3 failed for call ${callId}:`, error);
|
|
2885
2912
|
if (attempt < 3) await new Promise((r) => setTimeout(r, (attempt + 1) * 1e3));
|
|
2886
2913
|
}
|
|
2887
2914
|
if (!succeeded) {
|
|
2888
|
-
logger$
|
|
2915
|
+
logger$23.warn(`[AttachManager] Reattach failed after 3 attempts for call ${callId}, removing reference`);
|
|
2889
2916
|
await this.detach({
|
|
2890
2917
|
id: callId,
|
|
2891
2918
|
mediaDirections: attachment.mediaDirections
|
|
@@ -2920,20 +2947,31 @@ var AttachManager = class {
|
|
|
2920
2947
|
};
|
|
2921
2948
|
}
|
|
2922
2949
|
/**
|
|
2923
|
-
*
|
|
2924
|
-
*
|
|
2950
|
+
* Look up stored attachment data for a call id and return CallOptions
|
|
2951
|
+
* suitable for rehydrating a reattached call. Returns undefined when no
|
|
2952
|
+
* matching entry exists in storage.
|
|
2953
|
+
*
|
|
2954
|
+
* Used by the session-level verto.attach handler when the server pushes
|
|
2955
|
+
* an attach event for a call the client doesn't have an object for yet
|
|
2956
|
+
* (e.g. after a reload).
|
|
2925
2957
|
*/
|
|
2926
|
-
consumePendingAttachment(
|
|
2927
|
-
async detachExpired() {
|
|
2958
|
+
async consumePendingAttachment(callId) {
|
|
2928
2959
|
const attached = await this.readAttached();
|
|
2960
|
+
if (!Object.hasOwn(attached, callId)) return;
|
|
2961
|
+
return this.buildCallOptions(attached[callId]);
|
|
2962
|
+
}
|
|
2963
|
+
async detachExpired() {
|
|
2929
2964
|
const now = Date.now();
|
|
2930
2965
|
const timeout$4 = this.reconnectCallsTimeout;
|
|
2931
|
-
|
|
2932
|
-
if (expired.length > 0) {
|
|
2966
|
+
await this.mutate((attached) => {
|
|
2933
2967
|
const remaining = { ...attached };
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2968
|
+
let changed = false;
|
|
2969
|
+
for (const [callId, attachment] of Object.entries(attached)) if (now - attachment.attachedAt > timeout$4) {
|
|
2970
|
+
delete remaining[callId];
|
|
2971
|
+
changed = true;
|
|
2972
|
+
}
|
|
2973
|
+
return changed ? remaining : attached;
|
|
2974
|
+
});
|
|
2937
2975
|
}
|
|
2938
2976
|
};
|
|
2939
2977
|
|
|
@@ -3188,7 +3226,7 @@ function toggleHandraiseMethod(is) {
|
|
|
3188
3226
|
|
|
3189
3227
|
//#endregion
|
|
3190
3228
|
//#region src/core/entities/Participant.ts
|
|
3191
|
-
const logger$
|
|
3229
|
+
const logger$22 = require_operators.getLogger();
|
|
3192
3230
|
const initialState = {};
|
|
3193
3231
|
/**
|
|
3194
3232
|
* Represents a participant in a call.
|
|
@@ -3240,15 +3278,35 @@ var Participant = class extends Destroyable {
|
|
|
3240
3278
|
get deaf$() {
|
|
3241
3279
|
return this.cachedObservable("deaf$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.deaf), (0, rxjs_operators.distinctUntilChanged)()));
|
|
3242
3280
|
}
|
|
3243
|
-
/**
|
|
3281
|
+
/**
|
|
3282
|
+
* Observable of the participant's **server-side** microphone input volume
|
|
3283
|
+
* as reported by the mix engine. This is gain applied on the bridged audio
|
|
3284
|
+
* leg (FreeSWITCH channel read volume), NOT the local browser mic. For a
|
|
3285
|
+
* local PC mic control, see {@link Call.setLocalMicrophoneGain}.
|
|
3286
|
+
*
|
|
3287
|
+
* @see {@link setAudioInputVolume}
|
|
3288
|
+
*/
|
|
3244
3289
|
get inputVolume$() {
|
|
3245
3290
|
return this.cachedObservable("inputVolume$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.input_volume), (0, rxjs_operators.distinctUntilChanged)()));
|
|
3246
3291
|
}
|
|
3247
|
-
/**
|
|
3292
|
+
/**
|
|
3293
|
+
* Observable of the participant's **server-side** speaker output volume as
|
|
3294
|
+
* reported by the mix engine (FreeSWITCH channel write volume). NOT the
|
|
3295
|
+
* local HTML `<audio>` element volume — set that on your own element.
|
|
3296
|
+
*
|
|
3297
|
+
* @see {@link setAudioOutputVolume}
|
|
3298
|
+
*/
|
|
3248
3299
|
get outputVolume$() {
|
|
3249
3300
|
return this.cachedObservable("outputVolume$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.output_volume), (0, rxjs_operators.distinctUntilChanged)()));
|
|
3250
3301
|
}
|
|
3251
|
-
/**
|
|
3302
|
+
/**
|
|
3303
|
+
* Observable of the **conference-only** microphone energy/gate sensitivity
|
|
3304
|
+
* level for this member. Routes through the conferencing mix engine and has
|
|
3305
|
+
* no effect on 1:1 WebRTC calls. Populated from `member.updated` events for
|
|
3306
|
+
* conference members.
|
|
3307
|
+
*
|
|
3308
|
+
* @see {@link setAudioInputSensitivity}
|
|
3309
|
+
*/
|
|
3252
3310
|
get inputSensitivity$() {
|
|
3253
3311
|
return this.cachedObservable("inputSensitivity$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.input_sensitivity), (0, rxjs_operators.distinctUntilChanged)()));
|
|
3254
3312
|
}
|
|
@@ -3276,9 +3334,9 @@ var Participant = class extends Destroyable {
|
|
|
3276
3334
|
get meta$() {
|
|
3277
3335
|
return this.cachedObservable("meta$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.meta), (0, rxjs_operators.distinctUntilChanged)()));
|
|
3278
3336
|
}
|
|
3279
|
-
/** Observable of the participant's
|
|
3280
|
-
get
|
|
3281
|
-
return this.cachedObservable("
|
|
3337
|
+
/** Observable of the participant's user ID. */
|
|
3338
|
+
get userId$() {
|
|
3339
|
+
return this.cachedObservable("userId$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.subscriber_id), (0, rxjs_operators.distinctUntilChanged)()));
|
|
3282
3340
|
}
|
|
3283
3341
|
/** Observable of the participant's address ID. */
|
|
3284
3342
|
get addressId$() {
|
|
@@ -3336,15 +3394,25 @@ var Participant = class extends Destroyable {
|
|
|
3336
3394
|
get deaf() {
|
|
3337
3395
|
return this._state$.value.deaf ?? false;
|
|
3338
3396
|
}
|
|
3339
|
-
/**
|
|
3397
|
+
/**
|
|
3398
|
+
* Current **server-side** microphone input volume as reported by the mix
|
|
3399
|
+
* engine, or `undefined` if not set. Not the local PC mic — see
|
|
3400
|
+
* {@link Call.setLocalMicrophoneGain} for browser-side control.
|
|
3401
|
+
*/
|
|
3340
3402
|
get inputVolume() {
|
|
3341
3403
|
return this._state$.value.input_volume;
|
|
3342
3404
|
}
|
|
3343
|
-
/**
|
|
3405
|
+
/**
|
|
3406
|
+
* Current **server-side** speaker output volume from the mix engine, or
|
|
3407
|
+
* `undefined` if not set. Not the local `<audio>` element volume.
|
|
3408
|
+
*/
|
|
3344
3409
|
get outputVolume() {
|
|
3345
3410
|
return this._state$.value.output_volume;
|
|
3346
3411
|
}
|
|
3347
|
-
/**
|
|
3412
|
+
/**
|
|
3413
|
+
* Current **conference-only** microphone sensitivity/gate level, or
|
|
3414
|
+
* `undefined` if not set. Applies only to conference members.
|
|
3415
|
+
*/
|
|
3348
3416
|
get inputSensitivity() {
|
|
3349
3417
|
return this._state$.value.input_sensitivity;
|
|
3350
3418
|
}
|
|
@@ -3372,8 +3440,8 @@ var Participant = class extends Destroyable {
|
|
|
3372
3440
|
get meta() {
|
|
3373
3441
|
return this._state$.value.meta;
|
|
3374
3442
|
}
|
|
3375
|
-
/**
|
|
3376
|
-
get
|
|
3443
|
+
/** User ID of this participant, or `undefined` if not available. */
|
|
3444
|
+
get userId() {
|
|
3377
3445
|
return this._state$.value.subscriber_id;
|
|
3378
3446
|
}
|
|
3379
3447
|
/** Address ID of this participant, or `undefined` if not available. */
|
|
@@ -3448,19 +3516,44 @@ var Participant = class extends Destroyable {
|
|
|
3448
3516
|
async toggleLowbitrate() {
|
|
3449
3517
|
throw new require_operators.UnimplementedError();
|
|
3450
3518
|
}
|
|
3451
|
-
/**
|
|
3519
|
+
/**
|
|
3520
|
+
* Adjusts the **conference-only** microphone energy gate / sensitivity level
|
|
3521
|
+
* for this member. Routes through the conferencing mix engine
|
|
3522
|
+
* (`signalwire.conferencing member.set_input_sensitivity`) and has no effect
|
|
3523
|
+
* on 1:1 WebRTC calls — for those, use browser audio constraints via
|
|
3524
|
+
* {@link Call.setNoiseSuppression} / {@link Call.setAutoGainControl}.
|
|
3525
|
+
*
|
|
3526
|
+
* This is **not** a local PC mic gain control; it only changes how the
|
|
3527
|
+
* server-side mixer decides to open the mic gate on this participant.
|
|
3528
|
+
*
|
|
3529
|
+
* @param value - Sensitivity level as understood by the conference engine
|
|
3530
|
+
* (integer, larger values are more sensitive).
|
|
3531
|
+
*/
|
|
3452
3532
|
async setAudioInputSensitivity(value) {
|
|
3453
3533
|
await this.executeMethod(this.id, "call.microphone.sensitivity.set", { sensitivity: value });
|
|
3454
3534
|
}
|
|
3455
3535
|
/**
|
|
3456
|
-
* Sets the microphone
|
|
3536
|
+
* Sets the **server-side** microphone volume on this participant's bridged
|
|
3537
|
+
* call leg. Applies a multiplier to the audio flowing through the mix
|
|
3538
|
+
* engine (FreeSWITCH channel read volume) — changes what other participants
|
|
3539
|
+
* hear, not what the local browser captures.
|
|
3540
|
+
*
|
|
3541
|
+
* For local PC mic gain, use {@link Call.setLocalMicrophoneGain} instead.
|
|
3542
|
+
*
|
|
3457
3543
|
* @param value - Volume level (0-100).
|
|
3458
3544
|
*/
|
|
3459
3545
|
async setAudioInputVolume(value) {
|
|
3460
3546
|
await this.executeMethod(this.id, "call.microphone.volume.set", { volume: value });
|
|
3461
3547
|
}
|
|
3462
3548
|
/**
|
|
3463
|
-
* Sets the speaker
|
|
3549
|
+
* Sets the **server-side** speaker volume on this participant's bridged call
|
|
3550
|
+
* leg (FreeSWITCH channel write volume) — what this participant hears from
|
|
3551
|
+
* the mix before it reaches their client.
|
|
3552
|
+
*
|
|
3553
|
+
* For local playback volume (the `<audio>` element the consumer attaches
|
|
3554
|
+
* `remoteStream` to), set `audioElement.volume` directly in the consumer's
|
|
3555
|
+
* code.
|
|
3556
|
+
*
|
|
3464
3557
|
* @param value - Volume level (0-100).
|
|
3465
3558
|
*/
|
|
3466
3559
|
async setAudioOutputVolume(value) {
|
|
@@ -3566,7 +3659,7 @@ var SelfParticipant = class extends Participant {
|
|
|
3566
3659
|
try {
|
|
3567
3660
|
await this.vertoManager.addScreenMedia();
|
|
3568
3661
|
} catch (error) {
|
|
3569
|
-
logger$
|
|
3662
|
+
logger$22.error("[Participant.startScreenShare] Screen share error:", error);
|
|
3570
3663
|
}
|
|
3571
3664
|
}
|
|
3572
3665
|
/** Observable of the current screen share status. */
|
|
@@ -3586,7 +3679,7 @@ var SelfParticipant = class extends Participant {
|
|
|
3586
3679
|
try {
|
|
3587
3680
|
await this.vertoManager.addInputDevice(options);
|
|
3588
3681
|
} catch (error) {
|
|
3589
|
-
logger$
|
|
3682
|
+
logger$22.error("[Participant.startScreenShare] Screen share error:", error);
|
|
3590
3683
|
}
|
|
3591
3684
|
}
|
|
3592
3685
|
/** Removes an additional media input device by ID. */
|
|
@@ -3648,7 +3741,7 @@ var SelfParticipant = class extends Participant {
|
|
|
3648
3741
|
*/
|
|
3649
3742
|
exitStudioModeIfActive() {
|
|
3650
3743
|
if (this._studioAudio$.value) {
|
|
3651
|
-
logger$
|
|
3744
|
+
logger$22.debug("[SelfParticipant] Exiting studio audio mode due to individual flag toggle");
|
|
3652
3745
|
this._studioAudio$.next(false);
|
|
3653
3746
|
}
|
|
3654
3747
|
}
|
|
@@ -3672,7 +3765,7 @@ var SelfParticipant = class extends Participant {
|
|
|
3672
3765
|
try {
|
|
3673
3766
|
await super.mute();
|
|
3674
3767
|
} catch (error) {
|
|
3675
|
-
logger$
|
|
3768
|
+
logger$22.warn("[Participant.toggleAudioInput] Server Error while muting audio input, proceeding with local toggle anyway", error);
|
|
3676
3769
|
} finally {
|
|
3677
3770
|
this.vertoManager.muteMainAudioInputDevice();
|
|
3678
3771
|
}
|
|
@@ -3682,7 +3775,7 @@ var SelfParticipant = class extends Participant {
|
|
|
3682
3775
|
try {
|
|
3683
3776
|
await super.unmute();
|
|
3684
3777
|
} catch (error) {
|
|
3685
|
-
logger$
|
|
3778
|
+
logger$22.warn("[Participant.toggleAudioInput] Server Error while unmuting audio input, proceeding with local toggle anyway", error);
|
|
3686
3779
|
} finally {
|
|
3687
3780
|
await this.vertoManager.unmuteMainAudioInputDevice();
|
|
3688
3781
|
}
|
|
@@ -3692,7 +3785,7 @@ var SelfParticipant = class extends Participant {
|
|
|
3692
3785
|
try {
|
|
3693
3786
|
await super.muteVideo();
|
|
3694
3787
|
} catch (error) {
|
|
3695
|
-
logger$
|
|
3788
|
+
logger$22.warn("[Participant.toggleVideoInput] Server Error while muting video input, proceeding with local toggle anyway", error);
|
|
3696
3789
|
} finally {
|
|
3697
3790
|
this.vertoManager.muteMainVideoInputDevice();
|
|
3698
3791
|
}
|
|
@@ -3702,7 +3795,7 @@ var SelfParticipant = class extends Participant {
|
|
|
3702
3795
|
try {
|
|
3703
3796
|
await super.unmuteVideo();
|
|
3704
3797
|
} catch (error) {
|
|
3705
|
-
logger$
|
|
3798
|
+
logger$22.warn("[Participant.toggleVideoInput] Server Error while unmuting video input, proceeding with local toggle anyway", error);
|
|
3706
3799
|
} finally {
|
|
3707
3800
|
await this.vertoManager.unmuteMainVideoInputDevice();
|
|
3708
3801
|
}
|
|
@@ -3796,7 +3889,7 @@ function isLayoutChangedPayload(value) {
|
|
|
3796
3889
|
|
|
3797
3890
|
//#endregion
|
|
3798
3891
|
//#region src/managers/CallEventsManager.ts
|
|
3799
|
-
const logger$
|
|
3892
|
+
const logger$21 = require_operators.getLogger();
|
|
3800
3893
|
const initialSessionState = {};
|
|
3801
3894
|
/** @internal */
|
|
3802
3895
|
var CallEventsManager = class extends Destroyable {
|
|
@@ -3900,7 +3993,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
3900
3993
|
}
|
|
3901
3994
|
initSubscriptions() {
|
|
3902
3995
|
this.subscribeTo(this.callJoinedEvent$, (callJoinedEvent) => {
|
|
3903
|
-
logger$
|
|
3996
|
+
logger$21.debug("[CallEventsManager] Handling call.joined event for call/session IDs:", {
|
|
3904
3997
|
callId: callJoinedEvent.call_id,
|
|
3905
3998
|
roomSessionId: callJoinedEvent.room_session_id
|
|
3906
3999
|
});
|
|
@@ -3927,19 +4020,19 @@ var CallEventsManager = class extends Destroyable {
|
|
|
3927
4020
|
if (this._self$.value?.capabilities.setLayout) this.updateLayouts();
|
|
3928
4021
|
});
|
|
3929
4022
|
this.subscribeTo(this.memberUpdates$, (member) => {
|
|
3930
|
-
logger$
|
|
4023
|
+
logger$21.debug("[CallEventsManager] Handling member update event for member ID:", member);
|
|
3931
4024
|
this.upsertParticipant(member);
|
|
3932
4025
|
});
|
|
3933
4026
|
this.subscribeTo(this.webRtcCallSession.memberLeft$, (memberLeftEvent) => {
|
|
3934
|
-
logger$
|
|
4027
|
+
logger$21.debug("[CallEventsManager] Handling member.left event for member ID:", memberLeftEvent.member.member_id);
|
|
3935
4028
|
const participants = { ...this._participants$.value };
|
|
3936
4029
|
if (memberLeftEvent.member.member_id in participants) {
|
|
3937
4030
|
delete participants[memberLeftEvent.member.member_id];
|
|
3938
4031
|
this._participants$.next(participants);
|
|
3939
|
-
} else logger$
|
|
4032
|
+
} else logger$21.warn(`[CallEventsManager] Received member.left event for unknown member ID: ${memberLeftEvent.member.member_id}`);
|
|
3940
4033
|
});
|
|
3941
4034
|
this.subscribeTo(this.webRtcCallSession.callUpdated$, (callUpdatedEvent) => {
|
|
3942
|
-
logger$
|
|
4035
|
+
logger$21.debug("[CallEventsManager] Handling call.updated event:", callUpdatedEvent);
|
|
3943
4036
|
const roomSession = callUpdatedEvent.room_session;
|
|
3944
4037
|
this._sessionState$.next({
|
|
3945
4038
|
...this._sessionState$.value,
|
|
@@ -3954,7 +4047,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
3954
4047
|
});
|
|
3955
4048
|
});
|
|
3956
4049
|
this.subscribeTo(this.layoutChangedEvent$, (layoutChangedEvent) => {
|
|
3957
|
-
logger$
|
|
4050
|
+
logger$21.debug("[CallEventsManager] Handling layout.changed event:", layoutChangedEvent);
|
|
3958
4051
|
this._sessionState$.next({
|
|
3959
4052
|
...this._sessionState$.value,
|
|
3960
4053
|
layout_name: layoutChangedEvent.id,
|
|
@@ -3964,10 +4057,10 @@ var CallEventsManager = class extends Destroyable {
|
|
|
3964
4057
|
});
|
|
3965
4058
|
}
|
|
3966
4059
|
updateParticipantPositions(layoutChangedEvent) {
|
|
3967
|
-
if (Object.keys(this._participants$.value).length > 0 && !layoutChangedEvent.layers.some((layer) => !!layer.member_id)) logger$
|
|
4060
|
+
if (Object.keys(this._participants$.value).length > 0 && !layoutChangedEvent.layers.some((layer) => !!layer.member_id)) logger$21.warn("[CallEventsManager] No layers with member_id found in layout.changed event. Nothing to update.");
|
|
3968
4061
|
layoutChangedEvent.layers.filter((layer) => !!layer.member_id).filter((layer) => {
|
|
3969
4062
|
if (!(layer.member_id in this._participants$.value)) {
|
|
3970
|
-
logger$
|
|
4063
|
+
logger$21.warn(`[CallEventsManager] Skipping layout layer for unknown member_id: ${layer.member_id}`);
|
|
3971
4064
|
return false;
|
|
3972
4065
|
}
|
|
3973
4066
|
return true;
|
|
@@ -3990,7 +4083,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
3990
4083
|
layouts: response.result.layouts
|
|
3991
4084
|
});
|
|
3992
4085
|
}).catch((error) => {
|
|
3993
|
-
logger$
|
|
4086
|
+
logger$21.error("[CallEventsManager] Error fetching layouts:", error);
|
|
3994
4087
|
});
|
|
3995
4088
|
}
|
|
3996
4089
|
updateParticipants(members) {
|
|
@@ -4006,7 +4099,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
4006
4099
|
}
|
|
4007
4100
|
const participant = this._participants$.value[member.member_id];
|
|
4008
4101
|
const oldValue = participant.value;
|
|
4009
|
-
logger$
|
|
4102
|
+
logger$21.debug("[CallEventsManager] Updating participant:", member.member_id, {
|
|
4010
4103
|
oldValue,
|
|
4011
4104
|
newValue: member
|
|
4012
4105
|
});
|
|
@@ -4019,17 +4112,17 @@ var CallEventsManager = class extends Destroyable {
|
|
|
4019
4112
|
}
|
|
4020
4113
|
get callJoinedEvent$() {
|
|
4021
4114
|
return this.cachedObservable("callJoinedEvent$", () => this.webRtcCallSession.callEvent$.pipe((0, rxjs.filter)(isCallJoinedPayload), (0, rxjs.tap)((event) => {
|
|
4022
|
-
logger$
|
|
4115
|
+
logger$21.debug("[CallEventsManager] Call joined event:", event);
|
|
4023
4116
|
})));
|
|
4024
4117
|
}
|
|
4025
4118
|
get layoutChangedEvent$() {
|
|
4026
4119
|
return this.cachedObservable("layoutChangedEvent$", () => this.webRtcCallSession.callEvent$.pipe(require_operators.filterAs(isLayoutChangedPayload, "layout"), (0, rxjs.tap)((event) => {
|
|
4027
|
-
logger$
|
|
4120
|
+
logger$21.debug("[CallEventsManager] Layout changed event:", event);
|
|
4028
4121
|
})));
|
|
4029
4122
|
}
|
|
4030
4123
|
get memberUpdates$() {
|
|
4031
4124
|
return this.cachedObservable("memberUpdates$", () => (0, rxjs.merge)(this.webRtcCallSession.memberJoined$, this.webRtcCallSession.memberUpdated$, this.webRtcCallSession.memberTalking$).pipe((0, rxjs.map)((event) => event.member), (0, rxjs.tap)((event) => {
|
|
4032
|
-
logger$
|
|
4125
|
+
logger$21.debug("[CallEventsManager] Member update event:", event);
|
|
4033
4126
|
})));
|
|
4034
4127
|
}
|
|
4035
4128
|
destroy() {
|
|
@@ -4285,7 +4378,7 @@ function appendStereoParams(fmtpLine, maxBitrate) {
|
|
|
4285
4378
|
|
|
4286
4379
|
//#endregion
|
|
4287
4380
|
//#region src/controllers/ICEGatheringController.ts
|
|
4288
|
-
const logger$
|
|
4381
|
+
const logger$20 = require_operators.getLogger();
|
|
4289
4382
|
var ICEGatheringController = class extends Destroyable {
|
|
4290
4383
|
constructor(peerConnection, peerConnectionControllerNegotiating$, options = {}) {
|
|
4291
4384
|
super();
|
|
@@ -4293,23 +4386,23 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
4293
4386
|
this.peerConnectionControllerNegotiating$ = peerConnectionControllerNegotiating$;
|
|
4294
4387
|
this.onicegatheringstatechangeHandler = () => {
|
|
4295
4388
|
const { iceGatheringState } = this.peerConnection;
|
|
4296
|
-
logger$
|
|
4389
|
+
logger$20.debug(`[ICEGatheringController] ICE gathering state changed to: ${iceGatheringState}`);
|
|
4297
4390
|
if (iceGatheringState === "gathering") this._iceCandidatesState.next({
|
|
4298
4391
|
state: "gathering",
|
|
4299
4392
|
validSDP: false
|
|
4300
4393
|
});
|
|
4301
4394
|
};
|
|
4302
4395
|
this.onicecandidateHandler = (event) => {
|
|
4303
|
-
logger$
|
|
4396
|
+
logger$20.debug("[ICEGatheringController] ICE candidate event received:", event.candidate);
|
|
4304
4397
|
this.removeTimer("iceCandidateTimer");
|
|
4305
4398
|
if (event.candidate) this.iceCandidateTimer = setTimeout(() => {
|
|
4306
4399
|
if (this.peerConnection.iceGatheringState !== "complete") {
|
|
4307
|
-
logger$
|
|
4400
|
+
logger$20.warn("[ICEGatheringController] ICE candidate timeout, using current SDP");
|
|
4308
4401
|
this.handleICECandidateTimeout();
|
|
4309
4402
|
}
|
|
4310
4403
|
}, this.iceCandidateTimeout);
|
|
4311
4404
|
else {
|
|
4312
|
-
logger$
|
|
4405
|
+
logger$20.debug("[ICEGatheringController] ICE gathering completed: null candidate received");
|
|
4313
4406
|
this.removeTimer("iceGatheringTimer");
|
|
4314
4407
|
this.handleICEGatheringComplete();
|
|
4315
4408
|
}
|
|
@@ -4327,7 +4420,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
4327
4420
|
this.setupEventListeners();
|
|
4328
4421
|
this.iceGatheringTimer = setTimeout(() => {
|
|
4329
4422
|
if (this.peerConnection.iceGatheringState !== "complete") {
|
|
4330
|
-
logger$
|
|
4423
|
+
logger$20.warn("[ICEGatheringController] ICE gathering timeout, using current SDP");
|
|
4331
4424
|
this.handleICEGatheringTimeout();
|
|
4332
4425
|
}
|
|
4333
4426
|
}, this.iceGatheringTimeout);
|
|
@@ -4354,9 +4447,9 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
4354
4447
|
this.relayOnly = value;
|
|
4355
4448
|
}
|
|
4356
4449
|
handleICEGatheringComplete() {
|
|
4357
|
-
logger$
|
|
4358
|
-
logger$
|
|
4359
|
-
logger$
|
|
4450
|
+
logger$20.debug("[ICEGatheringController] Handling ICE gathering complete");
|
|
4451
|
+
logger$20.debug(`[ICEGatheringController] Checking ICE gathering state: ${this.peerConnection.iceGatheringState}`);
|
|
4452
|
+
logger$20.debug("[ICEGatheringController] ICE gathering complete");
|
|
4360
4453
|
this._iceCandidatesState.next({
|
|
4361
4454
|
state: "complete",
|
|
4362
4455
|
validSDP: this.hasValidLocalDescriptionSDP
|
|
@@ -4372,21 +4465,21 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
4372
4465
|
this.removeTimer("iceGatheringTimer");
|
|
4373
4466
|
const validSDP = this.hasValidLocalDescriptionSDP;
|
|
4374
4467
|
if (validSDP) {
|
|
4375
|
-
logger$
|
|
4468
|
+
logger$20.debug("[ICEGatheringController] Local SDP is valid");
|
|
4376
4469
|
this._iceCandidatesState.next({
|
|
4377
4470
|
state: "timeout",
|
|
4378
4471
|
validSDP
|
|
4379
4472
|
});
|
|
4380
4473
|
this.stopGathering();
|
|
4381
|
-
} else logger$
|
|
4474
|
+
} else logger$20.debug("### ICE gathering timeout\n", this.peerConnection.localDescription?.sdp);
|
|
4382
4475
|
}
|
|
4383
4476
|
handleICECandidateTimeout() {
|
|
4384
4477
|
if (this.iceCandidateTimer) this.removeTimer("iceCandidateTimer");
|
|
4385
|
-
logger$
|
|
4478
|
+
logger$20.warn("[ICEGatheringController] ICE candidate timeout");
|
|
4386
4479
|
const validSDP = this.hasValidLocalDescriptionSDP;
|
|
4387
4480
|
if (!validSDP && !this.relayOnly) this.restartICEGatheringWithRelayOnly();
|
|
4388
4481
|
else {
|
|
4389
|
-
logger$
|
|
4482
|
+
logger$20.debug("[ICEGatheringController] Using current SDP due to ICE candidate timeout");
|
|
4390
4483
|
this._iceCandidatesState.next({
|
|
4391
4484
|
state: "timeout",
|
|
4392
4485
|
validSDP
|
|
@@ -4395,22 +4488,22 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
4395
4488
|
}
|
|
4396
4489
|
}
|
|
4397
4490
|
restartICEGatheringWithRelayOnly() {
|
|
4398
|
-
logger$
|
|
4491
|
+
logger$20.debug("[ICEGatheringController] Restarting ICE gathering with relay-only candidates");
|
|
4399
4492
|
this.relayOnly = true;
|
|
4400
4493
|
this.peerConnection.setConfiguration({
|
|
4401
4494
|
...this.peerConnection.getConfiguration(),
|
|
4402
4495
|
iceTransportPolicy: "relay"
|
|
4403
4496
|
});
|
|
4404
|
-
|
|
4497
|
+
this.peerConnection.restartIce();
|
|
4405
4498
|
}
|
|
4406
|
-
removeTimer(timer$
|
|
4407
|
-
if (this[timer$
|
|
4408
|
-
clearTimeout(this[timer$
|
|
4409
|
-
this[timer$
|
|
4499
|
+
removeTimer(timer$3) {
|
|
4500
|
+
if (this[timer$3]) {
|
|
4501
|
+
clearTimeout(this[timer$3]);
|
|
4502
|
+
this[timer$3] = void 0;
|
|
4410
4503
|
}
|
|
4411
4504
|
}
|
|
4412
4505
|
clearAllTimers() {
|
|
4413
|
-
logger$
|
|
4506
|
+
logger$20.debug("[ICEGatheringController] Clearing all timers");
|
|
4414
4507
|
this.removeTimer("iceGatheringTimer");
|
|
4415
4508
|
this.removeTimer("iceCandidateTimer");
|
|
4416
4509
|
}
|
|
@@ -4419,16 +4512,179 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
4419
4512
|
this.peerConnection.removeEventListener("icecandidate", this.onicecandidateHandler);
|
|
4420
4513
|
}
|
|
4421
4514
|
destroy() {
|
|
4422
|
-
logger$
|
|
4515
|
+
logger$20.debug("[ICEGatheringController] Destroying ICEGatheringController");
|
|
4423
4516
|
this.clearAllTimers();
|
|
4424
4517
|
this.removeEventListeners();
|
|
4425
4518
|
super.destroy();
|
|
4426
4519
|
}
|
|
4427
4520
|
};
|
|
4428
4521
|
|
|
4522
|
+
//#endregion
|
|
4523
|
+
//#region src/controllers/LocalAudioPipeline.ts
|
|
4524
|
+
const logger$19 = require_operators.getLogger();
|
|
4525
|
+
/**
|
|
4526
|
+
* Web Audio pipeline for the local microphone stream.
|
|
4527
|
+
*
|
|
4528
|
+
* Wraps the raw mic `MediaStreamTrack` in a graph of:
|
|
4529
|
+
*
|
|
4530
|
+
* ```
|
|
4531
|
+
* MediaStreamAudioSourceNode → GainNode → AnalyserNode → MediaStreamAudioDestinationNode
|
|
4532
|
+
* ```
|
|
4533
|
+
*
|
|
4534
|
+
* The {@link outputTrack} from the destination node is what callers should
|
|
4535
|
+
* attach to the `RTCRtpSender` in place of the raw mic track. The same
|
|
4536
|
+
* destination track is reused across input changes (device switch, mute /
|
|
4537
|
+
* unmute track replacement) so the sender reference stays stable — only the
|
|
4538
|
+
* source end of the graph is rebuilt.
|
|
4539
|
+
*
|
|
4540
|
+
* The pipeline owns a single {@link AudioContext}. Callers must invoke
|
|
4541
|
+
* {@link destroy} to release it when the call ends.
|
|
4542
|
+
*/
|
|
4543
|
+
var LocalAudioPipeline = class extends Destroyable {
|
|
4544
|
+
constructor(options = {}) {
|
|
4545
|
+
super();
|
|
4546
|
+
this._inputSource = null;
|
|
4547
|
+
this._inputStream = null;
|
|
4548
|
+
this._lastSpokeAt = 0;
|
|
4549
|
+
this._gain$ = this.createBehaviorSubject(1);
|
|
4550
|
+
this._pttMultiplier = 1;
|
|
4551
|
+
this._audioContext = (options.audioContextFactory ?? (() => new AudioContext()))();
|
|
4552
|
+
this._gainNode = this._audioContext.createGain();
|
|
4553
|
+
this._analyser = this._audioContext.createAnalyser();
|
|
4554
|
+
this._analyser.fftSize = 2048;
|
|
4555
|
+
this._analyser.smoothingTimeConstant = .3;
|
|
4556
|
+
this._analyserBuffer = new Uint8Array(new ArrayBuffer(this._analyser.fftSize));
|
|
4557
|
+
this._destination = this._audioContext.createMediaStreamDestination();
|
|
4558
|
+
this._gainNode.connect(this._analyser);
|
|
4559
|
+
this._analyser.connect(this._destination);
|
|
4560
|
+
this._speakingThreshold = options.speakingThreshold ?? VAD_THRESHOLD;
|
|
4561
|
+
this._speakingHoldMs = options.speakingHoldMs ?? VAD_HOLD_MS;
|
|
4562
|
+
this._pollIntervalMs = options.pollIntervalMs ?? AUDIO_LEVEL_POLL_INTERVAL_MS;
|
|
4563
|
+
const initial = options.initialGain ?? 1;
|
|
4564
|
+
this._gain$.next(initial);
|
|
4565
|
+
this.applyEffectiveGain();
|
|
4566
|
+
}
|
|
4567
|
+
/** Observable of the current gain value (0..2). */
|
|
4568
|
+
get gain$() {
|
|
4569
|
+
return this._gain$.asObservable();
|
|
4570
|
+
}
|
|
4571
|
+
/** Current gain value (0..2). */
|
|
4572
|
+
get gain() {
|
|
4573
|
+
return this._gain$.value;
|
|
4574
|
+
}
|
|
4575
|
+
/**
|
|
4576
|
+
* Processed output track to attach to the RTCRtpSender. Stable reference
|
|
4577
|
+
* across input changes, so `sender.replaceTrack(pipeline.outputTrack)` only
|
|
4578
|
+
* needs to be called once.
|
|
4579
|
+
*/
|
|
4580
|
+
get outputTrack() {
|
|
4581
|
+
const [track] = this._destination.stream.getAudioTracks();
|
|
4582
|
+
return track;
|
|
4583
|
+
}
|
|
4584
|
+
/**
|
|
4585
|
+
* Root-mean-square audio level of the input signal, 0..1. Emits on a fixed
|
|
4586
|
+
* interval (~30fps by default).
|
|
4587
|
+
*/
|
|
4588
|
+
get level$() {
|
|
4589
|
+
return this.deferEmission((0, rxjs.interval)(this._pollIntervalMs, rxjs.animationFrameScheduler).pipe((0, rxjs.map)(() => this.computeLevel())));
|
|
4590
|
+
}
|
|
4591
|
+
/**
|
|
4592
|
+
* Boolean VAD derived from {@link level$}. True while level ≥ threshold or
|
|
4593
|
+
* during the hold window after the last frame that crossed the threshold.
|
|
4594
|
+
*/
|
|
4595
|
+
get speaking$() {
|
|
4596
|
+
return this.deferEmission(this.level$.pipe((0, rxjs.map)((level) => this.evaluateSpeaking(level)), (0, rxjs.distinctUntilChanged)()));
|
|
4597
|
+
}
|
|
4598
|
+
/**
|
|
4599
|
+
* Set gain multiplier applied to the input signal. 0 = silence,
|
|
4600
|
+
* 1 = unity, 2 = 2x. Values are clamped to [0, 2]. The effective gain on
|
|
4601
|
+
* the graph also respects the current PTT state.
|
|
4602
|
+
*/
|
|
4603
|
+
setGain(value) {
|
|
4604
|
+
const clamped = Math.max(0, Math.min(2, value));
|
|
4605
|
+
this._gain$.next(clamped);
|
|
4606
|
+
this.applyEffectiveGain();
|
|
4607
|
+
}
|
|
4608
|
+
/**
|
|
4609
|
+
* Silence the graph when `active = false`, otherwise restore the configured
|
|
4610
|
+
* gain. Use this from a PTT handler: released → `false`, held → `true`.
|
|
4611
|
+
* Orthogonal to {@link setGain} — once PTT returns to active, the last
|
|
4612
|
+
* configured gain reappears.
|
|
4613
|
+
*/
|
|
4614
|
+
setPTTActive(active) {
|
|
4615
|
+
this._pttMultiplier = active ? 1 : 0;
|
|
4616
|
+
this.applyEffectiveGain();
|
|
4617
|
+
}
|
|
4618
|
+
applyEffectiveGain() {
|
|
4619
|
+
this._gainNode.gain.value = this._gain$.value * this._pttMultiplier;
|
|
4620
|
+
}
|
|
4621
|
+
/**
|
|
4622
|
+
* Wire a new raw mic track as the pipeline's input. Replaces any previous
|
|
4623
|
+
* input source and reconnects the graph so {@link outputTrack} continues
|
|
4624
|
+
* to emit the processed audio. Pass `null` to disconnect the input (the
|
|
4625
|
+
* output track stays alive but emits silence).
|
|
4626
|
+
*
|
|
4627
|
+
* Also resumes the underlying AudioContext on attach — Chrome creates it
|
|
4628
|
+
* in a suspended state and the graph won't process (the destination
|
|
4629
|
+
* track emits silence) until resume() succeeds.
|
|
4630
|
+
*/
|
|
4631
|
+
setInputTrack(track) {
|
|
4632
|
+
if (this._inputSource) {
|
|
4633
|
+
try {
|
|
4634
|
+
this._inputSource.disconnect();
|
|
4635
|
+
} catch (error) {
|
|
4636
|
+
logger$19.debug("[LocalAudioPipeline] input disconnect warning:", error);
|
|
4637
|
+
}
|
|
4638
|
+
this._inputSource = null;
|
|
4639
|
+
}
|
|
4640
|
+
if (this._inputStream) this._inputStream = null;
|
|
4641
|
+
if (!track) return;
|
|
4642
|
+
this._inputStream = new MediaStream([track]);
|
|
4643
|
+
this._inputSource = this._audioContext.createMediaStreamSource(this._inputStream);
|
|
4644
|
+
this._inputSource.connect(this._gainNode);
|
|
4645
|
+
if (this._audioContext.state === "suspended") this._audioContext.resume().catch((error) => {
|
|
4646
|
+
logger$19.warn("[LocalAudioPipeline] AudioContext resume failed:", error);
|
|
4647
|
+
});
|
|
4648
|
+
}
|
|
4649
|
+
destroy() {
|
|
4650
|
+
if (this._inputSource) {
|
|
4651
|
+
try {
|
|
4652
|
+
this._inputSource.disconnect();
|
|
4653
|
+
} catch {}
|
|
4654
|
+
this._inputSource = null;
|
|
4655
|
+
}
|
|
4656
|
+
try {
|
|
4657
|
+
this._gainNode.disconnect();
|
|
4658
|
+
this._analyser.disconnect();
|
|
4659
|
+
} catch {}
|
|
4660
|
+
this._audioContext.close().catch((error) => {
|
|
4661
|
+
logger$19.debug("[LocalAudioPipeline] audio context close warning:", error);
|
|
4662
|
+
});
|
|
4663
|
+
super.destroy();
|
|
4664
|
+
}
|
|
4665
|
+
computeLevel() {
|
|
4666
|
+
if (!this._inputSource) return 0;
|
|
4667
|
+
this._analyser.getByteTimeDomainData(this._analyserBuffer);
|
|
4668
|
+
let sum = 0;
|
|
4669
|
+
for (const sample of this._analyserBuffer) {
|
|
4670
|
+
const normalized = (sample - 128) / 128;
|
|
4671
|
+
sum += normalized * normalized;
|
|
4672
|
+
}
|
|
4673
|
+
return Math.sqrt(sum / this._analyserBuffer.length);
|
|
4674
|
+
}
|
|
4675
|
+
evaluateSpeaking(level) {
|
|
4676
|
+
const now = Date.now();
|
|
4677
|
+
if (level >= this._speakingThreshold) {
|
|
4678
|
+
this._lastSpokeAt = now;
|
|
4679
|
+
return true;
|
|
4680
|
+
}
|
|
4681
|
+
return now - this._lastSpokeAt < this._speakingHoldMs;
|
|
4682
|
+
}
|
|
4683
|
+
};
|
|
4684
|
+
|
|
4429
4685
|
//#endregion
|
|
4430
4686
|
//#region src/controllers/LocalStreamController.ts
|
|
4431
|
-
const logger$
|
|
4687
|
+
const logger$18 = require_operators.getLogger();
|
|
4432
4688
|
var LocalStreamController = class extends Destroyable {
|
|
4433
4689
|
constructor(options) {
|
|
4434
4690
|
super();
|
|
@@ -4466,26 +4722,26 @@ var LocalStreamController = class extends Destroyable {
|
|
|
4466
4722
|
* Build the local media stream based on the provided options.
|
|
4467
4723
|
*/
|
|
4468
4724
|
async buildLocalStream() {
|
|
4469
|
-
logger$
|
|
4725
|
+
logger$18.debug("[LocalStreamController] Building local media stream.");
|
|
4470
4726
|
let stream;
|
|
4471
4727
|
if (this.options.inputAudioStream ?? this.options.inputVideoStream) {
|
|
4472
4728
|
const tracks = [...this.options.inputAudioStream?.getTracks() ?? [], ...this.options.inputVideoStream?.getTracks() ?? []];
|
|
4473
4729
|
stream = new MediaStream(tracks);
|
|
4474
4730
|
} else if (this.options.propose === "screenshare") {
|
|
4475
|
-
logger$
|
|
4731
|
+
logger$18.debug("[LocalStreamController] Requesting display media for screen sharing with audio:", Boolean(this.options.inputAudioDeviceConstraints));
|
|
4476
4732
|
stream = await this.options.getDisplayMedia({
|
|
4477
4733
|
video: true,
|
|
4478
4734
|
audio: Boolean(this.options.inputAudioDeviceConstraints)
|
|
4479
4735
|
});
|
|
4480
|
-
logger$
|
|
4736
|
+
logger$18.debug("[LocalStreamController] Screen share media obtained:", stream);
|
|
4481
4737
|
} else {
|
|
4482
4738
|
const constraints = {
|
|
4483
4739
|
audio: this.options.inputAudioDeviceConstraints,
|
|
4484
4740
|
video: this.options.inputVideoDeviceConstraints
|
|
4485
4741
|
};
|
|
4486
|
-
logger$
|
|
4742
|
+
logger$18.debug("[LocalStreamController] Requesting user media with constraints:", constraints);
|
|
4487
4743
|
stream = await this.options.getUserMedia(constraints);
|
|
4488
|
-
logger$
|
|
4744
|
+
logger$18.debug("[LocalStreamController] User media obtained:", stream);
|
|
4489
4745
|
}
|
|
4490
4746
|
this._localStream$.next(stream);
|
|
4491
4747
|
return stream;
|
|
@@ -4502,7 +4758,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
4502
4758
|
this._localStream$.next(localStream);
|
|
4503
4759
|
if (track.kind === "video") this._localVideoTracks$.next(localStream.getVideoTracks());
|
|
4504
4760
|
else this._localAudioTracks$.next(localStream.getAudioTracks());
|
|
4505
|
-
logger$
|
|
4761
|
+
logger$18.debug(`[LocalStreamController] ${track.kind} track added:`, track.id);
|
|
4506
4762
|
return localStream;
|
|
4507
4763
|
}
|
|
4508
4764
|
/**
|
|
@@ -4514,7 +4770,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
4514
4770
|
const stream = this._localStream$.value;
|
|
4515
4771
|
const track = stream?.getTracks().find((t) => t.id === trackId);
|
|
4516
4772
|
if (!track) {
|
|
4517
|
-
logger$
|
|
4773
|
+
logger$18.debug(`[LocalStreamController] track not found: ${trackId}`);
|
|
4518
4774
|
return;
|
|
4519
4775
|
}
|
|
4520
4776
|
track.removeEventListener("ended", this.mediaTrackEndedHandler);
|
|
@@ -4523,7 +4779,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
4523
4779
|
this._localStream$.next(stream);
|
|
4524
4780
|
if (track.kind === "video") this._localVideoTracks$.next(stream?.getVideoTracks() ?? []);
|
|
4525
4781
|
else this._localAudioTracks$.next(stream?.getAudioTracks() ?? []);
|
|
4526
|
-
logger$
|
|
4782
|
+
logger$18.debug(`[LocalStreamController] ${track.kind} track removed:`, trackId);
|
|
4527
4783
|
return track;
|
|
4528
4784
|
}
|
|
4529
4785
|
/**
|
|
@@ -4558,7 +4814,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
4558
4814
|
*/
|
|
4559
4815
|
stopAllTracks() {
|
|
4560
4816
|
this._localStream$.value?.getTracks().forEach((track) => {
|
|
4561
|
-
logger$
|
|
4817
|
+
logger$18.debug(`[LocalStreamController] Stopping local track: ${track.kind}`);
|
|
4562
4818
|
track.removeEventListener("ended", this.mediaTrackEndedHandler);
|
|
4563
4819
|
track.stop();
|
|
4564
4820
|
});
|
|
@@ -4574,7 +4830,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
4574
4830
|
|
|
4575
4831
|
//#endregion
|
|
4576
4832
|
//#region src/controllers/TransceiverController.ts
|
|
4577
|
-
const logger$
|
|
4833
|
+
const logger$17 = require_operators.getLogger();
|
|
4578
4834
|
const getDirection = (send, recv) => {
|
|
4579
4835
|
if (send && recv) return "sendrecv";
|
|
4580
4836
|
else if (send && !recv) return "sendonly";
|
|
@@ -4676,7 +4932,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
4676
4932
|
sendEncodings: isAudio ? void 0 : this.sendEncodings,
|
|
4677
4933
|
streams: direction === "recvonly" ? void 0 : [localStream]
|
|
4678
4934
|
};
|
|
4679
|
-
logger$
|
|
4935
|
+
logger$17.debug(`[TransceiverController] Setting up transceiver sender for local ${track.kind} track:`, {
|
|
4680
4936
|
transceiver,
|
|
4681
4937
|
transceiverParams
|
|
4682
4938
|
});
|
|
@@ -4684,11 +4940,11 @@ var TransceiverController = class extends Destroyable {
|
|
|
4684
4940
|
await transceiver.sender.replaceTrack(track);
|
|
4685
4941
|
transceiver.direction = transceiverParams.direction;
|
|
4686
4942
|
if (transceiverParams.streams?.some((stream) => Boolean(stream))) {
|
|
4687
|
-
logger$
|
|
4943
|
+
logger$17.debug(`[TransceiverController] Setting streams for transceiver sender for local ${track.kind} track:`, transceiverParams.streams);
|
|
4688
4944
|
transceiver.sender.setStreams(...transceiverParams.streams);
|
|
4689
4945
|
}
|
|
4690
4946
|
} else {
|
|
4691
|
-
logger$
|
|
4947
|
+
logger$17.debug(`[TransceiverController] Adding new transceiver for local ${track.kind} track:`, track.id);
|
|
4692
4948
|
this.peerConnection.addTransceiver(track, transceiverParams);
|
|
4693
4949
|
}
|
|
4694
4950
|
}
|
|
@@ -4702,13 +4958,13 @@ var TransceiverController = class extends Destroyable {
|
|
|
4702
4958
|
if (options.updateTransceiverDirection) transceiver.direction = "inactive";
|
|
4703
4959
|
}
|
|
4704
4960
|
} catch (error) {
|
|
4705
|
-
logger$
|
|
4961
|
+
logger$17.error("[TransceiverController] stopTrackSender error", kind, error);
|
|
4706
4962
|
this.options.onError?.(new require_operators.MediaTrackError("stopTrackSender", kind, error));
|
|
4707
4963
|
}
|
|
4708
4964
|
}
|
|
4709
4965
|
async restoreTrackSender(kind) {
|
|
4710
4966
|
try {
|
|
4711
|
-
logger$
|
|
4967
|
+
logger$17.debug("[TransceiverController] restoreTrackSender called", kind);
|
|
4712
4968
|
const constraints = {};
|
|
4713
4969
|
const transceivers = this.transceiverByKind(kind);
|
|
4714
4970
|
for (const transceiver of transceivers) {
|
|
@@ -4718,23 +4974,23 @@ var TransceiverController = class extends Destroyable {
|
|
|
4718
4974
|
if (trackKind === "audio" || trackKind === "video") constraints[trackKind] = this.getConstraintsFor(trackKind);
|
|
4719
4975
|
}
|
|
4720
4976
|
}
|
|
4721
|
-
logger$
|
|
4977
|
+
logger$17.debug("[TransceiverController] restoreTrackSender constraints:", constraints);
|
|
4722
4978
|
if (Object.keys(constraints).length === 0) {
|
|
4723
|
-
logger$
|
|
4979
|
+
logger$17.warn("[TransceiverController] restoreTrackSender: no tracks need restoration", kind);
|
|
4724
4980
|
return;
|
|
4725
4981
|
}
|
|
4726
4982
|
const newTracks = (await this.options.getUserMedia(constraints)).getTracks();
|
|
4727
|
-
logger$
|
|
4983
|
+
logger$17.debug("[TransceiverController] restoreTrackSender new tracks:", newTracks);
|
|
4728
4984
|
for (const newTrack of newTracks) {
|
|
4729
4985
|
this.options.localStreamController.addTrack(newTrack);
|
|
4730
4986
|
const trackKind = newTrack.kind;
|
|
4731
4987
|
const transceiverOfKind = this.transceiverByKind(trackKind)[0];
|
|
4732
4988
|
transceiverOfKind.direction = trackKind === "audio" ? this.audioDirection : this.videoDirection;
|
|
4733
|
-
logger$
|
|
4989
|
+
logger$17.debug("[TransceiverController] restoreTrackSender setting direction for", trackKind, transceiverOfKind.direction);
|
|
4734
4990
|
await transceiverOfKind.sender.replaceTrack(newTrack);
|
|
4735
4991
|
}
|
|
4736
4992
|
} catch (error) {
|
|
4737
|
-
logger$
|
|
4993
|
+
logger$17.error("[TransceiverController] restoreTrackSender error", kind, error);
|
|
4738
4994
|
this.options.onError?.(new require_operators.MediaTrackError("restoreTrackSender", kind, error));
|
|
4739
4995
|
}
|
|
4740
4996
|
}
|
|
@@ -4775,14 +5031,14 @@ var TransceiverController = class extends Destroyable {
|
|
|
4775
5031
|
};
|
|
4776
5032
|
try {
|
|
4777
5033
|
await track.applyConstraints(constraintsToApply);
|
|
4778
|
-
logger$
|
|
4779
|
-
logger$
|
|
5034
|
+
logger$17.debug(`[TransceiverController] Updated ${kind} sender constraints:`, constraintsToApply);
|
|
5035
|
+
logger$17.debug(`[TransceiverController] Updated ${kind} sender constraints:`, track.getConstraints());
|
|
4780
5036
|
} catch (error) {
|
|
4781
|
-
logger$
|
|
5037
|
+
logger$17.warn(`[TransceiverController] applyConstraints failed for ${kind} track ${track.id}, attempting track replacement fallback:`, error);
|
|
4782
5038
|
try {
|
|
4783
5039
|
await this.replaceTrackFallback(sender, track, kind, constraintsToApply);
|
|
4784
5040
|
} catch (fallbackError) {
|
|
4785
|
-
logger$
|
|
5041
|
+
logger$17.warn(`[TransceiverController] Track replacement fallback also failed for ${kind} track:`, fallbackError);
|
|
4786
5042
|
this.options.onError?.(new require_operators.MediaTrackError("updateSendersConstraints", kind, fallbackError));
|
|
4787
5043
|
}
|
|
4788
5044
|
}
|
|
@@ -4810,7 +5066,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
4810
5066
|
if (!newTrack) throw new require_operators.MediaTrackError("replaceTrackFallback", kind, /* @__PURE__ */ new Error("getUserMedia returned no track of the requested kind"));
|
|
4811
5067
|
await sender.replaceTrack(newTrack);
|
|
4812
5068
|
this.options.localStreamController.addTrack(newTrack);
|
|
4813
|
-
logger$
|
|
5069
|
+
logger$17.debug(`[TransceiverController] Track replacement fallback succeeded for ${kind}. New track: ${newTrack.id}`);
|
|
4814
5070
|
}
|
|
4815
5071
|
getMediaDirections() {
|
|
4816
5072
|
if (this.peerConnection.connectionState === "connected") return this.peerConnection.getTransceivers().reduce((acc, transceiver) => {
|
|
@@ -4840,7 +5096,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
4840
5096
|
|
|
4841
5097
|
//#endregion
|
|
4842
5098
|
//#region src/controllers/RTCPeerConnectionController.ts
|
|
4843
|
-
const logger$
|
|
5099
|
+
const logger$16 = require_operators.getLogger();
|
|
4844
5100
|
var RTCPeerConnectionController = class extends Destroyable {
|
|
4845
5101
|
constructor(options = {}, remoteSessionDescription, deviceController) {
|
|
4846
5102
|
super();
|
|
@@ -4856,43 +5112,43 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
4856
5112
|
this.oniceconnectionstatechangeHandler = () => {
|
|
4857
5113
|
if (this.peerConnection) {
|
|
4858
5114
|
const { iceConnectionState } = this.peerConnection;
|
|
4859
|
-
logger$
|
|
5115
|
+
logger$16.debug(`[RTCPeerConnectionController] ICE connection state changed to: ${iceConnectionState}`);
|
|
4860
5116
|
this._iceConnectionState$.next(this.peerConnection.iceConnectionState);
|
|
4861
5117
|
}
|
|
4862
5118
|
};
|
|
4863
5119
|
this.onconnectionstatechangeHandler = () => {
|
|
4864
5120
|
if (this.peerConnection) {
|
|
4865
5121
|
const { connectionState } = this.peerConnection;
|
|
4866
|
-
logger$
|
|
5122
|
+
logger$16.debug(`[RTCPeerConnectionController] Connection state changed to: ${connectionState}`);
|
|
4867
5123
|
if (connectionState === "connected") this.removeConnectionTimer();
|
|
4868
5124
|
this._connectionState$.next(this.peerConnection.connectionState);
|
|
4869
5125
|
}
|
|
4870
5126
|
};
|
|
4871
5127
|
this.onsignalingstatechangeHandler = () => {
|
|
4872
|
-
logger$
|
|
5128
|
+
logger$16.debug(`[RTCPeerConnectionController] Signaling state changed to: ${this.peerConnection?.signalingState}`);
|
|
4873
5129
|
};
|
|
4874
5130
|
this.onicegatheringstatechangeHandler = () => {
|
|
4875
5131
|
if (this.peerConnection) this._iceGatheringState$.next(this.peerConnection.iceGatheringState);
|
|
4876
5132
|
};
|
|
4877
5133
|
this.onnegotiationneededHandler = (event) => {
|
|
4878
|
-
logger$
|
|
5134
|
+
logger$16.debug("[RTCPeerConnectionController] Negotiation needed event received.", event);
|
|
4879
5135
|
this.negotiationNeeded$.next();
|
|
4880
5136
|
};
|
|
4881
5137
|
this.updateSelectedInputDevice = async (kind, deviceInfo) => {
|
|
4882
5138
|
try {
|
|
4883
5139
|
const { localStream } = this;
|
|
4884
5140
|
if (!localStream) {
|
|
4885
|
-
logger$
|
|
5141
|
+
logger$16.warn("[RTCPeerConnectionController] No local stream available to update input device.");
|
|
4886
5142
|
return;
|
|
4887
5143
|
}
|
|
4888
|
-
logger$
|
|
5144
|
+
logger$16.debug(`[RTCPeerConnectionController] Updating selected ${kind} input device:`, localStream.getTracks());
|
|
4889
5145
|
const track = localStream.getTracks().find((track$1) => track$1.kind === kind);
|
|
4890
5146
|
if (track) {
|
|
4891
5147
|
this.transceiverController?.stopTrackSender(kind);
|
|
4892
|
-
this.
|
|
4893
|
-
logger$
|
|
5148
|
+
this.localStreamController.removeTrack(track.id);
|
|
5149
|
+
logger$16.debug(`[RTCPeerConnectionController] Stopped existing ${kind} track: ${track.id}`, localStream.getTracks());
|
|
4894
5150
|
if (!deviceInfo) {
|
|
4895
|
-
logger$
|
|
5151
|
+
logger$16.debug(`[RTCPeerConnectionController] ${kind} input device selected: none`);
|
|
4896
5152
|
return;
|
|
4897
5153
|
}
|
|
4898
5154
|
const streamTrack = (await this.getUserMedia({ [kind]: {
|
|
@@ -4900,15 +5156,15 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
4900
5156
|
...this.deviceController.deviceInfoToConstraints(deviceInfo)
|
|
4901
5157
|
} })).getTracks().find((t) => t.kind === kind);
|
|
4902
5158
|
if (streamTrack) {
|
|
4903
|
-
logger$
|
|
4904
|
-
this.
|
|
5159
|
+
logger$16.debug(`[RTCPeerConnectionController] Adding new ${kind} track: ${streamTrack.id}`);
|
|
5160
|
+
this.localStreamController.addTrack(streamTrack);
|
|
4905
5161
|
await this.transceiverController?.replaceSenderTrack(kind, streamTrack);
|
|
4906
|
-
logger$
|
|
5162
|
+
logger$16.debug(`[RTCPeerConnectionController] Added new ${kind} track: ${streamTrack.id}`, this.localStream?.getTracks());
|
|
4907
5163
|
}
|
|
4908
5164
|
}
|
|
4909
|
-
logger$
|
|
5165
|
+
logger$16.debug(`[RTCPeerConnectionController] ${kind} input device selected:`, deviceInfo?.label);
|
|
4910
5166
|
} catch (error) {
|
|
4911
|
-
logger$
|
|
5167
|
+
logger$16.error(`[RTCPeerConnectionController] Failed to select ${kind} input device:`, error);
|
|
4912
5168
|
this._errors$.next(toError(error));
|
|
4913
5169
|
throw error;
|
|
4914
5170
|
}
|
|
@@ -4925,6 +5181,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
4925
5181
|
this._remoteDescription$ = this.createReplaySubject(1);
|
|
4926
5182
|
this._remoteStream$ = this.createBehaviorSubject(null);
|
|
4927
5183
|
this._remoteOfferMediaDirections = null;
|
|
5184
|
+
this._localAudioPipeline = null;
|
|
4928
5185
|
this.deviceController = deviceController ?? {};
|
|
4929
5186
|
this.id = options.callId ?? (0, uuid.v4)();
|
|
4930
5187
|
this._type = remoteSessionDescription ? "answer" : "offer";
|
|
@@ -5145,15 +5402,15 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5145
5402
|
this.setupPeerConnection();
|
|
5146
5403
|
this.subscribeTo(this.negotiationNeeded$.pipe((0, rxjs.auditTime)(0), (0, rxjs.exhaustMap)(async () => this.startNegotiation())), {
|
|
5147
5404
|
next: () => {
|
|
5148
|
-
logger$
|
|
5405
|
+
logger$16.debug("[RTCPeerConnectionController] Start Negotiation completed successfully");
|
|
5149
5406
|
},
|
|
5150
5407
|
error: (error) => {
|
|
5151
|
-
logger$
|
|
5408
|
+
logger$16.error("[RTCPeerConnectionController] Start Negotiation error:", error);
|
|
5152
5409
|
this._errors$.next(toError(error));
|
|
5153
5410
|
}
|
|
5154
5411
|
});
|
|
5155
5412
|
this.subscribeTo((0, rxjs.merge)(this.deviceController.selectedAudioInputDevice$.pipe((0, rxjs.map)((deviceInfo) => ["audio", deviceInfo])), this.deviceController.selectedVideoInputDevice$.pipe((0, rxjs.map)((deviceInfo) => ["video", deviceInfo]))).pipe((0, rxjs.skipWhile)(() => !this.localStreamController.localStream)), async ([kind, deviceInfo]) => {
|
|
5156
|
-
logger$
|
|
5413
|
+
logger$16.debug(`[RTCPeerConnectionController] Selected input device changed for:`, {
|
|
5157
5414
|
kind,
|
|
5158
5415
|
deviceInfo
|
|
5159
5416
|
});
|
|
@@ -5170,7 +5427,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5170
5427
|
this._initialized$.next(true);
|
|
5171
5428
|
}
|
|
5172
5429
|
} catch (error) {
|
|
5173
|
-
logger$
|
|
5430
|
+
logger$16.error("[RTCPeerConnectionController] Initialization error:", error);
|
|
5174
5431
|
this._errors$.next(toError(error));
|
|
5175
5432
|
this.destroy();
|
|
5176
5433
|
}
|
|
@@ -5202,22 +5459,22 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5202
5459
|
}
|
|
5203
5460
|
async startNegotiation() {
|
|
5204
5461
|
if (this.isNegotiating) {
|
|
5205
|
-
logger$
|
|
5462
|
+
logger$16.debug("[RTCPeerConnectionController] Negotiation already in progress, skipping.");
|
|
5206
5463
|
return;
|
|
5207
5464
|
}
|
|
5208
5465
|
this.setupEventListeners();
|
|
5209
5466
|
if (this.type === "answer") {
|
|
5210
|
-
logger$
|
|
5467
|
+
logger$16.debug("[RTCPeerConnectionController] This is an answer type still, skipping offer creation.");
|
|
5211
5468
|
return;
|
|
5212
5469
|
}
|
|
5213
5470
|
this._isNegotiating$.next(true);
|
|
5214
|
-
logger$
|
|
5471
|
+
logger$16.debug("[RTCPeerConnectionController] Starting negotiation.");
|
|
5215
5472
|
try {
|
|
5216
5473
|
const { offerOptions } = this;
|
|
5217
|
-
logger$
|
|
5474
|
+
logger$16.debug("[RTCPeerConnectionController] Creating offer with options:", offerOptions);
|
|
5218
5475
|
await this.createOffer(offerOptions);
|
|
5219
5476
|
} catch (error) {
|
|
5220
|
-
logger$
|
|
5477
|
+
logger$16.error("[RTCPeerConnectionController] Error during negotiation:", error);
|
|
5221
5478
|
this._errors$.next(toError(error));
|
|
5222
5479
|
}
|
|
5223
5480
|
}
|
|
@@ -5233,14 +5490,14 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5233
5490
|
let readyToConnect = status !== "failed";
|
|
5234
5491
|
try {
|
|
5235
5492
|
if (status === "received" && sdp) {
|
|
5236
|
-
logger$
|
|
5493
|
+
logger$16.debug("[RTCPeerConnectionController] Received answer SDP:", sdp);
|
|
5237
5494
|
await this._setRemoteDescription({
|
|
5238
5495
|
type: "answer",
|
|
5239
5496
|
sdp
|
|
5240
5497
|
});
|
|
5241
5498
|
}
|
|
5242
5499
|
} catch (error) {
|
|
5243
|
-
logger$
|
|
5500
|
+
logger$16.error("[RTCPeerConnectionController] Error updating answer status:", error);
|
|
5244
5501
|
this._errors$.next(toError(error));
|
|
5245
5502
|
readyToConnect = false;
|
|
5246
5503
|
} finally {
|
|
@@ -5259,7 +5516,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5259
5516
|
await this.handleOfferReceived();
|
|
5260
5517
|
break;
|
|
5261
5518
|
case "failed":
|
|
5262
|
-
logger$
|
|
5519
|
+
logger$16.error("[RTCPeerConnectionController] Offer failed to be processed by remote.");
|
|
5263
5520
|
break;
|
|
5264
5521
|
case "sent":
|
|
5265
5522
|
default:
|
|
@@ -5291,7 +5548,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5291
5548
|
}
|
|
5292
5549
|
await this.setupLocalTracks();
|
|
5293
5550
|
const { answerOptions } = this;
|
|
5294
|
-
logger$
|
|
5551
|
+
logger$16.debug("[RTCPeerConnectionController] Creating inbound answer with options:", answerOptions);
|
|
5295
5552
|
await this.createAnswer(answerOptions);
|
|
5296
5553
|
}
|
|
5297
5554
|
async handleOfferReceived() {
|
|
@@ -5299,7 +5556,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5299
5556
|
this._isNegotiating$.next(true);
|
|
5300
5557
|
await this._setRemoteDescription(this.sdpInit);
|
|
5301
5558
|
const { answerOptions } = this;
|
|
5302
|
-
logger$
|
|
5559
|
+
logger$16.debug("[RTCPeerConnectionController] Creating answer with options:", answerOptions);
|
|
5303
5560
|
await this.createAnswer(answerOptions);
|
|
5304
5561
|
}
|
|
5305
5562
|
readyToConnect() {
|
|
@@ -5307,7 +5564,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5307
5564
|
this.connectionTimer = setTimeout(() => {
|
|
5308
5565
|
this.removeConnectionTimer();
|
|
5309
5566
|
if (this.peerConnection?.connectionState !== "connected") {
|
|
5310
|
-
logger$
|
|
5567
|
+
logger$16.debug("[RTCPeerConnectionController] Connection timeout, restarting ICE gathering with relay only.");
|
|
5311
5568
|
this.iceGatheringController.restartICEGatheringWithRelayOnly();
|
|
5312
5569
|
}
|
|
5313
5570
|
}, this.connectionTimeout);
|
|
@@ -5329,14 +5586,14 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5329
5586
|
const stereo = this.options.stereo ?? PreferencesContainer.instance.stereoAudio;
|
|
5330
5587
|
if (preferredAudioCodecs.length > 0 || preferredVideoCodecs.length > 0) {
|
|
5331
5588
|
result = setCodecPreferences(result, preferredAudioCodecs, preferredVideoCodecs);
|
|
5332
|
-
logger$
|
|
5589
|
+
logger$16.debug("[RTCPeerConnectionController] Applied codec preferences to SDP", {
|
|
5333
5590
|
preferredAudioCodecs,
|
|
5334
5591
|
preferredVideoCodecs
|
|
5335
5592
|
});
|
|
5336
5593
|
}
|
|
5337
5594
|
if (stereo) {
|
|
5338
5595
|
result = enableStereoOpus(result);
|
|
5339
|
-
logger$
|
|
5596
|
+
logger$16.debug("[RTCPeerConnectionController] Applied stereo Opus to SDP");
|
|
5340
5597
|
}
|
|
5341
5598
|
return Promise.resolve(result);
|
|
5342
5599
|
}
|
|
@@ -5370,9 +5627,6 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5370
5627
|
negotiationEnded() {
|
|
5371
5628
|
this._isNegotiating$.next(false);
|
|
5372
5629
|
}
|
|
5373
|
-
restarIce() {
|
|
5374
|
-
this.peerConnection?.restartIce();
|
|
5375
|
-
}
|
|
5376
5630
|
/**
|
|
5377
5631
|
* Trigger an ICE restart through the existing negotiation pipeline.
|
|
5378
5632
|
*
|
|
@@ -5395,24 +5649,27 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5395
5649
|
...this.peerConnection.getConfiguration(),
|
|
5396
5650
|
iceTransportPolicy: "relay"
|
|
5397
5651
|
});
|
|
5398
|
-
logger$
|
|
5652
|
+
logger$16.debug("[RTCPeerConnectionController] ICE transport policy set to relay-only");
|
|
5399
5653
|
} catch (error) {
|
|
5400
|
-
logger$
|
|
5654
|
+
logger$16.warn("[RTCPeerConnectionController] Failed to set relay-only policy:", error);
|
|
5401
5655
|
}
|
|
5402
5656
|
this.setupEventListeners();
|
|
5403
5657
|
this._isNegotiating$.next(true);
|
|
5404
|
-
logger$
|
|
5658
|
+
logger$16.debug(`[RTCPeerConnectionController] Triggering ICE restart${relayOnly ? " (relay-only)" : ""}.`);
|
|
5405
5659
|
try {
|
|
5406
5660
|
const offer = await this.peerConnection.createOffer({ iceRestart: true });
|
|
5407
5661
|
await this.setLocalDescription(offer);
|
|
5408
5662
|
} catch (error) {
|
|
5409
|
-
logger$
|
|
5663
|
+
logger$16.error("[RTCPeerConnectionController] ICE restart offer failed:", error);
|
|
5410
5664
|
this._errors$.next(toError(error));
|
|
5411
5665
|
this.negotiationEnded();
|
|
5412
5666
|
if (policyChanged) this.restoreIceTransportPolicy();
|
|
5413
5667
|
throw error;
|
|
5414
5668
|
}
|
|
5415
|
-
if (policyChanged) this.restoreIceTransportPolicy()
|
|
5669
|
+
if (policyChanged) (0, rxjs.firstValueFrom)((0, rxjs.race)(this._iceGatheringState$.pipe((0, rxjs.filter)((state) => state === "complete"), (0, rxjs.take)(1)), (0, rxjs.timer)(ICE_GATHERING_COMPLETE_TIMEOUT_MS).pipe((0, rxjs.map)(() => "timeout")))).then(() => this.restoreIceTransportPolicy()).catch((error) => {
|
|
5670
|
+
logger$16.warn("[RTCPeerConnectionController] Error waiting for ICE gathering to complete:", error);
|
|
5671
|
+
this.restoreIceTransportPolicy();
|
|
5672
|
+
});
|
|
5416
5673
|
}
|
|
5417
5674
|
restoreIceTransportPolicy() {
|
|
5418
5675
|
try {
|
|
@@ -5420,9 +5677,9 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5420
5677
|
...this.peerConnection.getConfiguration(),
|
|
5421
5678
|
iceTransportPolicy: this.options.relayOnly ? "relay" : "all"
|
|
5422
5679
|
});
|
|
5423
|
-
logger$
|
|
5680
|
+
logger$16.debug("[RTCPeerConnectionController] ICE transport policy restored");
|
|
5424
5681
|
} catch (error) {
|
|
5425
|
-
logger$
|
|
5682
|
+
logger$16.warn("[RTCPeerConnectionController] Failed to restore ICE transport policy:", error);
|
|
5426
5683
|
}
|
|
5427
5684
|
}
|
|
5428
5685
|
/**
|
|
@@ -5434,13 +5691,13 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5434
5691
|
await this.setupRemoteTracks();
|
|
5435
5692
|
}
|
|
5436
5693
|
async setupLocalTracks() {
|
|
5437
|
-
logger$
|
|
5694
|
+
logger$16.debug("[RTCPeerConnectionController] Setting up local tracks/transceivers.");
|
|
5438
5695
|
const localStream = this.localStream ?? await this.localStreamController.buildLocalStream();
|
|
5439
5696
|
if (this.transceiverController?.useAddStream ?? false) {
|
|
5440
|
-
logger$
|
|
5697
|
+
logger$16.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
|
|
5441
5698
|
this.peerConnection?.addStream(localStream);
|
|
5442
5699
|
if (!this.isNegotiating) {
|
|
5443
|
-
logger$
|
|
5700
|
+
logger$16.debug("[RTCPeerConnectionController] Forcing negotiationneeded after local tracks setup.");
|
|
5444
5701
|
this.negotiationNeeded$.next();
|
|
5445
5702
|
}
|
|
5446
5703
|
return;
|
|
@@ -5456,7 +5713,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5456
5713
|
const transceivers = (kind === "audio" ? this.transceiverController?.audioTransceivers : this.transceiverController?.videoTransceivers) ?? [];
|
|
5457
5714
|
await this.transceiverController?.setupTransceiverSender(track, localStream, transceivers[index]);
|
|
5458
5715
|
} else {
|
|
5459
|
-
logger$
|
|
5716
|
+
logger$16.debug(`[RTCPeerConnectionController] Using addTrack for local ${kind} track:`, track.id);
|
|
5460
5717
|
this.peerConnection?.addTrack(track, localStream);
|
|
5461
5718
|
}
|
|
5462
5719
|
}
|
|
@@ -5473,7 +5730,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5473
5730
|
async setupRemoteTracks() {
|
|
5474
5731
|
if (!this.peerConnection) throw new require_operators.DependencyError("RTCPeerConnection is not initialized");
|
|
5475
5732
|
this.peerConnection.ontrack = (event) => {
|
|
5476
|
-
logger$
|
|
5733
|
+
logger$16.debug("[RTCPeerConnectionController] Remote track received:", event.track.kind);
|
|
5477
5734
|
if (event.streams[0]) this._remoteStream$.next(event.streams[0]);
|
|
5478
5735
|
else {
|
|
5479
5736
|
const existingTracks = this._remoteStream$.value?.getTracks() ?? [];
|
|
@@ -5485,6 +5742,45 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5485
5742
|
}
|
|
5486
5743
|
async restoreTrackSender(kind) {
|
|
5487
5744
|
await this.transceiverController?.restoreTrackSender(kind);
|
|
5745
|
+
if (kind !== "video" && this._localAudioPipeline) await this.applyLocalAudioPipelineToSender();
|
|
5746
|
+
}
|
|
5747
|
+
/**
|
|
5748
|
+
* Return the lazily-created {@link LocalAudioPipeline}, constructing it on
|
|
5749
|
+
* first access. On creation the current audio sender's track is routed
|
|
5750
|
+
* through the pipeline (input → gain → analyser → destination) and the
|
|
5751
|
+
* sender is switched to emit the processed track. Returns `null` when no
|
|
5752
|
+
* audio sender exists yet (pre-negotiation).
|
|
5753
|
+
*/
|
|
5754
|
+
ensureLocalAudioPipeline() {
|
|
5755
|
+
if (this._localAudioPipeline) return this._localAudioPipeline;
|
|
5756
|
+
if (!this.peerConnection) return null;
|
|
5757
|
+
try {
|
|
5758
|
+
this._localAudioPipeline = new LocalAudioPipeline();
|
|
5759
|
+
} catch (error) {
|
|
5760
|
+
logger$16.warn("[RTCPeerConnectionController] Failed to create LocalAudioPipeline:", error);
|
|
5761
|
+
return null;
|
|
5762
|
+
}
|
|
5763
|
+
this.subscribeTo(this.localStreamController.localAudioTracks$, () => {
|
|
5764
|
+
this.applyLocalAudioPipelineToSender();
|
|
5765
|
+
});
|
|
5766
|
+
this.applyLocalAudioPipelineToSender();
|
|
5767
|
+
return this._localAudioPipeline;
|
|
5768
|
+
}
|
|
5769
|
+
/** The active LocalAudioPipeline, or null if it hasn't been created yet. */
|
|
5770
|
+
get localAudioPipeline() {
|
|
5771
|
+
return this._localAudioPipeline;
|
|
5772
|
+
}
|
|
5773
|
+
async applyLocalAudioPipelineToSender() {
|
|
5774
|
+
if (!this._localAudioPipeline || !this.peerConnection) return;
|
|
5775
|
+
const raw = this.localStreamController.localAudioTracks.at(0);
|
|
5776
|
+
this._localAudioPipeline.setInputTrack(raw ?? null);
|
|
5777
|
+
const sender = (this.transceiverController?.audioTransceivers.at(0))?.sender ?? this.peerConnection.getSenders().find((s) => s.track?.kind === "audio");
|
|
5778
|
+
if (!sender || !raw) return;
|
|
5779
|
+
try {
|
|
5780
|
+
await sender.replaceTrack(this._localAudioPipeline.outputTrack);
|
|
5781
|
+
} catch (error) {
|
|
5782
|
+
logger$16.warn("[RTCPeerConnectionController] Failed to route audio sender through pipeline:", error);
|
|
5783
|
+
}
|
|
5488
5784
|
}
|
|
5489
5785
|
/**
|
|
5490
5786
|
* Add a local media track to the peer connection.
|
|
@@ -5499,9 +5795,9 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5499
5795
|
try {
|
|
5500
5796
|
const localStream = this.localStreamController.addTrack(track);
|
|
5501
5797
|
this.peerConnection.addTrack(track, localStream);
|
|
5502
|
-
logger$
|
|
5798
|
+
logger$16.debug(`[RTCPeerConnectionController] ${track.kind} track added:`, track.id);
|
|
5503
5799
|
} catch (error) {
|
|
5504
|
-
logger$
|
|
5800
|
+
logger$16.error(`[RTCPeerConnectionController] Failed to add ${track.kind} track:`, error);
|
|
5505
5801
|
this._errors$.next(toError(error));
|
|
5506
5802
|
throw error;
|
|
5507
5803
|
}
|
|
@@ -5518,15 +5814,15 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5518
5814
|
}
|
|
5519
5815
|
const sender = this.peerConnection.getSenders().find((sender$1) => sender$1.track?.id === trackId);
|
|
5520
5816
|
if (!sender) {
|
|
5521
|
-
logger$
|
|
5817
|
+
logger$16.debug(`[RTCPeerConnectionController] track not found: ${trackId}`);
|
|
5522
5818
|
return;
|
|
5523
5819
|
}
|
|
5524
5820
|
try {
|
|
5525
5821
|
this.peerConnection.removeTrack(sender);
|
|
5526
5822
|
this.localStreamController.removeTrack(trackId);
|
|
5527
|
-
logger$
|
|
5823
|
+
logger$16.debug(`[RTCPeerConnectionController] ${sender.track?.kind} track removed:`, trackId);
|
|
5528
5824
|
} catch (error) {
|
|
5529
|
-
logger$
|
|
5825
|
+
logger$16.error(`[RTCPeerConnectionController] Failed to remove ${sender.track?.kind} track:`, error);
|
|
5530
5826
|
this._errors$.next(toError(error));
|
|
5531
5827
|
throw error;
|
|
5532
5828
|
}
|
|
@@ -5553,7 +5849,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5553
5849
|
async replaceAudioTrackWithConstraints(constraints) {
|
|
5554
5850
|
const senders = this.peerConnection?.getSenders().filter((s) => s.track?.kind === "audio" && s.track.readyState === "live");
|
|
5555
5851
|
if (!senders || senders.length === 0) {
|
|
5556
|
-
logger$
|
|
5852
|
+
logger$16.warn("[RTCPeerConnectionController] No live audio sender to replace");
|
|
5557
5853
|
return;
|
|
5558
5854
|
}
|
|
5559
5855
|
for (const sender of senders) {
|
|
@@ -5571,7 +5867,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5571
5867
|
const newTrack = (await this.getUserMedia({ audio: mergedConstraints })).getAudioTracks()[0];
|
|
5572
5868
|
await sender.replaceTrack(newTrack);
|
|
5573
5869
|
this.localStreamController.addTrack(newTrack);
|
|
5574
|
-
logger$
|
|
5870
|
+
logger$16.debug(`[RTCPeerConnectionController] Audio track replaced for server-pushed params. New track: ${newTrack.id}`);
|
|
5575
5871
|
}
|
|
5576
5872
|
}
|
|
5577
5873
|
/**
|
|
@@ -5579,9 +5875,11 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5579
5875
|
* Completes all observables to prevent memory leaks.
|
|
5580
5876
|
*/
|
|
5581
5877
|
destroy() {
|
|
5582
|
-
logger$
|
|
5878
|
+
logger$16.debug(`[RTCPeerConnectionController] Destroying RTCPeerConnectionController. ${this.propose}`);
|
|
5583
5879
|
this.removeConnectionTimer();
|
|
5584
5880
|
this._iceGatheringController?.destroy();
|
|
5881
|
+
this._localAudioPipeline?.destroy();
|
|
5882
|
+
this._localAudioPipeline = null;
|
|
5585
5883
|
this.localStreamController.destroy();
|
|
5586
5884
|
this.transceiverController?.destroy();
|
|
5587
5885
|
if (this.peerConnection) {
|
|
@@ -5603,7 +5901,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5603
5901
|
}
|
|
5604
5902
|
stopRemoteTracks() {
|
|
5605
5903
|
this._remoteStream$.value?.getTracks().forEach((track) => {
|
|
5606
|
-
logger$
|
|
5904
|
+
logger$16.debug(`[RTCPeerConnectionController] Stopping remote track: ${track.kind}`);
|
|
5607
5905
|
track.stop();
|
|
5608
5906
|
});
|
|
5609
5907
|
}
|
|
@@ -5620,7 +5918,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5620
5918
|
...params,
|
|
5621
5919
|
sdp: finalRemote
|
|
5622
5920
|
};
|
|
5623
|
-
logger$
|
|
5921
|
+
logger$16.debug("[RTCPeerConnectionController] Setting remote description:", answer);
|
|
5624
5922
|
return this.peerConnection.setRemoteDescription(answer);
|
|
5625
5923
|
}
|
|
5626
5924
|
};
|
|
@@ -5658,7 +5956,24 @@ function isVertoPingInnerParams(value) {
|
|
|
5658
5956
|
|
|
5659
5957
|
//#endregion
|
|
5660
5958
|
//#region src/managers/VertoManager.ts
|
|
5661
|
-
const logger$
|
|
5959
|
+
const logger$15 = require_operators.getLogger();
|
|
5960
|
+
/**
|
|
5961
|
+
* Decide what value goes on the `node_id` field of a `webrtc.verto` envelope.
|
|
5962
|
+
*
|
|
5963
|
+
* - **Reattach invite:** must carry the persisted nodeId so the server routes
|
|
5964
|
+
* the new connection to the FreeSWITCH instance that holds the existing call.
|
|
5965
|
+
* - **Fresh invite, caller-supplied `CallOptions.nodeId`:** carry the explicit
|
|
5966
|
+
* value as a steering hint (dev/staging traffic pinning). Server may honour
|
|
5967
|
+
* or ignore for placement reasons.
|
|
5968
|
+
* - **Fresh invite, no caller nodeId:** strip to `''` = "server picks".
|
|
5969
|
+
* - **Non-invite frames** (verto.modify, verto.bye, etc.): always carry the
|
|
5970
|
+
* current `_nodeId$.value` so the frame targets the node hosting the call.
|
|
5971
|
+
*
|
|
5972
|
+
* Pure function — exported for unit testing.
|
|
5973
|
+
*/
|
|
5974
|
+
function resolveInviteNodeId(args) {
|
|
5975
|
+
return args.isInvite && !args.reattach && !args.explicitNodeId ? "" : args.currentNodeId ?? "";
|
|
5976
|
+
}
|
|
5662
5977
|
var VertoManager = class extends Destroyable {
|
|
5663
5978
|
constructor(callSession) {
|
|
5664
5979
|
super();
|
|
@@ -5697,7 +6012,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
5697
6012
|
try {
|
|
5698
6013
|
await this.executeVerto(vertoModifyMessage);
|
|
5699
6014
|
} catch (error) {
|
|
5700
|
-
logger$
|
|
6015
|
+
logger$15.warn("[WebRTCManager] Call might already be disconnected, error sending Verto hold:", error);
|
|
5701
6016
|
throw error;
|
|
5702
6017
|
}
|
|
5703
6018
|
}
|
|
@@ -5710,7 +6025,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
5710
6025
|
try {
|
|
5711
6026
|
await this.executeVerto(vertoModifyMessage);
|
|
5712
6027
|
} catch (error) {
|
|
5713
|
-
logger$
|
|
6028
|
+
logger$15.warn("[WebRTCManager] Call might already be disconnected, error sending Verto unhold:", error);
|
|
5714
6029
|
throw error;
|
|
5715
6030
|
}
|
|
5716
6031
|
}
|
|
@@ -5763,7 +6078,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
5763
6078
|
if (event.member_id) this.setSelfIdIfNull(event.member_id);
|
|
5764
6079
|
});
|
|
5765
6080
|
this.subscribeTo(this.vertoMedia$, (event) => {
|
|
5766
|
-
logger$
|
|
6081
|
+
logger$15.debug("[WebRTCManager] Received Verto media event (early media SDP):", event);
|
|
5767
6082
|
this._signalingStatus$.next("ringing");
|
|
5768
6083
|
const { sdp, callID } = event;
|
|
5769
6084
|
this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
|
|
@@ -5772,7 +6087,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
5772
6087
|
});
|
|
5773
6088
|
});
|
|
5774
6089
|
this.subscribeTo(this.vertoAnswer$, (event) => {
|
|
5775
|
-
logger$
|
|
6090
|
+
logger$15.debug("[WebRTCManager] Received Verto answer event:", event);
|
|
5776
6091
|
this._signalingStatus$.next("connecting");
|
|
5777
6092
|
const { sdp, callID } = event;
|
|
5778
6093
|
this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
|
|
@@ -5781,7 +6096,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
5781
6096
|
});
|
|
5782
6097
|
});
|
|
5783
6098
|
this.subscribeTo(this.vertoMediaParams$, (event) => {
|
|
5784
|
-
logger$
|
|
6099
|
+
logger$15.debug("[WebRTCManager] Received Verto mediaParams event:", event);
|
|
5785
6100
|
const { mediaParams, callID } = event;
|
|
5786
6101
|
const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callID);
|
|
5787
6102
|
const { audio, video } = mediaParams;
|
|
@@ -5795,7 +6110,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
5795
6110
|
timestamp: Date.now()
|
|
5796
6111
|
});
|
|
5797
6112
|
} catch (error) {
|
|
5798
|
-
logger$
|
|
6113
|
+
logger$15.warn("[WebRTCManager] Error applying server-pushed media params:", error);
|
|
5799
6114
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
5800
6115
|
}
|
|
5801
6116
|
})();
|
|
@@ -5817,13 +6132,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
5817
6132
|
*/
|
|
5818
6133
|
setNodeIdIfNull(nodeId) {
|
|
5819
6134
|
if (!this._nodeId$.value && nodeId) {
|
|
5820
|
-
logger$
|
|
6135
|
+
logger$15.debug(`[WebRTCManager] Early node_id set: ${nodeId}`);
|
|
5821
6136
|
this._nodeId$.next(nodeId);
|
|
5822
6137
|
}
|
|
5823
6138
|
}
|
|
5824
6139
|
setSelfIdIfNull(selfId) {
|
|
5825
6140
|
if (!this._selfId$.value && selfId) {
|
|
5826
|
-
logger$
|
|
6141
|
+
logger$15.debug(`[WebRTCManager] Early selfId set: ${selfId}`);
|
|
5827
6142
|
this._selfId$.next(selfId);
|
|
5828
6143
|
}
|
|
5829
6144
|
}
|
|
@@ -5832,7 +6147,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
5832
6147
|
const vertoPongMessage = VertoPong({ ...vertoPing });
|
|
5833
6148
|
await this.executeVerto(vertoPongMessage);
|
|
5834
6149
|
} catch (error) {
|
|
5835
|
-
logger$
|
|
6150
|
+
logger$15.warn("[WebRTCManager] Call might disconnect, error sending Verto pong:", error);
|
|
5836
6151
|
this.onError?.(new require_operators.VertoPongError(error));
|
|
5837
6152
|
}
|
|
5838
6153
|
}
|
|
@@ -5842,7 +6157,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
5842
6157
|
if (audio) await this.mainPeerConnection.updateSendersConstraints("audio", audio);
|
|
5843
6158
|
if (video) await this.mainPeerConnection.updateSendersConstraints("video", video);
|
|
5844
6159
|
} catch (error) {
|
|
5845
|
-
logger$
|
|
6160
|
+
logger$15.warn("[WebRTCManager] Error updating media constraints:", error);
|
|
5846
6161
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
5847
6162
|
throw error;
|
|
5848
6163
|
}
|
|
@@ -5872,20 +6187,20 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
5872
6187
|
try {
|
|
5873
6188
|
const pc = this.mainPeerConnection.peerConnection;
|
|
5874
6189
|
if (!pc) {
|
|
5875
|
-
logger$
|
|
6190
|
+
logger$15.warn("[WebRTCManager] No peer connection for keyframe request");
|
|
5876
6191
|
return;
|
|
5877
6192
|
}
|
|
5878
6193
|
const videoReceiver = pc.getReceivers().find((r) => r.track.kind === "video");
|
|
5879
6194
|
if (!videoReceiver) {
|
|
5880
|
-
logger$
|
|
6195
|
+
logger$15.warn("[WebRTCManager] No video receiver for keyframe request");
|
|
5881
6196
|
return;
|
|
5882
6197
|
}
|
|
5883
6198
|
if (typeof videoReceiver.requestKeyFrame === "function") {
|
|
5884
6199
|
videoReceiver.requestKeyFrame();
|
|
5885
|
-
logger$
|
|
5886
|
-
} else logger$
|
|
6200
|
+
logger$15.debug("[WebRTCManager] Keyframe requested via RTCRtpReceiver.requestKeyFrame()");
|
|
6201
|
+
} else logger$15.debug("[WebRTCManager] requestKeyFrame() not supported, skipping");
|
|
5887
6202
|
} catch (error) {
|
|
5888
|
-
logger$
|
|
6203
|
+
logger$15.warn("[WebRTCManager] Keyframe request failed (non-fatal):", error);
|
|
5889
6204
|
}
|
|
5890
6205
|
}
|
|
5891
6206
|
/**
|
|
@@ -5903,13 +6218,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
5903
6218
|
try {
|
|
5904
6219
|
const controller = this.mainPeerConnection;
|
|
5905
6220
|
if (!controller.peerConnection) {
|
|
5906
|
-
logger$
|
|
6221
|
+
logger$15.warn("[WebRTCManager] No peer connection for ICE restart");
|
|
5907
6222
|
return;
|
|
5908
6223
|
}
|
|
5909
6224
|
await controller.triggerIceRestart(relayOnly);
|
|
5910
|
-
logger$
|
|
6225
|
+
logger$15.info(`[WebRTCManager] ICE restart initiated${relayOnly ? " (relay-only)" : ""}`);
|
|
5911
6226
|
} catch (error) {
|
|
5912
|
-
logger$
|
|
6227
|
+
logger$15.error("[WebRTCManager] ICE restart failed:", error);
|
|
5913
6228
|
throw error;
|
|
5914
6229
|
}
|
|
5915
6230
|
}
|
|
@@ -5927,13 +6242,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
5927
6242
|
const entries = Array.from(this._rtcPeerConnectionsMap.entries());
|
|
5928
6243
|
for (const [id, controller] of entries) try {
|
|
5929
6244
|
if (!controller.peerConnection) {
|
|
5930
|
-
logger$
|
|
6245
|
+
logger$15.debug(`[WebRTCManager] No peer connection for leg ${id}, skipping ICE restart`);
|
|
5931
6246
|
continue;
|
|
5932
6247
|
}
|
|
5933
6248
|
await controller.triggerIceRestart(relayOnly);
|
|
5934
|
-
logger$
|
|
6249
|
+
logger$15.info(`[WebRTCManager] ICE restart initiated for leg ${id}${relayOnly ? " (relay-only)" : ""}`);
|
|
5935
6250
|
} catch (error) {
|
|
5936
|
-
logger$
|
|
6251
|
+
logger$15.warn(`[WebRTCManager] ICE restart failed for leg ${id}:`, error);
|
|
5937
6252
|
}
|
|
5938
6253
|
}
|
|
5939
6254
|
/**
|
|
@@ -5945,7 +6260,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
5945
6260
|
requestKeyframeAll() {
|
|
5946
6261
|
for (const [id, controller] of this._rtcPeerConnectionsMap) {
|
|
5947
6262
|
if (controller.isScreenShare) {
|
|
5948
|
-
logger$
|
|
6263
|
+
logger$15.debug(`[WebRTCManager] Skipping keyframe for send-only screen share leg ${id}`);
|
|
5949
6264
|
continue;
|
|
5950
6265
|
}
|
|
5951
6266
|
try {
|
|
@@ -5955,10 +6270,10 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
5955
6270
|
if (!videoReceiver) continue;
|
|
5956
6271
|
if (typeof videoReceiver.requestKeyFrame === "function") {
|
|
5957
6272
|
videoReceiver.requestKeyFrame();
|
|
5958
|
-
logger$
|
|
6273
|
+
logger$15.debug(`[WebRTCManager] Keyframe requested for leg ${id}`);
|
|
5959
6274
|
}
|
|
5960
6275
|
} catch (error) {
|
|
5961
|
-
logger$
|
|
6276
|
+
logger$15.warn(`[WebRTCManager] Keyframe request failed for leg ${id} (non-fatal):`, error);
|
|
5962
6277
|
}
|
|
5963
6278
|
}
|
|
5964
6279
|
}
|
|
@@ -6019,7 +6334,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6019
6334
|
default:
|
|
6020
6335
|
}
|
|
6021
6336
|
} catch (error) {
|
|
6022
|
-
logger$
|
|
6337
|
+
logger$15.error(`[WebRTCManager] Error sending Verto ${vertoMethod}:`, error);
|
|
6023
6338
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
6024
6339
|
if (vertoMethod === "verto.modify") this.onModifyFailed?.();
|
|
6025
6340
|
}
|
|
@@ -6034,7 +6349,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6034
6349
|
sdp
|
|
6035
6350
|
});
|
|
6036
6351
|
} catch (error) {
|
|
6037
|
-
logger$
|
|
6352
|
+
logger$15.warn("[WebRTCManager] Error processing modify response:", error);
|
|
6038
6353
|
const modifyError = error instanceof Error ? error : new Error(String(error), { cause: error });
|
|
6039
6354
|
this.onError?.(modifyError);
|
|
6040
6355
|
}
|
|
@@ -6046,7 +6361,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6046
6361
|
this._nodeId$.next(require_operators.getValueFrom(response, "result.node_id") ?? null);
|
|
6047
6362
|
const memberId = require_operators.getValueFrom(response, "result.result.result.memberID") ?? null;
|
|
6048
6363
|
const callId = require_operators.getValueFrom(response, "result.result.result.callID") ?? null;
|
|
6049
|
-
logger$
|
|
6364
|
+
logger$15.debug("[WebRTCManager] Verto invite response:", {
|
|
6050
6365
|
callId,
|
|
6051
6366
|
memberId,
|
|
6052
6367
|
response
|
|
@@ -6056,14 +6371,14 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6056
6371
|
if (callId) {
|
|
6057
6372
|
this.webRtcCallSession.addCallId(callId);
|
|
6058
6373
|
this.attachManager.attach(this.buildAttachableCall(callId));
|
|
6059
|
-
} else logger$
|
|
6374
|
+
} else logger$15.warn("[WebRTCManager] Cannot attach call, missing callId:", {
|
|
6060
6375
|
nodeId: this.nodeId,
|
|
6061
6376
|
callId
|
|
6062
6377
|
});
|
|
6063
|
-
logger$
|
|
6064
|
-
logger$
|
|
6378
|
+
logger$15.info("[WebRTCManager] Verto invite successful");
|
|
6379
|
+
logger$15.debug(`[WebRTCManager] nodeid: ${this._nodeId$.value}, selfId: ${this._selfId$.value}`);
|
|
6065
6380
|
} else {
|
|
6066
|
-
logger$
|
|
6381
|
+
logger$15.error("[WebRTCManager] Verto invite failed:", response);
|
|
6067
6382
|
const inviteError = response.error ? new require_operators.JSONRPCError(response.error.code, response.error.message, response.error.data) : /* @__PURE__ */ new Error("Verto invite failed: unexpected response");
|
|
6068
6383
|
this.onError?.(inviteError);
|
|
6069
6384
|
}
|
|
@@ -6108,17 +6423,17 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6108
6423
|
if (options.initOffer) this.handleInboundAnswer(rtcPeerConnController);
|
|
6109
6424
|
}
|
|
6110
6425
|
async handleInboundAnswer(rtcPeerConnController) {
|
|
6111
|
-
logger$
|
|
6426
|
+
logger$15.debug("[WebRTCManager] Waiting for inbound call to be accepted or rejected");
|
|
6112
6427
|
const vertoByeOrAccepted = await (0, rxjs.firstValueFrom)((0, rxjs.race)(this.vertoBye$, this.webRtcCallSession.answered$).pipe((0, rxjs.takeUntil)(this.destroyed$))).catch(() => null);
|
|
6113
6428
|
if (vertoByeOrAccepted === null) {
|
|
6114
|
-
logger$
|
|
6429
|
+
logger$15.debug("[WebRTCManager] Inbound answer handler aborted (destroyed).");
|
|
6115
6430
|
return;
|
|
6116
6431
|
}
|
|
6117
6432
|
if (isVertoByeMessage(vertoByeOrAccepted)) {
|
|
6118
|
-
logger$
|
|
6433
|
+
logger$15.info("[WebRTCManager] Inbound call ended by remote before answer.");
|
|
6119
6434
|
this.callSession?.destroy();
|
|
6120
6435
|
} else if (!vertoByeOrAccepted) {
|
|
6121
|
-
logger$
|
|
6436
|
+
logger$15.info("[WebRTCManager] Inbound call rejected by user.");
|
|
6122
6437
|
try {
|
|
6123
6438
|
await this.bye("USER_BUSY");
|
|
6124
6439
|
} finally {
|
|
@@ -6126,19 +6441,19 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6126
6441
|
this.callSession?.destroy();
|
|
6127
6442
|
}
|
|
6128
6443
|
} else {
|
|
6129
|
-
logger$
|
|
6444
|
+
logger$15.debug("[WebRTCManager] Inbound call accepted, creating SDP answer");
|
|
6130
6445
|
const answerOptions = this.webRtcCallSession.answerMediaOptions;
|
|
6131
6446
|
try {
|
|
6132
6447
|
await rtcPeerConnController.acceptInbound(answerOptions);
|
|
6133
6448
|
} catch (error) {
|
|
6134
|
-
logger$
|
|
6449
|
+
logger$15.error("[WebRTCManager] Error creating inbound answer:", error);
|
|
6135
6450
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
6136
6451
|
}
|
|
6137
6452
|
}
|
|
6138
6453
|
}
|
|
6139
6454
|
setupVertoAttachHandler() {
|
|
6140
6455
|
this.subscribeTo(this.vertoAttach$, async (vertoAttach) => {
|
|
6141
|
-
logger$
|
|
6456
|
+
logger$15.debug("[WebRTCManager] Received Verto attach event for existing call:", vertoAttach);
|
|
6142
6457
|
const { callID } = vertoAttach;
|
|
6143
6458
|
await this.attachManager.attach({
|
|
6144
6459
|
nodeId: this.nodeId ?? void 0,
|
|
@@ -6198,26 +6513,29 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6198
6513
|
else if (rtcPeerConnController.isAdditionalDevice) subscribe.push(...PreferencesContainer.instance.inviteSubscribeAdditionalDevice);
|
|
6199
6514
|
else if (rtcPeerConnController.isScreenShare) subscribe.push(...PreferencesContainer.instance.inviteSubscribeScreenshare);
|
|
6200
6515
|
}
|
|
6201
|
-
const isInvite = isVertoInviteMessage(vertoMessage);
|
|
6202
|
-
const isReattach = isInvite && this.webRtcCallSession.options.reattach;
|
|
6203
6516
|
return {
|
|
6204
6517
|
callID: rtcPeerConnController.id,
|
|
6205
|
-
node_id:
|
|
6518
|
+
node_id: resolveInviteNodeId({
|
|
6519
|
+
isInvite: isVertoInviteMessage(vertoMessage),
|
|
6520
|
+
reattach: this.webRtcCallSession.options.reattach === true,
|
|
6521
|
+
explicitNodeId: this.webRtcCallSession.options.nodeId,
|
|
6522
|
+
currentNodeId: this._nodeId$.value
|
|
6523
|
+
}),
|
|
6206
6524
|
subscribe
|
|
6207
6525
|
};
|
|
6208
6526
|
}
|
|
6209
6527
|
async sendLocalDescriptionOnceAccepted(vertoMessageRequest, rtcPeerConnectionController) {
|
|
6210
|
-
logger$
|
|
6528
|
+
logger$15.debug("[WebRTCManager] Waiting for call to be accepted or ended before sending answer");
|
|
6211
6529
|
const vertoByeOrAccepted = await (0, rxjs.firstValueFrom)((0, rxjs.race)(this.vertoBye$, this.webRtcCallSession.answered$).pipe((0, rxjs.takeUntil)(this.destroyed$))).catch(() => null);
|
|
6212
6530
|
if (vertoByeOrAccepted === null) {
|
|
6213
|
-
logger$
|
|
6531
|
+
logger$15.debug("[WebRTCManager] Destroyed while waiting for call acceptance");
|
|
6214
6532
|
return;
|
|
6215
6533
|
}
|
|
6216
6534
|
if (isVertoByeMessage(vertoByeOrAccepted)) {
|
|
6217
|
-
logger$
|
|
6535
|
+
logger$15.info("[WebRTCManager] Call ended before answer was sent.");
|
|
6218
6536
|
this.callSession?.destroy();
|
|
6219
6537
|
} else if (!vertoByeOrAccepted) {
|
|
6220
|
-
logger$
|
|
6538
|
+
logger$15.info("[WebRTCManager] Call was not accepted, sending verto.bye.");
|
|
6221
6539
|
try {
|
|
6222
6540
|
await this.bye("USER_BUSY");
|
|
6223
6541
|
} finally {
|
|
@@ -6225,14 +6543,14 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6225
6543
|
this.callSession?.destroy();
|
|
6226
6544
|
}
|
|
6227
6545
|
} else {
|
|
6228
|
-
logger$
|
|
6546
|
+
logger$15.debug("[WebRTCManager] Call accepted, sending answer");
|
|
6229
6547
|
try {
|
|
6230
6548
|
this._signalingStatus$.next("connecting");
|
|
6231
6549
|
await this.sendLocalDescription(vertoMessageRequest, rtcPeerConnectionController);
|
|
6232
6550
|
await rtcPeerConnectionController.updateAnswerStatus({ status: "sent" });
|
|
6233
6551
|
await this.attachManager.attach(this.buildAttachableCall());
|
|
6234
6552
|
} catch (error) {
|
|
6235
|
-
logger$
|
|
6553
|
+
logger$15.error("[WebRTCManager] Error sending Verto answer:", error);
|
|
6236
6554
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
6237
6555
|
await rtcPeerConnectionController.updateAnswerStatus({ status: "failed" });
|
|
6238
6556
|
}
|
|
@@ -6273,6 +6591,14 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6273
6591
|
async unmuteMainVideoInputDevice() {
|
|
6274
6592
|
return this.mainPeerConnection.restoreTrackSender("video");
|
|
6275
6593
|
}
|
|
6594
|
+
/** Get or lazily create the local audio pipeline for the main peer connection. */
|
|
6595
|
+
ensureLocalAudioPipeline() {
|
|
6596
|
+
return this.mainPeerConnection.ensureLocalAudioPipeline();
|
|
6597
|
+
}
|
|
6598
|
+
/** The currently-active local audio pipeline, or null if it hasn't been created. */
|
|
6599
|
+
get localAudioPipeline() {
|
|
6600
|
+
return this.mainPeerConnection.localAudioPipeline;
|
|
6601
|
+
}
|
|
6276
6602
|
async addInputDevice(options = {
|
|
6277
6603
|
audio: false,
|
|
6278
6604
|
video: true
|
|
@@ -6323,10 +6649,10 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6323
6649
|
});
|
|
6324
6650
|
await (0, rxjs.firstValueFrom)(rtcPeerConnController.connectionState$.pipe((0, rxjs.filter)((state) => state === "connected"), (0, rxjs.take)(1), (0, rxjs.timeout)(this._screenShareTimeoutMs), (0, rxjs.takeUntil)(this.destroyed$)));
|
|
6325
6651
|
this._screenShareStatus$.next("started");
|
|
6326
|
-
logger$
|
|
6652
|
+
logger$15.info("[WebRTCManager] Screen share started successfully.");
|
|
6327
6653
|
return rtcPeerConnController.id;
|
|
6328
6654
|
} catch (error) {
|
|
6329
|
-
logger$
|
|
6655
|
+
logger$15.warn("[WebRTCManager] Error initializing additional peer connection:", error);
|
|
6330
6656
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
6331
6657
|
if (rtcPeerConnController) rtcPeerConnController.destroy();
|
|
6332
6658
|
this._screenShareStatus$.next("none");
|
|
@@ -6345,9 +6671,9 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6345
6671
|
if (removeTrack) return this.mainPeerConnection.stopTrackSender(removeTrack, { updateTransceiverDirection: true });
|
|
6346
6672
|
}
|
|
6347
6673
|
async removeScreenMedia() {
|
|
6348
|
-
if (!["starting", "started"].includes(this._screenShareStatus$.value)) logger$
|
|
6674
|
+
if (!["starting", "started"].includes(this._screenShareStatus$.value)) logger$15.warn("[WebRTCManager] No active screen share to stop.");
|
|
6349
6675
|
if (!this._screenShareId) {
|
|
6350
|
-
logger$
|
|
6676
|
+
logger$15.debug("[WebRTCManager] No screen share peer connection found.");
|
|
6351
6677
|
return;
|
|
6352
6678
|
}
|
|
6353
6679
|
this._screenShareStatus$.next("stopping");
|
|
@@ -6376,7 +6702,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6376
6702
|
dialogParams: this.dialogParams(rtcPeerConnController)
|
|
6377
6703
|
}));
|
|
6378
6704
|
} catch (error) {
|
|
6379
|
-
logger$
|
|
6705
|
+
logger$15.warn("[WebRTCManager] Call might already be disconnected, error sending Verto bye:", error);
|
|
6380
6706
|
throw error;
|
|
6381
6707
|
}
|
|
6382
6708
|
}
|
|
@@ -6394,7 +6720,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6394
6720
|
try {
|
|
6395
6721
|
await this.executeVerto(vertoInfoMessage);
|
|
6396
6722
|
} catch (error) {
|
|
6397
|
-
logger$
|
|
6723
|
+
logger$15.warn("[WebRTCManager] Error sending DTMF digits:", error);
|
|
6398
6724
|
throw error;
|
|
6399
6725
|
}
|
|
6400
6726
|
}
|
|
@@ -6405,10 +6731,10 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6405
6731
|
action: "transfer"
|
|
6406
6732
|
});
|
|
6407
6733
|
try {
|
|
6408
|
-
logger$
|
|
6734
|
+
logger$15.debug("[WebRTCManager] Transferring call with options:", options);
|
|
6409
6735
|
await this.executeVerto(message);
|
|
6410
6736
|
} catch (error) {
|
|
6411
|
-
logger$
|
|
6737
|
+
logger$15.error("[WebRTCManager] Error transferring call:", error);
|
|
6412
6738
|
throw error;
|
|
6413
6739
|
}
|
|
6414
6740
|
}
|
|
@@ -6422,6 +6748,76 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6422
6748
|
}
|
|
6423
6749
|
};
|
|
6424
6750
|
|
|
6751
|
+
//#endregion
|
|
6752
|
+
//#region src/controllers/RemoteAudioMeter.ts
|
|
6753
|
+
const logger$14 = require_operators.getLogger();
|
|
6754
|
+
/**
|
|
6755
|
+
* Read-only audio level meter for a remote MediaStream. Attaches an
|
|
6756
|
+
* AnalyserNode to a MediaStreamAudioSourceNode so it observes the stream
|
|
6757
|
+
* without affecting the caller's playback path (no GainNode, no destination).
|
|
6758
|
+
*
|
|
6759
|
+
* The server delivers all remote audio as a single mixed stream — there is
|
|
6760
|
+
* no per-participant demux — so this meter reports the aggregate remote
|
|
6761
|
+
* level, not per-member.
|
|
6762
|
+
*/
|
|
6763
|
+
var RemoteAudioMeter = class extends Destroyable {
|
|
6764
|
+
constructor(options = {}) {
|
|
6765
|
+
super();
|
|
6766
|
+
this._source = null;
|
|
6767
|
+
this._stream = null;
|
|
6768
|
+
this._audioContext = (options.audioContextFactory ?? (() => new AudioContext()))();
|
|
6769
|
+
this._analyser = this._audioContext.createAnalyser();
|
|
6770
|
+
this._analyser.fftSize = 2048;
|
|
6771
|
+
this._analyser.smoothingTimeConstant = .3;
|
|
6772
|
+
this._analyserBuffer = new Uint8Array(new ArrayBuffer(this._analyser.fftSize));
|
|
6773
|
+
this._pollIntervalMs = options.pollIntervalMs ?? AUDIO_LEVEL_POLL_INTERVAL_MS;
|
|
6774
|
+
}
|
|
6775
|
+
/** RMS level of the remote audio, 0..1. 0 when no stream is attached. */
|
|
6776
|
+
get level$() {
|
|
6777
|
+
return this.deferEmission((0, rxjs.interval)(this._pollIntervalMs, rxjs.animationFrameScheduler).pipe((0, rxjs.map)(() => this.computeLevel())));
|
|
6778
|
+
}
|
|
6779
|
+
/**
|
|
6780
|
+
* Attach (or replace) the MediaStream whose audio track is being metered.
|
|
6781
|
+
* Pass null to detach without destroying the meter.
|
|
6782
|
+
*/
|
|
6783
|
+
setStream(stream) {
|
|
6784
|
+
if (this._source) {
|
|
6785
|
+
try {
|
|
6786
|
+
this._source.disconnect();
|
|
6787
|
+
} catch (error) {
|
|
6788
|
+
logger$14.debug("[RemoteAudioMeter] source disconnect warning:", error);
|
|
6789
|
+
}
|
|
6790
|
+
this._source = null;
|
|
6791
|
+
this._stream = null;
|
|
6792
|
+
}
|
|
6793
|
+
if (!stream || stream.getAudioTracks().length === 0) return;
|
|
6794
|
+
this._stream = new MediaStream(stream.getAudioTracks());
|
|
6795
|
+
this._source = this._audioContext.createMediaStreamSource(this._stream);
|
|
6796
|
+
}
|
|
6797
|
+
destroy() {
|
|
6798
|
+
if (this._source) {
|
|
6799
|
+
try {
|
|
6800
|
+
this._source.disconnect();
|
|
6801
|
+
} catch {}
|
|
6802
|
+
this._source = null;
|
|
6803
|
+
}
|
|
6804
|
+
this._audioContext.close().catch((error) => {
|
|
6805
|
+
logger$14.debug("[RemoteAudioMeter] audio context close warning:", error);
|
|
6806
|
+
});
|
|
6807
|
+
super.destroy();
|
|
6808
|
+
}
|
|
6809
|
+
computeLevel() {
|
|
6810
|
+
if (!this._source) return 0;
|
|
6811
|
+
this._analyser.getByteTimeDomainData(this._analyserBuffer);
|
|
6812
|
+
let sum = 0;
|
|
6813
|
+
for (const sample of this._analyserBuffer) {
|
|
6814
|
+
const normalized = (sample - 128) / 128;
|
|
6815
|
+
sum += normalized * normalized;
|
|
6816
|
+
}
|
|
6817
|
+
return Math.sqrt(sum / this._analyserBuffer.length);
|
|
6818
|
+
}
|
|
6819
|
+
};
|
|
6820
|
+
|
|
6425
6821
|
//#endregion
|
|
6426
6822
|
//#region src/controllers/RTCStatsMonitor.ts
|
|
6427
6823
|
/**
|
|
@@ -6584,11 +6980,11 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
6584
6980
|
let availableOutgoingBitrate;
|
|
6585
6981
|
report.forEach((stat) => {
|
|
6586
6982
|
if (isInboundRtpStat(stat)) if (stat.kind === "audio") {
|
|
6587
|
-
audioPacketsReceived += stat.packetsReceived ??
|
|
6983
|
+
audioPacketsReceived += stat.packetsReceived ?? 0;
|
|
6588
6984
|
audioPacketsLost += stat.packetsLost ?? 0;
|
|
6589
6985
|
audioJitter = Math.max(audioJitter, (stat.jitter ?? 0) * 1e3);
|
|
6590
6986
|
} else {
|
|
6591
|
-
videoPacketsReceived += stat.packetsReceived ??
|
|
6987
|
+
videoPacketsReceived += stat.packetsReceived ?? 0;
|
|
6592
6988
|
videoPacketsLost += stat.packetsLost ?? 0;
|
|
6593
6989
|
}
|
|
6594
6990
|
if (isCandidatePairStat(stat) && stat.state === "succeeded" && stat.nominated) {
|
|
@@ -7238,6 +7634,8 @@ var WebRTCCall = class extends Destroyable {
|
|
|
7238
7634
|
this._bandwidthConstrained$ = this.createBehaviorSubject(false);
|
|
7239
7635
|
this._mediaParamsUpdated$ = this.createSubject();
|
|
7240
7636
|
this._customSubscriptions = /* @__PURE__ */ new Map();
|
|
7637
|
+
this._pushToTalkEnabled = false;
|
|
7638
|
+
this._remoteAudioMeter = null;
|
|
7241
7639
|
this.id = options.callId ?? (0, uuid.v4)();
|
|
7242
7640
|
this.to = options.to;
|
|
7243
7641
|
this._userVariables$.next({
|
|
@@ -7636,10 +8034,10 @@ var WebRTCCall = class extends Destroyable {
|
|
|
7636
8034
|
try {
|
|
7637
8035
|
if (this.vertoManager.requestIceRestartAll) await this.vertoManager.requestIceRestartAll(relayOnly);
|
|
7638
8036
|
else await this.vertoManager.requestIceRestart?.(relayOnly);
|
|
7639
|
-
return true;
|
|
7640
8037
|
} catch {
|
|
7641
8038
|
return false;
|
|
7642
8039
|
}
|
|
8040
|
+
return this.waitForPeerConnectionConnected();
|
|
7643
8041
|
},
|
|
7644
8042
|
disableVideo: () => {
|
|
7645
8043
|
try {
|
|
@@ -7731,6 +8129,27 @@ var WebRTCCall = class extends Destroyable {
|
|
|
7731
8129
|
}
|
|
7732
8130
|
}
|
|
7733
8131
|
/**
|
|
8132
|
+
* Wait for the underlying RTCPeerConnection to reach 'connected' after
|
|
8133
|
+
* triggering an ICE restart. Resolves true on success, false on failure
|
|
8134
|
+
* or if the state doesn't transition within the configured timeout.
|
|
8135
|
+
*
|
|
8136
|
+
* Polls connectionState directly because the recovery manager already
|
|
8137
|
+
* wraps this call in its own withTimeout(); a separate listener-based
|
|
8138
|
+
* implementation would race the outer timeout in subtle ways.
|
|
8139
|
+
*/
|
|
8140
|
+
async waitForPeerConnectionConnected() {
|
|
8141
|
+
const pc = this.rtcPeerConnection;
|
|
8142
|
+
if (!pc) return false;
|
|
8143
|
+
const deadline = Date.now() + PEER_CONNECTION_RECOVERY_WAIT_MS;
|
|
8144
|
+
for (;;) {
|
|
8145
|
+
const state = pc.connectionState;
|
|
8146
|
+
if (state === "connected") return true;
|
|
8147
|
+
if (state === "failed" || state === "closed") return false;
|
|
8148
|
+
if (Date.now() >= deadline) return false;
|
|
8149
|
+
await new Promise((resolve) => setTimeout(resolve, PEER_CONNECTION_RECOVERY_POLL_MS));
|
|
8150
|
+
}
|
|
8151
|
+
}
|
|
8152
|
+
/**
|
|
7734
8153
|
* @internal Stop and destroy resilience subsystems (on disconnect/destroy).
|
|
7735
8154
|
* Clears references so they can be re-created on reconnect.
|
|
7736
8155
|
*/
|
|
@@ -7867,8 +8286,13 @@ var WebRTCCall = class extends Destroyable {
|
|
|
7867
8286
|
const cached = this._customSubscriptions.get(eventType);
|
|
7868
8287
|
if (cached) return cached;
|
|
7869
8288
|
const filtered$ = this.callSessionEvents$.pipe((0, rxjs.filter)((event) => event.event_type === eventType), (0, rxjs.map)((event) => JSON.parse(JSON.stringify(event))), (0, rxjs.takeUntil)(this._destroyed$));
|
|
8289
|
+
this._sendVertoSubscribe(eventType).then(() => {
|
|
8290
|
+
this._customSubscriptions.set(eventType, filtered$);
|
|
8291
|
+
}, (error) => {
|
|
8292
|
+
this._customSubscriptions.delete(eventType);
|
|
8293
|
+
logger$11.warn(`[Call] verto.subscribe for '${eventType}' failed, not caching:`, error);
|
|
8294
|
+
});
|
|
7870
8295
|
this._customSubscriptions.set(eventType, filtered$);
|
|
7871
|
-
this._sendVertoSubscribe(eventType);
|
|
7872
8296
|
return filtered$;
|
|
7873
8297
|
}
|
|
7874
8298
|
get webrtcMessages$() {
|
|
@@ -7983,37 +8407,156 @@ var WebRTCCall = class extends Destroyable {
|
|
|
7983
8407
|
async transfer(options) {
|
|
7984
8408
|
return this.vertoManager.transfer(options);
|
|
7985
8409
|
}
|
|
8410
|
+
/**
|
|
8411
|
+
* Set the local microphone gain as a percentage applied before transmission.
|
|
8412
|
+
*
|
|
8413
|
+
* - `0` = silent
|
|
8414
|
+
* - `100` = unity (no change, default)
|
|
8415
|
+
* - `200` = 2× digital boost (max; expect clipping / noise amplification)
|
|
8416
|
+
*
|
|
8417
|
+
* Values are clamped to [0, 200]. Engages the local audio pipeline on
|
|
8418
|
+
* first use (one-time cost).
|
|
8419
|
+
*
|
|
8420
|
+
* Note: this is a **digital** multiplier applied in a Web Audio GainNode
|
|
8421
|
+
* between your mic track and the RTCRtpSender — it does not change the
|
|
8422
|
+
* physical mic's hardware sensitivity. Browsers' autoGainControl can
|
|
8423
|
+
* fight the setting; call {@link setAutoGainControl}(false) for
|
|
8424
|
+
* predictable behaviour.
|
|
8425
|
+
*
|
|
8426
|
+
* @param value - Gain percentage (0..200; 100 = unity).
|
|
8427
|
+
*/
|
|
8428
|
+
setLocalMicrophoneGain(value) {
|
|
8429
|
+
const pipeline = this.vertoManager.ensureLocalAudioPipeline();
|
|
8430
|
+
if (!pipeline) {
|
|
8431
|
+
logger$11.warn("[Call] setLocalMicrophoneGain: audio pipeline unavailable");
|
|
8432
|
+
return;
|
|
8433
|
+
}
|
|
8434
|
+
const percent = Math.max(0, Math.min(200, value));
|
|
8435
|
+
pipeline.setGain(percent / 100);
|
|
8436
|
+
}
|
|
8437
|
+
/** Observable of the current local microphone gain (0..200, where 100 = unity). */
|
|
8438
|
+
get localMicrophoneGain$() {
|
|
8439
|
+
const pipeline = this.vertoManager.ensureLocalAudioPipeline();
|
|
8440
|
+
if (!pipeline) return (0, rxjs.of)(100).pipe((0, rxjs.takeUntil)(this._destroyed$));
|
|
8441
|
+
return this.publicCachedObservable("localMicrophoneGain$", () => pipeline.gain$.pipe((0, rxjs.map)((multiplier) => multiplier * 100), (0, rxjs.takeUntil)(this._destroyed$)));
|
|
8442
|
+
}
|
|
8443
|
+
/**
|
|
8444
|
+
* Observable of the RMS audio level of the local microphone, 0..1.
|
|
8445
|
+
* Emits at ~30fps while a mic track is active. Engages the local audio
|
|
8446
|
+
* pipeline on first subscription.
|
|
8447
|
+
*/
|
|
8448
|
+
get localAudioLevel$() {
|
|
8449
|
+
const pipeline = this.vertoManager.ensureLocalAudioPipeline();
|
|
8450
|
+
if (!pipeline) return (0, rxjs.of)(0).pipe((0, rxjs.takeUntil)(this._destroyed$));
|
|
8451
|
+
return this.publicCachedObservable("localAudioLevel$", () => pipeline.level$.pipe((0, rxjs.takeUntil)(this._destroyed$), (0, rxjs.share)()));
|
|
8452
|
+
}
|
|
8453
|
+
/**
|
|
8454
|
+
* Observable that is `true` while the local participant is speaking
|
|
8455
|
+
* (RMS level above the VAD threshold, with hold time to avoid flicker).
|
|
8456
|
+
*/
|
|
8457
|
+
get localSpeaking$() {
|
|
8458
|
+
const pipeline = this.vertoManager.ensureLocalAudioPipeline();
|
|
8459
|
+
if (!pipeline) return (0, rxjs.of)(false).pipe((0, rxjs.takeUntil)(this._destroyed$));
|
|
8460
|
+
return this.publicCachedObservable("localSpeaking$", () => pipeline.speaking$.pipe((0, rxjs.takeUntil)(this._destroyed$), (0, rxjs.share)()));
|
|
8461
|
+
}
|
|
8462
|
+
/**
|
|
8463
|
+
* Enable push-to-talk: while {@link setPushToTalkActive} has been called
|
|
8464
|
+
* with `false`, the microphone gain is forced to 0; calling
|
|
8465
|
+
* {@link setPushToTalkActive} with `true` restores the configured gain.
|
|
8466
|
+
* Use this instead of mute/unmute for instant talk/silence transitions
|
|
8467
|
+
* because it doesn't rebuild the track.
|
|
8468
|
+
*
|
|
8469
|
+
* This method installs the pipeline but does not attach any keyboard
|
|
8470
|
+
* listener — consumers bind the key themselves and call
|
|
8471
|
+
* {@link setPushToTalkActive} on keydown/keyup.
|
|
8472
|
+
*/
|
|
8473
|
+
enablePushToTalk() {
|
|
8474
|
+
const pipeline = this.vertoManager.ensureLocalAudioPipeline();
|
|
8475
|
+
if (!pipeline) {
|
|
8476
|
+
logger$11.warn("[Call] enablePushToTalk: audio pipeline unavailable");
|
|
8477
|
+
return;
|
|
8478
|
+
}
|
|
8479
|
+
pipeline.setPTTActive(false);
|
|
8480
|
+
this._pushToTalkEnabled = true;
|
|
8481
|
+
}
|
|
8482
|
+
/** Disable push-to-talk; mic gain returns to the configured value. */
|
|
8483
|
+
disablePushToTalk() {
|
|
8484
|
+
this.vertoManager.localAudioPipeline?.setPTTActive(true);
|
|
8485
|
+
this._pushToTalkEnabled = false;
|
|
8486
|
+
}
|
|
8487
|
+
/**
|
|
8488
|
+
* While push-to-talk is enabled, sets the talk state. `true` = transmitting,
|
|
8489
|
+
* `false` = silent. No-op if push-to-talk has not been enabled.
|
|
8490
|
+
*/
|
|
8491
|
+
setPushToTalkActive(active) {
|
|
8492
|
+
if (!this._pushToTalkEnabled) return;
|
|
8493
|
+
this.vertoManager.localAudioPipeline?.setPTTActive(active);
|
|
8494
|
+
}
|
|
8495
|
+
/**
|
|
8496
|
+
* Toggle echo cancellation on the local mic at runtime. Applied via
|
|
8497
|
+
* `track.applyConstraints`; browsers that don't honour runtime constraints
|
|
8498
|
+
* (notably iOS Safari) fall back to re-acquiring the track with the new
|
|
8499
|
+
* constraint set and plumbing the replacement through the local audio
|
|
8500
|
+
* pipeline if one is active.
|
|
8501
|
+
*/
|
|
8502
|
+
async setEchoCancellation(enabled) {
|
|
8503
|
+
await this.vertoManager.updateMediaConstraints({ audio: { echoCancellation: enabled } });
|
|
8504
|
+
}
|
|
8505
|
+
/** Toggle browser noise suppression on the local mic at runtime. */
|
|
8506
|
+
async setNoiseSuppression(enabled) {
|
|
8507
|
+
await this.vertoManager.updateMediaConstraints({ audio: { noiseSuppression: enabled } });
|
|
8508
|
+
}
|
|
8509
|
+
/** Toggle browser automatic gain control on the local mic at runtime. */
|
|
8510
|
+
async setAutoGainControl(enabled) {
|
|
8511
|
+
await this.vertoManager.updateMediaConstraints({ audio: { autoGainControl: enabled } });
|
|
8512
|
+
}
|
|
8513
|
+
/**
|
|
8514
|
+
* Observable of the aggregate remote audio level, 0..1 RMS. The server
|
|
8515
|
+
* delivers a single mixed audio stream for all remote participants — this
|
|
8516
|
+
* meter reports that mix. Per-participant audio is not available client-side.
|
|
8517
|
+
*
|
|
8518
|
+
* Engages a shared AudioContext on first subscription (cheap — one
|
|
8519
|
+
* AnalyserNode, no GainNode, no destination) so it does not affect the
|
|
8520
|
+
* caller's audio element playback.
|
|
8521
|
+
*/
|
|
8522
|
+
get remoteAudioLevel$() {
|
|
8523
|
+
return this.publicCachedObservable("remoteAudioLevel$", () => {
|
|
8524
|
+
this._remoteAudioMeter ??= new RemoteAudioMeter();
|
|
8525
|
+
const meter = this._remoteAudioMeter;
|
|
8526
|
+
this.subscribeTo(this.vertoManager.remoteStream$, (stream) => {
|
|
8527
|
+
meter.setStream(stream);
|
|
8528
|
+
});
|
|
8529
|
+
return meter.level$.pipe((0, rxjs.takeUntil)(this._destroyed$), (0, rxjs.share)());
|
|
8530
|
+
});
|
|
8531
|
+
}
|
|
7986
8532
|
/** Destroys the call, releasing all resources and subscriptions. */
|
|
7987
8533
|
destroy() {
|
|
7988
8534
|
if (this._status$.value === "destroyed") return;
|
|
7989
8535
|
this._status$.next("destroyed");
|
|
7990
8536
|
this.stopResilienceSubsystems();
|
|
8537
|
+
this._remoteAudioMeter?.destroy();
|
|
8538
|
+
this._remoteAudioMeter = null;
|
|
7991
8539
|
this.vertoManager.destroy();
|
|
7992
8540
|
this.callEventsManager.destroy();
|
|
7993
8541
|
super.destroy();
|
|
7994
8542
|
}
|
|
7995
8543
|
/**
|
|
7996
8544
|
* @internal Send a verto.subscribe message to add an event type to the
|
|
7997
|
-
* server's subscription list for this call.
|
|
7998
|
-
*
|
|
7999
|
-
|
|
8000
|
-
|
|
8001
|
-
|
|
8002
|
-
|
|
8003
|
-
|
|
8004
|
-
|
|
8005
|
-
|
|
8006
|
-
|
|
8007
|
-
|
|
8008
|
-
|
|
8009
|
-
|
|
8010
|
-
|
|
8011
|
-
|
|
8012
|
-
logger$11.warn(`[Call] verto.subscribe for '${eventType}' failed (non-fatal):`, error);
|
|
8013
|
-
});
|
|
8014
|
-
} catch (error) {
|
|
8015
|
-
logger$11.warn(`[Call] Failed to send verto.subscribe for '${eventType}':`, error);
|
|
8016
|
-
}
|
|
8545
|
+
* server's subscription list for this call. Returns the underlying RPC
|
|
8546
|
+
* promise so callers can decide whether to cache the observable on success
|
|
8547
|
+
* or retry on failure.
|
|
8548
|
+
*/
|
|
8549
|
+
async _sendVertoSubscribe(eventType) {
|
|
8550
|
+
const message = VertoSubscribe({
|
|
8551
|
+
sessid: this.id,
|
|
8552
|
+
eventChannel: [eventType]
|
|
8553
|
+
});
|
|
8554
|
+
const params = {
|
|
8555
|
+
callID: this.id,
|
|
8556
|
+
node_id: this.vertoManager.nodeId ?? "",
|
|
8557
|
+
message
|
|
8558
|
+
};
|
|
8559
|
+
await this.clientSession.execute(WebrtcVerto(params));
|
|
8017
8560
|
}
|
|
8018
8561
|
};
|
|
8019
8562
|
|
|
@@ -8030,11 +8573,21 @@ function inferCallErrorKind(error) {
|
|
|
8030
8573
|
if (error instanceof require_operators.WebSocketConnectionError || error instanceof require_operators.TransportConnectionError) return "network";
|
|
8031
8574
|
return "internal";
|
|
8032
8575
|
}
|
|
8576
|
+
/** JSON-RPC error codes that ClientSessionManager treats as recoverable at the
|
|
8577
|
+
* session level. Surfacing one of these against an in-flight call should not
|
|
8578
|
+
* destroy the call, because the session will reauthenticate and any pending
|
|
8579
|
+
* RPC can then be retried. */
|
|
8580
|
+
const RECOVERABLE_RPC_CODES = new Set([
|
|
8581
|
+
RPC_ERROR_REQUESTER_VALIDATION_FAILED,
|
|
8582
|
+
RPC_ERROR_AUTHENTICATION_FAILED,
|
|
8583
|
+
RPC_ERROR_INVALID_PARAMS
|
|
8584
|
+
]);
|
|
8033
8585
|
/** Determines whether an error should be fatal (destroy the call). */
|
|
8034
8586
|
function isFatalError(error) {
|
|
8035
8587
|
if (error instanceof require_operators.VertoPongError) return false;
|
|
8036
8588
|
if (error instanceof require_operators.MediaTrackError) return false;
|
|
8037
8589
|
if (error instanceof require_operators.RPCTimeoutError) return false;
|
|
8590
|
+
if (error instanceof require_operators.JSONRPCError && RECOVERABLE_RPC_CODES.has(error.code)) return false;
|
|
8038
8591
|
return true;
|
|
8039
8592
|
}
|
|
8040
8593
|
/**
|
|
@@ -8469,10 +9022,10 @@ var PendingRPC = class PendingRPC {
|
|
|
8469
9022
|
}
|
|
8470
9023
|
let isSettled = false;
|
|
8471
9024
|
const subscription = (0, rxjs.race)(responses$.pipe((0, rxjs.filter)((result) => result.id === request.id), (0, rxjs.take)(1)), new rxjs.Observable((subscriber) => {
|
|
8472
|
-
const timer$
|
|
9025
|
+
const timer$3 = setTimeout(() => {
|
|
8473
9026
|
subscriber.error(new require_operators.RPCTimeoutError(request.id, timeoutMs));
|
|
8474
9027
|
}, timeoutMs);
|
|
8475
|
-
return () => clearTimeout(timer$
|
|
9028
|
+
return () => clearTimeout(timer$3);
|
|
8476
9029
|
}), signal ? new rxjs.Observable((subscriber) => {
|
|
8477
9030
|
const abortHandler = () => {
|
|
8478
9031
|
subscriber.error(new DOMException("The operation was aborted", "AbortError"));
|
|
@@ -8537,7 +9090,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
8537
9090
|
this.attachManager = attachManager;
|
|
8538
9091
|
this.dpopManager = dpopManager;
|
|
8539
9092
|
this.callCreateTimeout = 6e3;
|
|
8540
|
-
this.agent = `signalwire-
|
|
9093
|
+
this.agent = `signalwire-js/4.0.0`;
|
|
8541
9094
|
this.eventAcks = true;
|
|
8542
9095
|
this.authorizationState$ = this.createReplaySubject(1);
|
|
8543
9096
|
this.connectVersion = {
|
|
@@ -8549,7 +9102,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
8549
9102
|
this._errors$ = this.createReplaySubject(1);
|
|
8550
9103
|
this._authState$ = this.createBehaviorSubject({ kind: "unauthenticated" });
|
|
8551
9104
|
this._wasClientBound = false;
|
|
8552
|
-
this.
|
|
9105
|
+
this._userInfo$ = this.createBehaviorSubject(null);
|
|
8553
9106
|
this._calls$ = this.createBehaviorSubject({});
|
|
8554
9107
|
this._iceServers$ = this.createBehaviorSubject([]);
|
|
8555
9108
|
attachManager.setSession(this);
|
|
@@ -8562,11 +9115,11 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
8562
9115
|
get incomingCalls() {
|
|
8563
9116
|
return Object.values(this._calls$.value).filter((call) => call.direction === "inbound");
|
|
8564
9117
|
}
|
|
8565
|
-
get
|
|
8566
|
-
return this.
|
|
9118
|
+
get userInfo$() {
|
|
9119
|
+
return this._userInfo$.asObservable();
|
|
8567
9120
|
}
|
|
8568
|
-
get
|
|
8569
|
-
return this.
|
|
9121
|
+
get userInfo() {
|
|
9122
|
+
return this._userInfo$.value;
|
|
8570
9123
|
}
|
|
8571
9124
|
get calls$() {
|
|
8572
9125
|
return this.cachedObservable("calls$", () => this._calls$.pipe((0, rxjs.map)((calls) => Object.values(calls))));
|
|
@@ -8871,7 +9424,6 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
8871
9424
|
displayDirection: invite.display_direction,
|
|
8872
9425
|
userVariables: invite.userVariables
|
|
8873
9426
|
});
|
|
8874
|
-
await (0, rxjs.firstValueFrom)(callSession.status$);
|
|
8875
9427
|
this._calls$.next({
|
|
8876
9428
|
[`${callSession.id}`]: callSession,
|
|
8877
9429
|
...this._calls$.value
|
|
@@ -8892,7 +9444,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
8892
9444
|
logger$8.debug(`[Session] Verto attach for existing call ${callID}, deferring to per-call handler`);
|
|
8893
9445
|
return;
|
|
8894
9446
|
}
|
|
8895
|
-
const storedOptions = this.attachManager.consumePendingAttachment(callID);
|
|
9447
|
+
const storedOptions = await this.attachManager.consumePendingAttachment(callID);
|
|
8896
9448
|
logger$8.debug(`[Session] Creating reattached call for callID: ${callID}`);
|
|
8897
9449
|
const callSession = await this.createCall({
|
|
8898
9450
|
nodeId: attach.node_id,
|
|
@@ -8904,7 +9456,6 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
8904
9456
|
reattach: true,
|
|
8905
9457
|
...storedOptions
|
|
8906
9458
|
});
|
|
8907
|
-
await (0, rxjs.firstValueFrom)(callSession.status$);
|
|
8908
9459
|
this._calls$.next({
|
|
8909
9460
|
[`${callSession.id}`]: callSession,
|
|
8910
9461
|
...this._calls$.value
|
|
@@ -9013,22 +9564,22 @@ var ConversationMessageCollection = class extends EntityCollection {
|
|
|
9013
9564
|
}
|
|
9014
9565
|
};
|
|
9015
9566
|
var ConversationsManager = class {
|
|
9016
|
-
constructor(clientSession, http,
|
|
9567
|
+
constructor(clientSession, http, getUserAddressId, onError) {
|
|
9017
9568
|
this.clientSession = clientSession;
|
|
9018
9569
|
this.http = http;
|
|
9019
|
-
this.
|
|
9570
|
+
this.getUserAddressId = getUserAddressId;
|
|
9020
9571
|
this.onError = onError;
|
|
9021
9572
|
this.groupIds = /* @__PURE__ */ new Map();
|
|
9022
9573
|
}
|
|
9023
9574
|
async join(addressId) {
|
|
9024
|
-
const
|
|
9575
|
+
const userFromAddressId = this.getUserAddressId();
|
|
9025
9576
|
try {
|
|
9026
9577
|
const response = await this.http.request({
|
|
9027
9578
|
...POST_PARAMS,
|
|
9028
9579
|
url: `/api/fabric/conversations/join`,
|
|
9029
9580
|
body: JSON.stringify({
|
|
9030
|
-
from_fabric_address_id:
|
|
9031
|
-
fabric_address_ids: [addressId,
|
|
9581
|
+
from_fabric_address_id: userFromAddressId,
|
|
9582
|
+
fabric_address_ids: [addressId, userFromAddressId]
|
|
9032
9583
|
})
|
|
9033
9584
|
});
|
|
9034
9585
|
if (response.ok && !!response.body) {
|
|
@@ -9050,14 +9601,14 @@ var ConversationsManager = class {
|
|
|
9050
9601
|
}
|
|
9051
9602
|
async sendText(text, destinationAddressId) {
|
|
9052
9603
|
const groupId = this.groupIds.get(destinationAddressId) ?? await this.join(destinationAddressId);
|
|
9053
|
-
const
|
|
9604
|
+
const userFromAddressId = this.getUserAddressId();
|
|
9054
9605
|
try {
|
|
9055
9606
|
if ((await this.http.request({
|
|
9056
9607
|
...POST_PARAMS,
|
|
9057
9608
|
url: "/api/fabric/messages",
|
|
9058
9609
|
body: JSON.stringify({
|
|
9059
9610
|
group_id: groupId,
|
|
9060
|
-
from_fabric_address_id:
|
|
9611
|
+
from_fabric_address_id: userFromAddressId,
|
|
9061
9612
|
text
|
|
9062
9613
|
})
|
|
9063
9614
|
})).ok) return;
|
|
@@ -9129,17 +9680,17 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
9129
9680
|
return this._effectiveExpireIn;
|
|
9130
9681
|
}
|
|
9131
9682
|
/**
|
|
9132
|
-
* Activates the Client Bound SAT flow when the
|
|
9683
|
+
* Activates the Client Bound SAT flow when the user's token has
|
|
9133
9684
|
* `sat:refresh` scope.
|
|
9134
9685
|
*
|
|
9135
9686
|
* Steps:
|
|
9136
|
-
* 1. Check
|
|
9687
|
+
* 1. Check user's `sat_claims` for `sat:refresh` scope
|
|
9137
9688
|
* 2. Call `/api/fabric/subscriber/devices/token` with a DPoP proof
|
|
9138
9689
|
* 3. Reauthenticate the session with the Client Bound SAT + DPoP proof
|
|
9139
9690
|
* 4. Emit token to trigger the reactive refresh pipeline
|
|
9140
9691
|
*/
|
|
9141
|
-
async activate(
|
|
9142
|
-
const { satClaims } =
|
|
9692
|
+
async activate(user, session, updateCredential) {
|
|
9693
|
+
const { satClaims } = user;
|
|
9143
9694
|
if (!satClaims?.scope?.includes(SAT_REFRESH_SCOPE)) {
|
|
9144
9695
|
logger$6.debug("[DeviceToken] No sat:refresh scope, skipping Client Bound SAT activation");
|
|
9145
9696
|
return;
|
|
@@ -9422,8 +9973,8 @@ const isEmptyArray = (a) => {
|
|
|
9422
9973
|
};
|
|
9423
9974
|
|
|
9424
9975
|
//#endregion
|
|
9425
|
-
//#region src/utils/
|
|
9426
|
-
const
|
|
9976
|
+
//#region src/utils/warmup.ts
|
|
9977
|
+
const warmup = (observable) => {
|
|
9427
9978
|
observable.pipe((0, rxjs.take)(1)).subscribe();
|
|
9428
9979
|
};
|
|
9429
9980
|
|
|
@@ -9471,7 +10022,7 @@ var DirectoryManager = class extends Destroyable {
|
|
|
9471
10022
|
return address;
|
|
9472
10023
|
}));
|
|
9473
10024
|
if (observable) {
|
|
9474
|
-
|
|
10025
|
+
warmup(observable);
|
|
9475
10026
|
this._observableRegistry.set(id, observable);
|
|
9476
10027
|
}
|
|
9477
10028
|
this._addressesInstances.set(id, address);
|
|
@@ -9623,10 +10174,9 @@ var WebSocketController = class WebSocketController extends Destroyable {
|
|
|
9623
10174
|
else this._status$.next("disconnected");
|
|
9624
10175
|
}
|
|
9625
10176
|
reconnect() {
|
|
9626
|
-
|
|
9627
|
-
|
|
9628
|
-
|
|
9629
|
-
} else this._status$.next("disconnected");
|
|
10177
|
+
this.shouldReconnect = true;
|
|
10178
|
+
this._status$.next("reconnecting");
|
|
10179
|
+
this.scheduleReconnection();
|
|
9630
10180
|
}
|
|
9631
10181
|
send(data) {
|
|
9632
10182
|
if (this._status$.value === "connected" && this.socket?.readyState === 1) {
|
|
@@ -9983,7 +10533,7 @@ var SignalWire = class extends Destroyable {
|
|
|
9983
10533
|
constructor(credentialProvider, options = {}) {
|
|
9984
10534
|
super();
|
|
9985
10535
|
this.preferences = new ClientPreferences();
|
|
9986
|
-
this.
|
|
10536
|
+
this._user$ = this.createBehaviorSubject(void 0);
|
|
9987
10537
|
this._directory$ = this.createBehaviorSubject(void 0);
|
|
9988
10538
|
this._isConnected$ = this.createBehaviorSubject(false);
|
|
9989
10539
|
this._isRegistered$ = this.createBehaviorSubject(false);
|
|
@@ -10123,7 +10673,7 @@ var SignalWire = class extends Destroyable {
|
|
|
10123
10673
|
if (this._deps.persistSession) this._deps.storage.setItem("sw:cached_credential", credential, "local");
|
|
10124
10674
|
}
|
|
10125
10675
|
async init() {
|
|
10126
|
-
this.
|
|
10676
|
+
this._user$.next(new User(this._deps.http));
|
|
10127
10677
|
if (!this._options.skipConnection) await this.connect();
|
|
10128
10678
|
if (!this._options.reconnectAttachedCalls && this._attachManager) await this._attachManager.flush();
|
|
10129
10679
|
if (!this._options.skipRegister) try {
|
|
@@ -10185,14 +10735,15 @@ var SignalWire = class extends Destroyable {
|
|
|
10185
10735
|
* `'reconnecting'`, `'disconnecting'`, or `'disconnected'`.
|
|
10186
10736
|
*/
|
|
10187
10737
|
async connect() {
|
|
10738
|
+
await this.teardownTransportAndSession();
|
|
10188
10739
|
try {
|
|
10189
|
-
const
|
|
10190
|
-
if (!
|
|
10191
|
-
if (!await (0, rxjs.firstValueFrom)(
|
|
10192
|
-
this._deps.
|
|
10740
|
+
const user = this._user$.value;
|
|
10741
|
+
if (!user) throw new require_operators.UnexpectedError("User not initialized before connect");
|
|
10742
|
+
if (!await (0, rxjs.firstValueFrom)(user.fetched$)) throw new require_operators.UnexpectedError("Failed to fetch user information - fetched$ emitted false");
|
|
10743
|
+
this._deps.user = user;
|
|
10193
10744
|
} catch (error) {
|
|
10194
|
-
logger$1.error(`[SignalWire] Failed to fetch
|
|
10195
|
-
throw new require_operators.UnexpectedError("Error fetching
|
|
10745
|
+
logger$1.error(`[SignalWire] Failed to fetch user information: ${error instanceof Error ? error.message : "Unknown error"}. This usually means the user token is invalid or expired.`);
|
|
10746
|
+
throw new require_operators.UnexpectedError("Error fetching user information", { cause: error });
|
|
10196
10747
|
}
|
|
10197
10748
|
const errorHandler = (error) => {
|
|
10198
10749
|
this._errors$.next(error);
|
|
@@ -10226,7 +10777,7 @@ var SignalWire = class extends Destroyable {
|
|
|
10226
10777
|
logger$1.debug("[SignalWire] Developer refresh disabled — Client Bound SAT activation starting");
|
|
10227
10778
|
}
|
|
10228
10779
|
this._deviceTokenManager = new DeviceTokenManager(this._dpopManager, this._deps.http, (error) => this._errors$.next(error), () => this._deps.credential);
|
|
10229
|
-
await this._deviceTokenManager.activate(this._deps.
|
|
10780
|
+
await this._deviceTokenManager.activate(this._deps.user, this._clientSession, (cred) => {
|
|
10230
10781
|
this._deps.credential = {
|
|
10231
10782
|
...this._deps.credential,
|
|
10232
10783
|
...cred
|
|
@@ -10236,7 +10787,7 @@ var SignalWire = class extends Destroyable {
|
|
|
10236
10787
|
this.subscribeTo(this._clientSession.authenticated$.pipe((0, rxjs.skip)(1), (0, rxjs.filter)(Boolean)), async () => {
|
|
10237
10788
|
try {
|
|
10238
10789
|
if (this._deviceTokenManager) {
|
|
10239
|
-
await this._deviceTokenManager.activate(this._deps.
|
|
10790
|
+
await this._deviceTokenManager.activate(this._deps.user, this._clientSession, (cred) => {
|
|
10240
10791
|
this._deps.credential = {
|
|
10241
10792
|
...this._deps.credential,
|
|
10242
10793
|
...cred
|
|
@@ -10249,15 +10800,15 @@ var SignalWire = class extends Destroyable {
|
|
|
10249
10800
|
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
10250
10801
|
}
|
|
10251
10802
|
try {
|
|
10252
|
-
logger$1.debug("[SignalWire] Re-registering
|
|
10803
|
+
logger$1.debug("[SignalWire] Re-registering user after reconnect");
|
|
10253
10804
|
await this.register();
|
|
10254
|
-
logger$1.debug("[SignalWire]
|
|
10805
|
+
logger$1.debug("[SignalWire] User re-registered successfully after reconnect");
|
|
10255
10806
|
} catch (error) {
|
|
10256
10807
|
logger$1.error("[SignalWire] Re-registration failed after reconnect:", error);
|
|
10257
10808
|
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
10258
10809
|
}
|
|
10259
10810
|
});
|
|
10260
|
-
const conversationManager = new ConversationsManager(this._clientSession, this._deps.http, () => this._deps.
|
|
10811
|
+
const conversationManager = new ConversationsManager(this._clientSession, this._deps.http, () => this._deps.getUserFromAddressId(), errorHandler);
|
|
10261
10812
|
const directory = new DirectoryManager(this._deps.http, this._clientSession, conversationManager, errorHandler);
|
|
10262
10813
|
this._directory$.next(directory);
|
|
10263
10814
|
this._clientSession.setDirectory(directory);
|
|
@@ -10268,22 +10819,22 @@ var SignalWire = class extends Destroyable {
|
|
|
10268
10819
|
});
|
|
10269
10820
|
}
|
|
10270
10821
|
/**
|
|
10271
|
-
* Observable that emits the {@link
|
|
10822
|
+
* Observable that emits the {@link User} profile once fetched,
|
|
10272
10823
|
* or `undefined` before authentication completes.
|
|
10273
10824
|
*
|
|
10274
10825
|
* @example
|
|
10275
10826
|
* ```ts
|
|
10276
|
-
* client.
|
|
10277
|
-
* if (
|
|
10827
|
+
* client.user$.subscribe(u => {
|
|
10828
|
+
* if (u) console.log('Logged in as', u.email);
|
|
10278
10829
|
* });
|
|
10279
10830
|
* ```
|
|
10280
10831
|
*/
|
|
10281
|
-
get
|
|
10282
|
-
return this.deferEmission(this.
|
|
10832
|
+
get user$() {
|
|
10833
|
+
return this.deferEmission(this._user$.asObservable());
|
|
10283
10834
|
}
|
|
10284
|
-
/** Current
|
|
10285
|
-
get
|
|
10286
|
-
return this.
|
|
10835
|
+
/** Current user snapshot, or `undefined` if not yet authenticated. */
|
|
10836
|
+
get user() {
|
|
10837
|
+
return this._user$.value;
|
|
10287
10838
|
}
|
|
10288
10839
|
/**
|
|
10289
10840
|
* Observable that emits the {@link Directory} instance once the client is connected,
|
|
@@ -10307,11 +10858,11 @@ var SignalWire = class extends Destroyable {
|
|
|
10307
10858
|
get directory() {
|
|
10308
10859
|
return this._directory$.value;
|
|
10309
10860
|
}
|
|
10310
|
-
/** Observable that emits when the
|
|
10861
|
+
/** Observable that emits when the user registration state changes. */
|
|
10311
10862
|
get isRegistered$() {
|
|
10312
10863
|
return this.deferEmission(this._isRegistered$.asObservable());
|
|
10313
10864
|
}
|
|
10314
|
-
/** Whether the
|
|
10865
|
+
/** Whether the user is currently registered. */
|
|
10315
10866
|
get isRegistered() {
|
|
10316
10867
|
return this._isRegistered$.value;
|
|
10317
10868
|
}
|
|
@@ -10416,15 +10967,35 @@ var SignalWire = class extends Destroyable {
|
|
|
10416
10967
|
this._refreshTimerId = void 0;
|
|
10417
10968
|
}
|
|
10418
10969
|
this._diagnosticsCollector?.record("connection", "disconnected");
|
|
10419
|
-
await this.
|
|
10420
|
-
this._clientSession.destroy();
|
|
10970
|
+
await this.teardownTransportAndSession();
|
|
10421
10971
|
this._isConnected$.next(false);
|
|
10422
10972
|
}
|
|
10973
|
+
/**
|
|
10974
|
+
* Tear down the current transport / session / attach manager. Safe to call
|
|
10975
|
+
* when nothing has been initialized yet (e.g. first connect()).
|
|
10976
|
+
*/
|
|
10977
|
+
async teardownTransportAndSession() {
|
|
10978
|
+
const session = this._clientSession;
|
|
10979
|
+
const transport = this._transport;
|
|
10980
|
+
if (session) {
|
|
10981
|
+
try {
|
|
10982
|
+
await session.disconnect();
|
|
10983
|
+
} catch (error) {
|
|
10984
|
+
logger$1.warn("[SignalWire] Error disconnecting previous session:", error);
|
|
10985
|
+
}
|
|
10986
|
+
session.destroy();
|
|
10987
|
+
}
|
|
10988
|
+
if (transport) transport.destroy();
|
|
10989
|
+
this._clientSession = void 0;
|
|
10990
|
+
this._publicSession = void 0;
|
|
10991
|
+
this._transport = void 0;
|
|
10992
|
+
this._attachManager = void 0;
|
|
10993
|
+
}
|
|
10423
10994
|
async waitAuthentication() {
|
|
10424
10995
|
await (0, rxjs.firstValueFrom)(this.ready$.pipe((0, rxjs.filter)((ready$1) => ready$1 === true)));
|
|
10425
10996
|
}
|
|
10426
10997
|
/**
|
|
10427
|
-
* Registers the
|
|
10998
|
+
* Registers the user as online to receive inbound calls and events.
|
|
10428
10999
|
*
|
|
10429
11000
|
* Waits for authentication to complete before sending the registration.
|
|
10430
11001
|
* If the initial attempt fails, reauthentication is attempted automatically.
|
|
@@ -10439,26 +11010,31 @@ var SignalWire = class extends Destroyable {
|
|
|
10439
11010
|
params: {}
|
|
10440
11011
|
}));
|
|
10441
11012
|
this._isRegistered$.next(true);
|
|
11013
|
+
return;
|
|
10442
11014
|
} catch (error) {
|
|
10443
|
-
|
|
10444
|
-
|
|
11015
|
+
if (!this._deps.credential.token) {
|
|
11016
|
+
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
11017
|
+
throw error;
|
|
11018
|
+
}
|
|
11019
|
+
logger$1.debug("[SignalWire] Failed to register user, trying reauthentication...");
|
|
11020
|
+
try {
|
|
11021
|
+
await this._clientSession.reauthenticate(this._deps.credential.token);
|
|
10445
11022
|
logger$1.debug("[SignalWire] Reauthentication successful, retrying register()");
|
|
10446
11023
|
await this._transport.execute(RPCExecute({
|
|
10447
11024
|
method: "subscriber.online",
|
|
10448
11025
|
params: {}
|
|
10449
11026
|
}));
|
|
10450
11027
|
this._isRegistered$.next(true);
|
|
10451
|
-
}
|
|
11028
|
+
} catch (reauthError) {
|
|
10452
11029
|
logger$1.error("[SignalWire] Reauthentication failed during register():", reauthError);
|
|
10453
|
-
const registerError = new require_operators.InvalidCredentialsError("Failed to register
|
|
11030
|
+
const registerError = new require_operators.InvalidCredentialsError("Failed to register user, and reauthentication attempt also failed. Please check your credentials.", { cause: reauthError instanceof Error ? reauthError : new Error(String(reauthError), { cause: reauthError }) });
|
|
10454
11031
|
this._errors$.next(registerError);
|
|
10455
|
-
|
|
10456
|
-
|
|
10457
|
-
throw error;
|
|
11032
|
+
throw registerError;
|
|
11033
|
+
}
|
|
10458
11034
|
}
|
|
10459
11035
|
}
|
|
10460
11036
|
/**
|
|
10461
|
-
* Unregisters the
|
|
11037
|
+
* Unregisters the user, going offline for inbound calls.
|
|
10462
11038
|
*
|
|
10463
11039
|
* The WebSocket connection remains open; use {@link disconnect} to fully close it.
|
|
10464
11040
|
*/
|
|
@@ -10470,7 +11046,7 @@ var SignalWire = class extends Destroyable {
|
|
|
10470
11046
|
}));
|
|
10471
11047
|
this._isRegistered$.next(false);
|
|
10472
11048
|
} catch (error) {
|
|
10473
|
-
logger$1.error("[SignalWire] Failed to unregister
|
|
11049
|
+
logger$1.error("[SignalWire] Failed to unregister user:", error);
|
|
10474
11050
|
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
10475
11051
|
throw error;
|
|
10476
11052
|
}
|
|
@@ -10610,6 +11186,36 @@ var SignalWire = class extends Destroyable {
|
|
|
10610
11186
|
selectAudioOutputDevice(device) {
|
|
10611
11187
|
this._deviceController.selectAudioOutputDevice(device);
|
|
10612
11188
|
}
|
|
11189
|
+
/**
|
|
11190
|
+
* Apply the currently selected audio output device to an HTMLMediaElement
|
|
11191
|
+
* (e.g. the `<audio>` or `<video>` element the consumer attached the
|
|
11192
|
+
* remote stream to). Uses `HTMLMediaElement.setSinkId` under the hood.
|
|
11193
|
+
* Returns a `Promise<boolean>`: `true` if the sink was applied,
|
|
11194
|
+
* `false` if the browser doesn't support `setSinkId` or no device is
|
|
11195
|
+
* selected.
|
|
11196
|
+
*
|
|
11197
|
+
* @example
|
|
11198
|
+
* ```ts
|
|
11199
|
+
* audioEl.srcObject = call.remoteStream;
|
|
11200
|
+
* await client.applySelectedAudioOutputDevice(audioEl);
|
|
11201
|
+
* ```
|
|
11202
|
+
*/
|
|
11203
|
+
async applySelectedAudioOutputDevice(element) {
|
|
11204
|
+
const device = this._deviceController.selectedAudioOutputDevice;
|
|
11205
|
+
if (!device?.deviceId) return false;
|
|
11206
|
+
const withSink = element;
|
|
11207
|
+
if (typeof withSink.setSinkId !== "function") {
|
|
11208
|
+
logger$1.warn("[SignalWire] setSinkId not supported on this element / browser");
|
|
11209
|
+
return false;
|
|
11210
|
+
}
|
|
11211
|
+
try {
|
|
11212
|
+
await withSink.setSinkId(device.deviceId);
|
|
11213
|
+
return true;
|
|
11214
|
+
} catch (error) {
|
|
11215
|
+
logger$1.warn("[SignalWire] Failed to apply audio output device:", error);
|
|
11216
|
+
return false;
|
|
11217
|
+
}
|
|
11218
|
+
}
|
|
10613
11219
|
/** Starts monitoring for media device changes (connect/disconnect). */
|
|
10614
11220
|
enableDeviceMonitoring() {
|
|
10615
11221
|
this._deviceController.enableDeviceMonitoring();
|
|
@@ -10790,6 +11396,7 @@ var EmbedTokenCredentialProvider = class {
|
|
|
10790
11396
|
try {
|
|
10791
11397
|
const response = await fetch(url, {
|
|
10792
11398
|
method: "POST",
|
|
11399
|
+
headers: { "Content-Type": "application/json" },
|
|
10793
11400
|
body: JSON.stringify({ token: this.embedToken }),
|
|
10794
11401
|
signal: controller.signal
|
|
10795
11402
|
});
|
|
@@ -10902,6 +11509,7 @@ exports.ClientPreferences = ClientPreferences;
|
|
|
10902
11509
|
exports.CollectionFetchError = require_operators.CollectionFetchError;
|
|
10903
11510
|
exports.DPoPInitError = require_operators.DPoPInitError;
|
|
10904
11511
|
exports.DeviceTokenError = require_operators.DeviceTokenError;
|
|
11512
|
+
exports.EmbedTokenCredentialProvider = EmbedTokenCredentialProvider;
|
|
10905
11513
|
exports.InvalidCredentialsError = require_operators.InvalidCredentialsError;
|
|
10906
11514
|
exports.MediaTrackError = require_operators.MediaTrackError;
|
|
10907
11515
|
exports.MessageParseError = require_operators.MessageParseError;
|
|
@@ -10913,12 +11521,13 @@ exports.SelfCapabilities = SelfCapabilities;
|
|
|
10913
11521
|
exports.SelfParticipant = SelfParticipant;
|
|
10914
11522
|
exports.SignalWire = SignalWire;
|
|
10915
11523
|
exports.StaticCredentialProvider = StaticCredentialProvider;
|
|
10916
|
-
exports.Subscriber = Subscriber;
|
|
10917
11524
|
exports.TokenRefreshError = require_operators.TokenRefreshError;
|
|
10918
11525
|
exports.UnexpectedError = require_operators.UnexpectedError;
|
|
11526
|
+
exports.User = User;
|
|
10919
11527
|
exports.VertoPongError = require_operators.VertoPongError;
|
|
10920
11528
|
exports.WebRTCCall = WebRTCCall;
|
|
10921
11529
|
exports.embeddableCall = embeddableCall;
|
|
11530
|
+
exports.getLogger = require_operators.getLogger;
|
|
10922
11531
|
exports.isSelfParticipant = isSelfParticipant;
|
|
10923
11532
|
exports.ready = ready;
|
|
10924
11533
|
exports.setDebugOptions = require_operators.setDebugOptions;
|