@signalwire/js 4.0.0-dev-20260421201955 → 4.0.0-dev-20260422003445
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 +858 -361
- package/dist/browser.mjs.map +1 -1
- package/dist/browser.umd.js +858 -361
- package/dist/browser.umd.js.map +1 -1
- package/dist/index.cjs +729 -234
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +273 -9
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +273 -9
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +730 -235
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -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" }
|
|
@@ -195,7 +195,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
195
195
|
this._responses$.next(response);
|
|
196
196
|
return response;
|
|
197
197
|
} catch (error) {
|
|
198
|
-
logger$
|
|
198
|
+
logger$30.error("[HTTPRequestController] Request error:", error);
|
|
199
199
|
this._status$.next("error");
|
|
200
200
|
const err = error instanceof Error ? error : new Error("HTTP request failed", { cause: error });
|
|
201
201
|
this._errors$.next(err);
|
|
@@ -222,7 +222,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
222
222
|
const url = this.buildURL(request.url);
|
|
223
223
|
const headers = this.buildHeaders(request.headers);
|
|
224
224
|
const timeout$4 = request.timeout ?? this.requestTimeout;
|
|
225
|
-
logger$
|
|
225
|
+
logger$30.debug("[HTTPRequestController] Executing request:", {
|
|
226
226
|
method: request.method,
|
|
227
227
|
url,
|
|
228
228
|
headers: Object.keys(headers).reduce((acc, key) => {
|
|
@@ -242,7 +242,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
242
242
|
});
|
|
243
243
|
clearTimeout(timeoutId);
|
|
244
244
|
const httpResponse = await this.convertResponse(response);
|
|
245
|
-
logger$
|
|
245
|
+
logger$30.debug("[HTTPRequestController] Response received:", {
|
|
246
246
|
status: response.status,
|
|
247
247
|
statusText: response.statusText,
|
|
248
248
|
headers: [...response.headers.entries()],
|
|
@@ -252,7 +252,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
252
252
|
} catch (error) {
|
|
253
253
|
clearTimeout(timeoutId);
|
|
254
254
|
if (error instanceof Error && error.name === "AbortError") throw new require_operators.RequestTimeoutError(`Request timeout after ${timeout$4}ms`, { cause: error });
|
|
255
|
-
logger$
|
|
255
|
+
logger$30.error("[HTTPRequestController] Request failed:", error);
|
|
256
256
|
throw error;
|
|
257
257
|
}
|
|
258
258
|
}
|
|
@@ -266,8 +266,8 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
266
266
|
const credential = this.getCredential();
|
|
267
267
|
if (credential.token) {
|
|
268
268
|
headers.Authorization = `Bearer ${credential.token}`;
|
|
269
|
-
logger$
|
|
270
|
-
} 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");
|
|
271
271
|
return headers;
|
|
272
272
|
}
|
|
273
273
|
/**
|
|
@@ -485,6 +485,12 @@ const ICE_GATHERING_COMPLETE_TIMEOUT_MS = 1e4;
|
|
|
485
485
|
const PEER_CONNECTION_RECOVERY_WAIT_MS = 5e3;
|
|
486
486
|
/** Polling interval in ms while waiting for RTCPeerConnection.connectionState to transition. */
|
|
487
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;
|
|
488
494
|
/** Whether to persist device selections to storage by default. */
|
|
489
495
|
const DEFAULT_PERSIST_DEVICE_SELECTION = true;
|
|
490
496
|
/** Whether to auto-apply device changes to active calls by default. */
|
|
@@ -533,7 +539,7 @@ function fromMsToSec(milliseconds) {
|
|
|
533
539
|
|
|
534
540
|
//#endregion
|
|
535
541
|
//#region src/containers/PreferencesContainer.ts
|
|
536
|
-
const logger$
|
|
542
|
+
const logger$29 = require_operators.getLogger();
|
|
537
543
|
var PreferencesContainer = class PreferencesContainer {
|
|
538
544
|
static get instance() {
|
|
539
545
|
this._instance ??= new PreferencesContainer();
|
|
@@ -1195,7 +1201,7 @@ var ClientPreferences = class {
|
|
|
1195
1201
|
if (!this._storage) return;
|
|
1196
1202
|
const data = collectStoredPreferences();
|
|
1197
1203
|
this._storage.setItem(PREFERENCES_STORAGE_KEY, data, "local").catch((error) => {
|
|
1198
|
-
logger$
|
|
1204
|
+
logger$29.error(`[ClientPreferences] Failed to save preferences: ${String(error)}`);
|
|
1199
1205
|
});
|
|
1200
1206
|
}
|
|
1201
1207
|
/** Loads preferences from storage and applies them to the container. */
|
|
@@ -1204,7 +1210,7 @@ var ClientPreferences = class {
|
|
|
1204
1210
|
this._storage.getItem(PREFERENCES_STORAGE_KEY, "local").then((stored) => {
|
|
1205
1211
|
if (stored) applyStoredPreferences(stored);
|
|
1206
1212
|
}).catch((error) => {
|
|
1207
|
-
logger$
|
|
1213
|
+
logger$29.error(`[ClientPreferences] Failed to load preferences: ${String(error)}`);
|
|
1208
1214
|
});
|
|
1209
1215
|
}
|
|
1210
1216
|
};
|
|
@@ -1225,7 +1231,7 @@ function toError(value) {
|
|
|
1225
1231
|
|
|
1226
1232
|
//#endregion
|
|
1227
1233
|
//#region src/controllers/NavigatorDeviceController.ts
|
|
1228
|
-
const logger$
|
|
1234
|
+
const logger$28 = require_operators.getLogger();
|
|
1229
1235
|
/** Maps a device kind to its storage key. */
|
|
1230
1236
|
const DEVICE_STORAGE_KEYS = {
|
|
1231
1237
|
audioinput: DEVICE_STORAGE_KEY_AUDIO_INPUT,
|
|
@@ -1247,7 +1253,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1247
1253
|
super();
|
|
1248
1254
|
this.webRTCApiProvider = webRTCApiProvider;
|
|
1249
1255
|
this.deviceChangeHandler = () => {
|
|
1250
|
-
logger$
|
|
1256
|
+
logger$28.debug("[DeviceController] Device change detected");
|
|
1251
1257
|
this.enumerateDevices();
|
|
1252
1258
|
};
|
|
1253
1259
|
this._devicesState$ = this.createBehaviorSubject(initialDevicesState);
|
|
@@ -1312,13 +1318,13 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1312
1318
|
return this.cachedObservable("videoInputDevices$", () => this._devicesState$.pipe((0, rxjs.map)((state) => state.videoinput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$)));
|
|
1313
1319
|
}
|
|
1314
1320
|
get selectedAudioInputDevice$() {
|
|
1315
|
-
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))));
|
|
1316
1322
|
}
|
|
1317
1323
|
get selectedAudioOutputDevice$() {
|
|
1318
|
-
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))));
|
|
1319
1325
|
}
|
|
1320
1326
|
get selectedVideoInputDevice$() {
|
|
1321
|
-
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))));
|
|
1322
1328
|
}
|
|
1323
1329
|
get selectedAudioInputDevice() {
|
|
1324
1330
|
if (this._audioInputDisabled$.value) return null;
|
|
@@ -1393,7 +1399,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1393
1399
|
if (device) this.persistDeviceSelection("audioinput", device);
|
|
1394
1400
|
}
|
|
1395
1401
|
selectVideoInputDevice(device) {
|
|
1396
|
-
logger$
|
|
1402
|
+
logger$28.debug("[DeviceController] Setting selected video input device:", device);
|
|
1397
1403
|
if (this._videoInputDisabled$.value && device) this._videoInputDisabled$.next(false);
|
|
1398
1404
|
const previous = this._selectedDevicesState$.value.videoinput;
|
|
1399
1405
|
if (previous && previous.deviceId !== device?.deviceId) this._deviceHistory.push("videoinput", previous);
|
|
@@ -1450,7 +1456,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1450
1456
|
}
|
|
1451
1457
|
const fromHistory = this._deviceHistory.findInHistory(kind, devices);
|
|
1452
1458
|
if (fromHistory) {
|
|
1453
|
-
logger$
|
|
1459
|
+
logger$28.debug(`[DeviceController] Device disappeared, falling back to history: ${fromHistory.label}`);
|
|
1454
1460
|
this.emitDeviceRecovered(kind, selected, fromHistory, "device_disconnected");
|
|
1455
1461
|
return fromHistory;
|
|
1456
1462
|
}
|
|
@@ -1503,7 +1509,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1503
1509
|
try {
|
|
1504
1510
|
await this._storageManager.setItem(DEVICE_STORAGE_KEYS[kind], stored, "local");
|
|
1505
1511
|
} catch (error) {
|
|
1506
|
-
logger$
|
|
1512
|
+
logger$28.error(`[DeviceController] Failed to persist device selection for ${kind}:`, error);
|
|
1507
1513
|
}
|
|
1508
1514
|
}
|
|
1509
1515
|
async loadPersistedDevices() {
|
|
@@ -1519,7 +1525,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1519
1525
|
[kind]: stored
|
|
1520
1526
|
};
|
|
1521
1527
|
} catch (error) {
|
|
1522
|
-
logger$
|
|
1528
|
+
logger$28.error(`[DeviceController] Failed to load persisted device for ${kind}:`, error);
|
|
1523
1529
|
}
|
|
1524
1530
|
}
|
|
1525
1531
|
/** Clears device history, persisted selections, and re-enumerates devices. */
|
|
@@ -1537,7 +1543,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1537
1543
|
this.disableDeviceMonitoring();
|
|
1538
1544
|
this.webRTCApiProvider.mediaDevices.addEventListener("devicechange", this.deviceChangeHandler);
|
|
1539
1545
|
if (PreferencesContainer.instance.devicePollingInterval > 0) this._devicesPoolingSubscription = (0, rxjs.interval)(PreferencesContainer.instance.devicePollingInterval).subscribe(() => {
|
|
1540
|
-
logger$
|
|
1546
|
+
logger$28.debug("[DeviceController] Polling devices due to interval");
|
|
1541
1547
|
this.enumerateDevices();
|
|
1542
1548
|
});
|
|
1543
1549
|
this.enumerateDevices();
|
|
@@ -1563,13 +1569,13 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1563
1569
|
videoinput: []
|
|
1564
1570
|
});
|
|
1565
1571
|
this._devicesState$.next(devicesByKind);
|
|
1566
|
-
logger$
|
|
1572
|
+
logger$28.debug("[DeviceController] Devices enumerated:", {
|
|
1567
1573
|
audioInputs: devicesByKind.audioinput.length,
|
|
1568
1574
|
audioOutputs: devicesByKind.audiooutput.length,
|
|
1569
1575
|
videoInputs: devicesByKind.videoinput.length
|
|
1570
1576
|
});
|
|
1571
1577
|
} catch (error) {
|
|
1572
|
-
logger$
|
|
1578
|
+
logger$28.error("[DeviceController] Failed to enumerate devices:", error);
|
|
1573
1579
|
this._errors$.next(toError(error));
|
|
1574
1580
|
}
|
|
1575
1581
|
}
|
|
@@ -1585,7 +1591,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1585
1591
|
stream.getTracks().forEach((t) => t.stop());
|
|
1586
1592
|
return capabilities;
|
|
1587
1593
|
} catch (error) {
|
|
1588
|
-
logger$
|
|
1594
|
+
logger$28.error("[DeviceController] Failed to get device capabilities:", error);
|
|
1589
1595
|
this._errors$.next(toError(error));
|
|
1590
1596
|
throw error;
|
|
1591
1597
|
}
|
|
@@ -1836,7 +1842,7 @@ var DependencyContainer = class {
|
|
|
1836
1842
|
|
|
1837
1843
|
//#endregion
|
|
1838
1844
|
//#region src/controllers/CryptoController.ts
|
|
1839
|
-
const logger$
|
|
1845
|
+
const logger$27 = require_operators.getLogger();
|
|
1840
1846
|
const DPOP_DB_NAME = "sw-dpop";
|
|
1841
1847
|
const DPOP_DB_VERSION = 1;
|
|
1842
1848
|
const DPOP_STORE_NAME = "keys";
|
|
@@ -1895,7 +1901,7 @@ async function loadKeyPairFromDB() {
|
|
|
1895
1901
|
tx.oncomplete = () => db.close();
|
|
1896
1902
|
});
|
|
1897
1903
|
} catch (error) {
|
|
1898
|
-
logger$
|
|
1904
|
+
logger$27.warn("[DPoP] Failed to load key pair from IndexedDB:", error);
|
|
1899
1905
|
return null;
|
|
1900
1906
|
}
|
|
1901
1907
|
}
|
|
@@ -1915,7 +1921,7 @@ async function saveKeyPairToDB(keyPair) {
|
|
|
1915
1921
|
};
|
|
1916
1922
|
});
|
|
1917
1923
|
} catch (error) {
|
|
1918
|
-
logger$
|
|
1924
|
+
logger$27.warn("[DPoP] Failed to save key pair to IndexedDB:", error);
|
|
1919
1925
|
}
|
|
1920
1926
|
}
|
|
1921
1927
|
async function deleteKeyPairFromDB() {
|
|
@@ -1934,7 +1940,7 @@ async function deleteKeyPairFromDB() {
|
|
|
1934
1940
|
};
|
|
1935
1941
|
});
|
|
1936
1942
|
} catch (error) {
|
|
1937
|
-
logger$
|
|
1943
|
+
logger$27.warn("[DPoP] Failed to delete key pair from IndexedDB:", error);
|
|
1938
1944
|
}
|
|
1939
1945
|
}
|
|
1940
1946
|
/**
|
|
@@ -1994,13 +2000,13 @@ var CryptoController = class {
|
|
|
1994
2000
|
this._publicJwk = await crypto.subtle.exportKey("jwk", stored.publicKey);
|
|
1995
2001
|
this._fingerprint = await computeJwkThumbprint(this._publicJwk);
|
|
1996
2002
|
this._initialized = true;
|
|
1997
|
-
logger$
|
|
2003
|
+
logger$27.debug("[DPoP] Key pair restored from IndexedDB, fingerprint:", this._fingerprint);
|
|
1998
2004
|
return this._fingerprint;
|
|
1999
2005
|
} catch (error) {
|
|
2000
|
-
logger$
|
|
2006
|
+
logger$27.warn("[DPoP] Stored key pair unusable, generating new one:", error);
|
|
2001
2007
|
await deleteKeyPairFromDB();
|
|
2002
2008
|
}
|
|
2003
|
-
logger$
|
|
2009
|
+
logger$27.debug("[DPoP] Generating RSA key pair");
|
|
2004
2010
|
this._keyPair = await crypto.subtle.generateKey({
|
|
2005
2011
|
name: "RSASSA-PKCS1-v1_5",
|
|
2006
2012
|
modulusLength: 2048,
|
|
@@ -2015,7 +2021,7 @@ var CryptoController = class {
|
|
|
2015
2021
|
this._fingerprint = await computeJwkThumbprint(this._publicJwk);
|
|
2016
2022
|
this._initialized = true;
|
|
2017
2023
|
await saveKeyPairToDB(this._keyPair);
|
|
2018
|
-
logger$
|
|
2024
|
+
logger$27.debug("[DPoP] Key pair generated and persisted, fingerprint:", this._fingerprint);
|
|
2019
2025
|
return this._fingerprint;
|
|
2020
2026
|
}
|
|
2021
2027
|
/**
|
|
@@ -2081,7 +2087,7 @@ var CryptoController = class {
|
|
|
2081
2087
|
this._fingerprint = null;
|
|
2082
2088
|
this._initialized = false;
|
|
2083
2089
|
deleteKeyPairFromDB();
|
|
2084
|
-
logger$
|
|
2090
|
+
logger$27.debug("[DPoP] Controller destroyed");
|
|
2085
2091
|
}
|
|
2086
2092
|
get publicJwk() {
|
|
2087
2093
|
if (!this._publicJwk) throw new require_operators.DPoPInitError("CryptoController not initialized. Call init() first.");
|
|
@@ -2104,7 +2110,7 @@ var CryptoController = class {
|
|
|
2104
2110
|
|
|
2105
2111
|
//#endregion
|
|
2106
2112
|
//#region src/controllers/NetworkMonitor.ts
|
|
2107
|
-
const logger$
|
|
2113
|
+
const logger$26 = require_operators.getLogger();
|
|
2108
2114
|
/**
|
|
2109
2115
|
* Safely check whether we are running in a browser environment
|
|
2110
2116
|
* with `window` and the relevant event targets.
|
|
@@ -2161,7 +2167,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
2161
2167
|
}
|
|
2162
2168
|
attachListeners() {
|
|
2163
2169
|
if (!hasBrowserNetworkEvents()) {
|
|
2164
|
-
logger$
|
|
2170
|
+
logger$26.debug("NetworkMonitor: no browser environment detected, skipping event listeners");
|
|
2165
2171
|
return;
|
|
2166
2172
|
}
|
|
2167
2173
|
window.addEventListener("online", this._onOnline);
|
|
@@ -2169,7 +2175,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
2169
2175
|
const connection = getNetworkConnection();
|
|
2170
2176
|
if (connection) connection.addEventListener("change", this._onConnectionChange);
|
|
2171
2177
|
this._listenersAttached = true;
|
|
2172
|
-
logger$
|
|
2178
|
+
logger$26.debug("NetworkMonitor: event listeners attached");
|
|
2173
2179
|
}
|
|
2174
2180
|
removeListeners() {
|
|
2175
2181
|
if (!this._listenersAttached) return;
|
|
@@ -2180,10 +2186,10 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
2180
2186
|
if (connection) connection.removeEventListener("change", this._onConnectionChange);
|
|
2181
2187
|
}
|
|
2182
2188
|
this._listenersAttached = false;
|
|
2183
|
-
logger$
|
|
2189
|
+
logger$26.debug("NetworkMonitor: event listeners removed");
|
|
2184
2190
|
}
|
|
2185
2191
|
handleOnline() {
|
|
2186
|
-
logger$
|
|
2192
|
+
logger$26.info("NetworkMonitor: browser went online");
|
|
2187
2193
|
this._isOnline$.next(true);
|
|
2188
2194
|
this._networkChange$.next({
|
|
2189
2195
|
type: "online",
|
|
@@ -2192,7 +2198,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
2192
2198
|
});
|
|
2193
2199
|
}
|
|
2194
2200
|
handleOffline() {
|
|
2195
|
-
logger$
|
|
2201
|
+
logger$26.info("NetworkMonitor: browser went offline");
|
|
2196
2202
|
this._isOnline$.next(false);
|
|
2197
2203
|
this._networkChange$.next({
|
|
2198
2204
|
type: "offline",
|
|
@@ -2201,7 +2207,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
2201
2207
|
}
|
|
2202
2208
|
handleConnectionChange() {
|
|
2203
2209
|
const networkType = getNetworkType();
|
|
2204
|
-
logger$
|
|
2210
|
+
logger$26.info(`NetworkMonitor: connection changed — effectiveType=${networkType ?? "unknown"}`);
|
|
2205
2211
|
this._networkChange$.next({
|
|
2206
2212
|
type: "connection_change",
|
|
2207
2213
|
timestamp: Date.now(),
|
|
@@ -2316,7 +2322,7 @@ function getNavigatorMediaDevices() {
|
|
|
2316
2322
|
|
|
2317
2323
|
//#endregion
|
|
2318
2324
|
//#region src/controllers/PreflightRunner.ts
|
|
2319
|
-
const logger$
|
|
2325
|
+
const logger$25 = require_operators.getLogger();
|
|
2320
2326
|
const DEFAULT_MEDIA_TEST_DURATION_S = 10;
|
|
2321
2327
|
const ICE_GATHERING_TIMEOUT_MS = 1e4;
|
|
2322
2328
|
const SIGNALING_RTT_TIMEOUT_MS = 5e3;
|
|
@@ -2365,7 +2371,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
2365
2371
|
if (!this._options.skipMediaTest) try {
|
|
2366
2372
|
bandwidth = await this.testMediaBandwidth(destination);
|
|
2367
2373
|
} catch (error) {
|
|
2368
|
-
logger$
|
|
2374
|
+
logger$25.warn("[PreflightRunner] Media bandwidth test failed:", error);
|
|
2369
2375
|
warnings.push("Media bandwidth test failed");
|
|
2370
2376
|
}
|
|
2371
2377
|
return {
|
|
@@ -2377,7 +2383,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
2377
2383
|
warnings
|
|
2378
2384
|
};
|
|
2379
2385
|
} catch (error) {
|
|
2380
|
-
logger$
|
|
2386
|
+
logger$25.error("[PreflightRunner] Preflight test failed:", error);
|
|
2381
2387
|
throw new require_operators.PreflightError("preflight", error instanceof Error ? error : new Error(String(error)));
|
|
2382
2388
|
} finally {
|
|
2383
2389
|
this.destroy();
|
|
@@ -2408,7 +2414,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
2408
2414
|
if (track.kind === "video" && track.readyState === "live") videoWorking = true;
|
|
2409
2415
|
}
|
|
2410
2416
|
} catch (error) {
|
|
2411
|
-
logger$
|
|
2417
|
+
logger$25.warn("[PreflightRunner] Device test failed:", error);
|
|
2412
2418
|
} finally {
|
|
2413
2419
|
if (audioStream) audioStream.getTracks().forEach((t) => t.stop());
|
|
2414
2420
|
}
|
|
@@ -2466,7 +2472,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
2466
2472
|
rttMs
|
|
2467
2473
|
};
|
|
2468
2474
|
} catch (error) {
|
|
2469
|
-
logger$
|
|
2475
|
+
logger$25.warn("[PreflightRunner] ICE connectivity test failed:", error);
|
|
2470
2476
|
return {
|
|
2471
2477
|
type: "failed",
|
|
2472
2478
|
turnReachable: false,
|
|
@@ -2513,7 +2519,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
2513
2519
|
|
|
2514
2520
|
//#endregion
|
|
2515
2521
|
//#region src/controllers/VisibilityController.ts
|
|
2516
|
-
const logger$
|
|
2522
|
+
const logger$24 = require_operators.getLogger();
|
|
2517
2523
|
/**
|
|
2518
2524
|
* Checks whether the document visibility API is available.
|
|
2519
2525
|
*/
|
|
@@ -2550,8 +2556,8 @@ var VisibilityController = class extends Destroyable {
|
|
|
2550
2556
|
this._boundHandler = this._handleVisibilityChange.bind(this);
|
|
2551
2557
|
if (this._hasVisibilityApi) {
|
|
2552
2558
|
document.addEventListener("visibilitychange", this._boundHandler);
|
|
2553
|
-
logger$
|
|
2554
|
-
} 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");
|
|
2555
2561
|
}
|
|
2556
2562
|
/**
|
|
2557
2563
|
* Observable of the current visibility state.
|
|
@@ -2576,7 +2582,7 @@ var VisibilityController = class extends Destroyable {
|
|
|
2576
2582
|
destroy() {
|
|
2577
2583
|
if (this._hasVisibilityApi) {
|
|
2578
2584
|
document.removeEventListener("visibilitychange", this._boundHandler);
|
|
2579
|
-
logger$
|
|
2585
|
+
logger$24.debug("VisibilityController: removed visibilitychange listener");
|
|
2580
2586
|
}
|
|
2581
2587
|
super.destroy();
|
|
2582
2588
|
}
|
|
@@ -2594,7 +2600,7 @@ var VisibilityController = class extends Destroyable {
|
|
|
2594
2600
|
timestamp: Date.now()
|
|
2595
2601
|
};
|
|
2596
2602
|
this._visibilityChange$.next(changeEvent);
|
|
2597
|
-
logger$
|
|
2603
|
+
logger$24.debug("VisibilityController: visibility changed", {
|
|
2598
2604
|
from: previousState,
|
|
2599
2605
|
to: newState
|
|
2600
2606
|
});
|
|
@@ -2797,7 +2803,7 @@ const RPCEventAckResponse = (id) => makeRPCResponse({
|
|
|
2797
2803
|
|
|
2798
2804
|
//#endregion
|
|
2799
2805
|
//#region src/managers/AttachManager.ts
|
|
2800
|
-
const logger$
|
|
2806
|
+
const logger$23 = require_operators.getLogger();
|
|
2801
2807
|
var AttachManager = class {
|
|
2802
2808
|
constructor(storage, deviceController, reconnectCallsTimeout, attachKey) {
|
|
2803
2809
|
this.storage = storage;
|
|
@@ -2818,7 +2824,7 @@ var AttachManager = class {
|
|
|
2818
2824
|
try {
|
|
2819
2825
|
return await this.storage.getItem(this.attachKey) ?? {};
|
|
2820
2826
|
} catch (error) {
|
|
2821
|
-
logger$
|
|
2827
|
+
logger$23.warn("[AttachManager] Failed to retrieve attached calls from storage", error);
|
|
2822
2828
|
return {};
|
|
2823
2829
|
}
|
|
2824
2830
|
}
|
|
@@ -2826,7 +2832,7 @@ var AttachManager = class {
|
|
|
2826
2832
|
try {
|
|
2827
2833
|
await this.storage.setItem(this.attachKey, attached);
|
|
2828
2834
|
} catch (error) {
|
|
2829
|
-
logger$
|
|
2835
|
+
logger$23.warn("[AttachManager] Failed to write attached calls to storage", error);
|
|
2830
2836
|
}
|
|
2831
2837
|
}
|
|
2832
2838
|
/**
|
|
@@ -2845,7 +2851,7 @@ var AttachManager = class {
|
|
|
2845
2851
|
}
|
|
2846
2852
|
async attach(call) {
|
|
2847
2853
|
if (!call.to) {
|
|
2848
|
-
logger$
|
|
2854
|
+
logger$23.warn("[AttachManager] Skip attach for calls with no destination");
|
|
2849
2855
|
return;
|
|
2850
2856
|
}
|
|
2851
2857
|
const destination = call.to;
|
|
@@ -2898,15 +2904,15 @@ var AttachManager = class {
|
|
|
2898
2904
|
callId,
|
|
2899
2905
|
...options
|
|
2900
2906
|
});
|
|
2901
|
-
logger$
|
|
2907
|
+
logger$23.info(`[AttachManager] Reattached call ${callId} (attempt ${attempt})`);
|
|
2902
2908
|
succeeded = true;
|
|
2903
2909
|
break;
|
|
2904
2910
|
} catch (error) {
|
|
2905
|
-
logger$
|
|
2911
|
+
logger$23.warn(`[AttachManager] Reattach attempt ${attempt}/3 failed for call ${callId}:`, error);
|
|
2906
2912
|
if (attempt < 3) await new Promise((r) => setTimeout(r, (attempt + 1) * 1e3));
|
|
2907
2913
|
}
|
|
2908
2914
|
if (!succeeded) {
|
|
2909
|
-
logger$
|
|
2915
|
+
logger$23.warn(`[AttachManager] Reattach failed after 3 attempts for call ${callId}, removing reference`);
|
|
2910
2916
|
await this.detach({
|
|
2911
2917
|
id: callId,
|
|
2912
2918
|
mediaDirections: attachment.mediaDirections
|
|
@@ -3220,7 +3226,7 @@ function toggleHandraiseMethod(is) {
|
|
|
3220
3226
|
|
|
3221
3227
|
//#endregion
|
|
3222
3228
|
//#region src/core/entities/Participant.ts
|
|
3223
|
-
const logger$
|
|
3229
|
+
const logger$22 = require_operators.getLogger();
|
|
3224
3230
|
const initialState = {};
|
|
3225
3231
|
/**
|
|
3226
3232
|
* Represents a participant in a call.
|
|
@@ -3272,15 +3278,35 @@ var Participant = class extends Destroyable {
|
|
|
3272
3278
|
get deaf$() {
|
|
3273
3279
|
return this.cachedObservable("deaf$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.deaf), (0, rxjs_operators.distinctUntilChanged)()));
|
|
3274
3280
|
}
|
|
3275
|
-
/**
|
|
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
|
+
*/
|
|
3276
3289
|
get inputVolume$() {
|
|
3277
3290
|
return this.cachedObservable("inputVolume$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.input_volume), (0, rxjs_operators.distinctUntilChanged)()));
|
|
3278
3291
|
}
|
|
3279
|
-
/**
|
|
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
|
+
*/
|
|
3280
3299
|
get outputVolume$() {
|
|
3281
3300
|
return this.cachedObservable("outputVolume$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.output_volume), (0, rxjs_operators.distinctUntilChanged)()));
|
|
3282
3301
|
}
|
|
3283
|
-
/**
|
|
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
|
+
*/
|
|
3284
3310
|
get inputSensitivity$() {
|
|
3285
3311
|
return this.cachedObservable("inputSensitivity$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.input_sensitivity), (0, rxjs_operators.distinctUntilChanged)()));
|
|
3286
3312
|
}
|
|
@@ -3368,15 +3394,25 @@ var Participant = class extends Destroyable {
|
|
|
3368
3394
|
get deaf() {
|
|
3369
3395
|
return this._state$.value.deaf ?? false;
|
|
3370
3396
|
}
|
|
3371
|
-
/**
|
|
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
|
+
*/
|
|
3372
3402
|
get inputVolume() {
|
|
3373
3403
|
return this._state$.value.input_volume;
|
|
3374
3404
|
}
|
|
3375
|
-
/**
|
|
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
|
+
*/
|
|
3376
3409
|
get outputVolume() {
|
|
3377
3410
|
return this._state$.value.output_volume;
|
|
3378
3411
|
}
|
|
3379
|
-
/**
|
|
3412
|
+
/**
|
|
3413
|
+
* Current **conference-only** microphone sensitivity/gate level, or
|
|
3414
|
+
* `undefined` if not set. Applies only to conference members.
|
|
3415
|
+
*/
|
|
3380
3416
|
get inputSensitivity() {
|
|
3381
3417
|
return this._state$.value.input_sensitivity;
|
|
3382
3418
|
}
|
|
@@ -3480,19 +3516,44 @@ var Participant = class extends Destroyable {
|
|
|
3480
3516
|
async toggleLowbitrate() {
|
|
3481
3517
|
throw new require_operators.UnimplementedError();
|
|
3482
3518
|
}
|
|
3483
|
-
/**
|
|
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
|
+
*/
|
|
3484
3532
|
async setAudioInputSensitivity(value) {
|
|
3485
3533
|
await this.executeMethod(this.id, "call.microphone.sensitivity.set", { sensitivity: value });
|
|
3486
3534
|
}
|
|
3487
3535
|
/**
|
|
3488
|
-
* 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
|
+
*
|
|
3489
3543
|
* @param value - Volume level (0-100).
|
|
3490
3544
|
*/
|
|
3491
3545
|
async setAudioInputVolume(value) {
|
|
3492
3546
|
await this.executeMethod(this.id, "call.microphone.volume.set", { volume: value });
|
|
3493
3547
|
}
|
|
3494
3548
|
/**
|
|
3495
|
-
* 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
|
+
*
|
|
3496
3557
|
* @param value - Volume level (0-100).
|
|
3497
3558
|
*/
|
|
3498
3559
|
async setAudioOutputVolume(value) {
|
|
@@ -3598,7 +3659,7 @@ var SelfParticipant = class extends Participant {
|
|
|
3598
3659
|
try {
|
|
3599
3660
|
await this.vertoManager.addScreenMedia();
|
|
3600
3661
|
} catch (error) {
|
|
3601
|
-
logger$
|
|
3662
|
+
logger$22.error("[Participant.startScreenShare] Screen share error:", error);
|
|
3602
3663
|
}
|
|
3603
3664
|
}
|
|
3604
3665
|
/** Observable of the current screen share status. */
|
|
@@ -3618,7 +3679,7 @@ var SelfParticipant = class extends Participant {
|
|
|
3618
3679
|
try {
|
|
3619
3680
|
await this.vertoManager.addInputDevice(options);
|
|
3620
3681
|
} catch (error) {
|
|
3621
|
-
logger$
|
|
3682
|
+
logger$22.error("[Participant.startScreenShare] Screen share error:", error);
|
|
3622
3683
|
}
|
|
3623
3684
|
}
|
|
3624
3685
|
/** Removes an additional media input device by ID. */
|
|
@@ -3680,7 +3741,7 @@ var SelfParticipant = class extends Participant {
|
|
|
3680
3741
|
*/
|
|
3681
3742
|
exitStudioModeIfActive() {
|
|
3682
3743
|
if (this._studioAudio$.value) {
|
|
3683
|
-
logger$
|
|
3744
|
+
logger$22.debug("[SelfParticipant] Exiting studio audio mode due to individual flag toggle");
|
|
3684
3745
|
this._studioAudio$.next(false);
|
|
3685
3746
|
}
|
|
3686
3747
|
}
|
|
@@ -3704,7 +3765,7 @@ var SelfParticipant = class extends Participant {
|
|
|
3704
3765
|
try {
|
|
3705
3766
|
await super.mute();
|
|
3706
3767
|
} catch (error) {
|
|
3707
|
-
logger$
|
|
3768
|
+
logger$22.warn("[Participant.toggleAudioInput] Server Error while muting audio input, proceeding with local toggle anyway", error);
|
|
3708
3769
|
} finally {
|
|
3709
3770
|
this.vertoManager.muteMainAudioInputDevice();
|
|
3710
3771
|
}
|
|
@@ -3714,7 +3775,7 @@ var SelfParticipant = class extends Participant {
|
|
|
3714
3775
|
try {
|
|
3715
3776
|
await super.unmute();
|
|
3716
3777
|
} catch (error) {
|
|
3717
|
-
logger$
|
|
3778
|
+
logger$22.warn("[Participant.toggleAudioInput] Server Error while unmuting audio input, proceeding with local toggle anyway", error);
|
|
3718
3779
|
} finally {
|
|
3719
3780
|
await this.vertoManager.unmuteMainAudioInputDevice();
|
|
3720
3781
|
}
|
|
@@ -3724,7 +3785,7 @@ var SelfParticipant = class extends Participant {
|
|
|
3724
3785
|
try {
|
|
3725
3786
|
await super.muteVideo();
|
|
3726
3787
|
} catch (error) {
|
|
3727
|
-
logger$
|
|
3788
|
+
logger$22.warn("[Participant.toggleVideoInput] Server Error while muting video input, proceeding with local toggle anyway", error);
|
|
3728
3789
|
} finally {
|
|
3729
3790
|
this.vertoManager.muteMainVideoInputDevice();
|
|
3730
3791
|
}
|
|
@@ -3734,7 +3795,7 @@ var SelfParticipant = class extends Participant {
|
|
|
3734
3795
|
try {
|
|
3735
3796
|
await super.unmuteVideo();
|
|
3736
3797
|
} catch (error) {
|
|
3737
|
-
logger$
|
|
3798
|
+
logger$22.warn("[Participant.toggleVideoInput] Server Error while unmuting video input, proceeding with local toggle anyway", error);
|
|
3738
3799
|
} finally {
|
|
3739
3800
|
await this.vertoManager.unmuteMainVideoInputDevice();
|
|
3740
3801
|
}
|
|
@@ -3828,7 +3889,7 @@ function isLayoutChangedPayload(value) {
|
|
|
3828
3889
|
|
|
3829
3890
|
//#endregion
|
|
3830
3891
|
//#region src/managers/CallEventsManager.ts
|
|
3831
|
-
const logger$
|
|
3892
|
+
const logger$21 = require_operators.getLogger();
|
|
3832
3893
|
const initialSessionState = {};
|
|
3833
3894
|
/** @internal */
|
|
3834
3895
|
var CallEventsManager = class extends Destroyable {
|
|
@@ -3932,7 +3993,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
3932
3993
|
}
|
|
3933
3994
|
initSubscriptions() {
|
|
3934
3995
|
this.subscribeTo(this.callJoinedEvent$, (callJoinedEvent) => {
|
|
3935
|
-
logger$
|
|
3996
|
+
logger$21.debug("[CallEventsManager] Handling call.joined event for call/session IDs:", {
|
|
3936
3997
|
callId: callJoinedEvent.call_id,
|
|
3937
3998
|
roomSessionId: callJoinedEvent.room_session_id
|
|
3938
3999
|
});
|
|
@@ -3959,19 +4020,19 @@ var CallEventsManager = class extends Destroyable {
|
|
|
3959
4020
|
if (this._self$.value?.capabilities.setLayout) this.updateLayouts();
|
|
3960
4021
|
});
|
|
3961
4022
|
this.subscribeTo(this.memberUpdates$, (member) => {
|
|
3962
|
-
logger$
|
|
4023
|
+
logger$21.debug("[CallEventsManager] Handling member update event for member ID:", member);
|
|
3963
4024
|
this.upsertParticipant(member);
|
|
3964
4025
|
});
|
|
3965
4026
|
this.subscribeTo(this.webRtcCallSession.memberLeft$, (memberLeftEvent) => {
|
|
3966
|
-
logger$
|
|
4027
|
+
logger$21.debug("[CallEventsManager] Handling member.left event for member ID:", memberLeftEvent.member.member_id);
|
|
3967
4028
|
const participants = { ...this._participants$.value };
|
|
3968
4029
|
if (memberLeftEvent.member.member_id in participants) {
|
|
3969
4030
|
delete participants[memberLeftEvent.member.member_id];
|
|
3970
4031
|
this._participants$.next(participants);
|
|
3971
|
-
} else logger$
|
|
4032
|
+
} else logger$21.warn(`[CallEventsManager] Received member.left event for unknown member ID: ${memberLeftEvent.member.member_id}`);
|
|
3972
4033
|
});
|
|
3973
4034
|
this.subscribeTo(this.webRtcCallSession.callUpdated$, (callUpdatedEvent) => {
|
|
3974
|
-
logger$
|
|
4035
|
+
logger$21.debug("[CallEventsManager] Handling call.updated event:", callUpdatedEvent);
|
|
3975
4036
|
const roomSession = callUpdatedEvent.room_session;
|
|
3976
4037
|
this._sessionState$.next({
|
|
3977
4038
|
...this._sessionState$.value,
|
|
@@ -3986,7 +4047,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
3986
4047
|
});
|
|
3987
4048
|
});
|
|
3988
4049
|
this.subscribeTo(this.layoutChangedEvent$, (layoutChangedEvent) => {
|
|
3989
|
-
logger$
|
|
4050
|
+
logger$21.debug("[CallEventsManager] Handling layout.changed event:", layoutChangedEvent);
|
|
3990
4051
|
this._sessionState$.next({
|
|
3991
4052
|
...this._sessionState$.value,
|
|
3992
4053
|
layout_name: layoutChangedEvent.id,
|
|
@@ -3996,10 +4057,10 @@ var CallEventsManager = class extends Destroyable {
|
|
|
3996
4057
|
});
|
|
3997
4058
|
}
|
|
3998
4059
|
updateParticipantPositions(layoutChangedEvent) {
|
|
3999
|
-
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.");
|
|
4000
4061
|
layoutChangedEvent.layers.filter((layer) => !!layer.member_id).filter((layer) => {
|
|
4001
4062
|
if (!(layer.member_id in this._participants$.value)) {
|
|
4002
|
-
logger$
|
|
4063
|
+
logger$21.warn(`[CallEventsManager] Skipping layout layer for unknown member_id: ${layer.member_id}`);
|
|
4003
4064
|
return false;
|
|
4004
4065
|
}
|
|
4005
4066
|
return true;
|
|
@@ -4022,7 +4083,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
4022
4083
|
layouts: response.result.layouts
|
|
4023
4084
|
});
|
|
4024
4085
|
}).catch((error) => {
|
|
4025
|
-
logger$
|
|
4086
|
+
logger$21.error("[CallEventsManager] Error fetching layouts:", error);
|
|
4026
4087
|
});
|
|
4027
4088
|
}
|
|
4028
4089
|
updateParticipants(members) {
|
|
@@ -4038,7 +4099,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
4038
4099
|
}
|
|
4039
4100
|
const participant = this._participants$.value[member.member_id];
|
|
4040
4101
|
const oldValue = participant.value;
|
|
4041
|
-
logger$
|
|
4102
|
+
logger$21.debug("[CallEventsManager] Updating participant:", member.member_id, {
|
|
4042
4103
|
oldValue,
|
|
4043
4104
|
newValue: member
|
|
4044
4105
|
});
|
|
@@ -4051,17 +4112,17 @@ var CallEventsManager = class extends Destroyable {
|
|
|
4051
4112
|
}
|
|
4052
4113
|
get callJoinedEvent$() {
|
|
4053
4114
|
return this.cachedObservable("callJoinedEvent$", () => this.webRtcCallSession.callEvent$.pipe((0, rxjs.filter)(isCallJoinedPayload), (0, rxjs.tap)((event) => {
|
|
4054
|
-
logger$
|
|
4115
|
+
logger$21.debug("[CallEventsManager] Call joined event:", event);
|
|
4055
4116
|
})));
|
|
4056
4117
|
}
|
|
4057
4118
|
get layoutChangedEvent$() {
|
|
4058
4119
|
return this.cachedObservable("layoutChangedEvent$", () => this.webRtcCallSession.callEvent$.pipe(require_operators.filterAs(isLayoutChangedPayload, "layout"), (0, rxjs.tap)((event) => {
|
|
4059
|
-
logger$
|
|
4120
|
+
logger$21.debug("[CallEventsManager] Layout changed event:", event);
|
|
4060
4121
|
})));
|
|
4061
4122
|
}
|
|
4062
4123
|
get memberUpdates$() {
|
|
4063
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) => {
|
|
4064
|
-
logger$
|
|
4125
|
+
logger$21.debug("[CallEventsManager] Member update event:", event);
|
|
4065
4126
|
})));
|
|
4066
4127
|
}
|
|
4067
4128
|
destroy() {
|
|
@@ -4317,7 +4378,7 @@ function appendStereoParams(fmtpLine, maxBitrate) {
|
|
|
4317
4378
|
|
|
4318
4379
|
//#endregion
|
|
4319
4380
|
//#region src/controllers/ICEGatheringController.ts
|
|
4320
|
-
const logger$
|
|
4381
|
+
const logger$20 = require_operators.getLogger();
|
|
4321
4382
|
var ICEGatheringController = class extends Destroyable {
|
|
4322
4383
|
constructor(peerConnection, peerConnectionControllerNegotiating$, options = {}) {
|
|
4323
4384
|
super();
|
|
@@ -4325,23 +4386,23 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
4325
4386
|
this.peerConnectionControllerNegotiating$ = peerConnectionControllerNegotiating$;
|
|
4326
4387
|
this.onicegatheringstatechangeHandler = () => {
|
|
4327
4388
|
const { iceGatheringState } = this.peerConnection;
|
|
4328
|
-
logger$
|
|
4389
|
+
logger$20.debug(`[ICEGatheringController] ICE gathering state changed to: ${iceGatheringState}`);
|
|
4329
4390
|
if (iceGatheringState === "gathering") this._iceCandidatesState.next({
|
|
4330
4391
|
state: "gathering",
|
|
4331
4392
|
validSDP: false
|
|
4332
4393
|
});
|
|
4333
4394
|
};
|
|
4334
4395
|
this.onicecandidateHandler = (event) => {
|
|
4335
|
-
logger$
|
|
4396
|
+
logger$20.debug("[ICEGatheringController] ICE candidate event received:", event.candidate);
|
|
4336
4397
|
this.removeTimer("iceCandidateTimer");
|
|
4337
4398
|
if (event.candidate) this.iceCandidateTimer = setTimeout(() => {
|
|
4338
4399
|
if (this.peerConnection.iceGatheringState !== "complete") {
|
|
4339
|
-
logger$
|
|
4400
|
+
logger$20.warn("[ICEGatheringController] ICE candidate timeout, using current SDP");
|
|
4340
4401
|
this.handleICECandidateTimeout();
|
|
4341
4402
|
}
|
|
4342
4403
|
}, this.iceCandidateTimeout);
|
|
4343
4404
|
else {
|
|
4344
|
-
logger$
|
|
4405
|
+
logger$20.debug("[ICEGatheringController] ICE gathering completed: null candidate received");
|
|
4345
4406
|
this.removeTimer("iceGatheringTimer");
|
|
4346
4407
|
this.handleICEGatheringComplete();
|
|
4347
4408
|
}
|
|
@@ -4359,7 +4420,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
4359
4420
|
this.setupEventListeners();
|
|
4360
4421
|
this.iceGatheringTimer = setTimeout(() => {
|
|
4361
4422
|
if (this.peerConnection.iceGatheringState !== "complete") {
|
|
4362
|
-
logger$
|
|
4423
|
+
logger$20.warn("[ICEGatheringController] ICE gathering timeout, using current SDP");
|
|
4363
4424
|
this.handleICEGatheringTimeout();
|
|
4364
4425
|
}
|
|
4365
4426
|
}, this.iceGatheringTimeout);
|
|
@@ -4386,9 +4447,9 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
4386
4447
|
this.relayOnly = value;
|
|
4387
4448
|
}
|
|
4388
4449
|
handleICEGatheringComplete() {
|
|
4389
|
-
logger$
|
|
4390
|
-
logger$
|
|
4391
|
-
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");
|
|
4392
4453
|
this._iceCandidatesState.next({
|
|
4393
4454
|
state: "complete",
|
|
4394
4455
|
validSDP: this.hasValidLocalDescriptionSDP
|
|
@@ -4404,21 +4465,21 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
4404
4465
|
this.removeTimer("iceGatheringTimer");
|
|
4405
4466
|
const validSDP = this.hasValidLocalDescriptionSDP;
|
|
4406
4467
|
if (validSDP) {
|
|
4407
|
-
logger$
|
|
4468
|
+
logger$20.debug("[ICEGatheringController] Local SDP is valid");
|
|
4408
4469
|
this._iceCandidatesState.next({
|
|
4409
4470
|
state: "timeout",
|
|
4410
4471
|
validSDP
|
|
4411
4472
|
});
|
|
4412
4473
|
this.stopGathering();
|
|
4413
|
-
} else logger$
|
|
4474
|
+
} else logger$20.debug("### ICE gathering timeout\n", this.peerConnection.localDescription?.sdp);
|
|
4414
4475
|
}
|
|
4415
4476
|
handleICECandidateTimeout() {
|
|
4416
4477
|
if (this.iceCandidateTimer) this.removeTimer("iceCandidateTimer");
|
|
4417
|
-
logger$
|
|
4478
|
+
logger$20.warn("[ICEGatheringController] ICE candidate timeout");
|
|
4418
4479
|
const validSDP = this.hasValidLocalDescriptionSDP;
|
|
4419
4480
|
if (!validSDP && !this.relayOnly) this.restartICEGatheringWithRelayOnly();
|
|
4420
4481
|
else {
|
|
4421
|
-
logger$
|
|
4482
|
+
logger$20.debug("[ICEGatheringController] Using current SDP due to ICE candidate timeout");
|
|
4422
4483
|
this._iceCandidatesState.next({
|
|
4423
4484
|
state: "timeout",
|
|
4424
4485
|
validSDP
|
|
@@ -4427,7 +4488,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
4427
4488
|
}
|
|
4428
4489
|
}
|
|
4429
4490
|
restartICEGatheringWithRelayOnly() {
|
|
4430
|
-
logger$
|
|
4491
|
+
logger$20.debug("[ICEGatheringController] Restarting ICE gathering with relay-only candidates");
|
|
4431
4492
|
this.relayOnly = true;
|
|
4432
4493
|
this.peerConnection.setConfiguration({
|
|
4433
4494
|
...this.peerConnection.getConfiguration(),
|
|
@@ -4442,7 +4503,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
4442
4503
|
}
|
|
4443
4504
|
}
|
|
4444
4505
|
clearAllTimers() {
|
|
4445
|
-
logger$
|
|
4506
|
+
logger$20.debug("[ICEGatheringController] Clearing all timers");
|
|
4446
4507
|
this.removeTimer("iceGatheringTimer");
|
|
4447
4508
|
this.removeTimer("iceCandidateTimer");
|
|
4448
4509
|
}
|
|
@@ -4451,16 +4512,179 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
4451
4512
|
this.peerConnection.removeEventListener("icecandidate", this.onicecandidateHandler);
|
|
4452
4513
|
}
|
|
4453
4514
|
destroy() {
|
|
4454
|
-
logger$
|
|
4515
|
+
logger$20.debug("[ICEGatheringController] Destroying ICEGatheringController");
|
|
4455
4516
|
this.clearAllTimers();
|
|
4456
4517
|
this.removeEventListeners();
|
|
4457
4518
|
super.destroy();
|
|
4458
4519
|
}
|
|
4459
4520
|
};
|
|
4460
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
|
+
|
|
4461
4685
|
//#endregion
|
|
4462
4686
|
//#region src/controllers/LocalStreamController.ts
|
|
4463
|
-
const logger$
|
|
4687
|
+
const logger$18 = require_operators.getLogger();
|
|
4464
4688
|
var LocalStreamController = class extends Destroyable {
|
|
4465
4689
|
constructor(options) {
|
|
4466
4690
|
super();
|
|
@@ -4498,26 +4722,26 @@ var LocalStreamController = class extends Destroyable {
|
|
|
4498
4722
|
* Build the local media stream based on the provided options.
|
|
4499
4723
|
*/
|
|
4500
4724
|
async buildLocalStream() {
|
|
4501
|
-
logger$
|
|
4725
|
+
logger$18.debug("[LocalStreamController] Building local media stream.");
|
|
4502
4726
|
let stream;
|
|
4503
4727
|
if (this.options.inputAudioStream ?? this.options.inputVideoStream) {
|
|
4504
4728
|
const tracks = [...this.options.inputAudioStream?.getTracks() ?? [], ...this.options.inputVideoStream?.getTracks() ?? []];
|
|
4505
4729
|
stream = new MediaStream(tracks);
|
|
4506
4730
|
} else if (this.options.propose === "screenshare") {
|
|
4507
|
-
logger$
|
|
4731
|
+
logger$18.debug("[LocalStreamController] Requesting display media for screen sharing with audio:", Boolean(this.options.inputAudioDeviceConstraints));
|
|
4508
4732
|
stream = await this.options.getDisplayMedia({
|
|
4509
4733
|
video: true,
|
|
4510
4734
|
audio: Boolean(this.options.inputAudioDeviceConstraints)
|
|
4511
4735
|
});
|
|
4512
|
-
logger$
|
|
4736
|
+
logger$18.debug("[LocalStreamController] Screen share media obtained:", stream);
|
|
4513
4737
|
} else {
|
|
4514
4738
|
const constraints = {
|
|
4515
4739
|
audio: this.options.inputAudioDeviceConstraints,
|
|
4516
4740
|
video: this.options.inputVideoDeviceConstraints
|
|
4517
4741
|
};
|
|
4518
|
-
logger$
|
|
4742
|
+
logger$18.debug("[LocalStreamController] Requesting user media with constraints:", constraints);
|
|
4519
4743
|
stream = await this.options.getUserMedia(constraints);
|
|
4520
|
-
logger$
|
|
4744
|
+
logger$18.debug("[LocalStreamController] User media obtained:", stream);
|
|
4521
4745
|
}
|
|
4522
4746
|
this._localStream$.next(stream);
|
|
4523
4747
|
return stream;
|
|
@@ -4534,7 +4758,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
4534
4758
|
this._localStream$.next(localStream);
|
|
4535
4759
|
if (track.kind === "video") this._localVideoTracks$.next(localStream.getVideoTracks());
|
|
4536
4760
|
else this._localAudioTracks$.next(localStream.getAudioTracks());
|
|
4537
|
-
logger$
|
|
4761
|
+
logger$18.debug(`[LocalStreamController] ${track.kind} track added:`, track.id);
|
|
4538
4762
|
return localStream;
|
|
4539
4763
|
}
|
|
4540
4764
|
/**
|
|
@@ -4546,7 +4770,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
4546
4770
|
const stream = this._localStream$.value;
|
|
4547
4771
|
const track = stream?.getTracks().find((t) => t.id === trackId);
|
|
4548
4772
|
if (!track) {
|
|
4549
|
-
logger$
|
|
4773
|
+
logger$18.debug(`[LocalStreamController] track not found: ${trackId}`);
|
|
4550
4774
|
return;
|
|
4551
4775
|
}
|
|
4552
4776
|
track.removeEventListener("ended", this.mediaTrackEndedHandler);
|
|
@@ -4555,7 +4779,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
4555
4779
|
this._localStream$.next(stream);
|
|
4556
4780
|
if (track.kind === "video") this._localVideoTracks$.next(stream?.getVideoTracks() ?? []);
|
|
4557
4781
|
else this._localAudioTracks$.next(stream?.getAudioTracks() ?? []);
|
|
4558
|
-
logger$
|
|
4782
|
+
logger$18.debug(`[LocalStreamController] ${track.kind} track removed:`, trackId);
|
|
4559
4783
|
return track;
|
|
4560
4784
|
}
|
|
4561
4785
|
/**
|
|
@@ -4590,7 +4814,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
4590
4814
|
*/
|
|
4591
4815
|
stopAllTracks() {
|
|
4592
4816
|
this._localStream$.value?.getTracks().forEach((track) => {
|
|
4593
|
-
logger$
|
|
4817
|
+
logger$18.debug(`[LocalStreamController] Stopping local track: ${track.kind}`);
|
|
4594
4818
|
track.removeEventListener("ended", this.mediaTrackEndedHandler);
|
|
4595
4819
|
track.stop();
|
|
4596
4820
|
});
|
|
@@ -4606,7 +4830,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
4606
4830
|
|
|
4607
4831
|
//#endregion
|
|
4608
4832
|
//#region src/controllers/TransceiverController.ts
|
|
4609
|
-
const logger$
|
|
4833
|
+
const logger$17 = require_operators.getLogger();
|
|
4610
4834
|
const getDirection = (send, recv) => {
|
|
4611
4835
|
if (send && recv) return "sendrecv";
|
|
4612
4836
|
else if (send && !recv) return "sendonly";
|
|
@@ -4708,7 +4932,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
4708
4932
|
sendEncodings: isAudio ? void 0 : this.sendEncodings,
|
|
4709
4933
|
streams: direction === "recvonly" ? void 0 : [localStream]
|
|
4710
4934
|
};
|
|
4711
|
-
logger$
|
|
4935
|
+
logger$17.debug(`[TransceiverController] Setting up transceiver sender for local ${track.kind} track:`, {
|
|
4712
4936
|
transceiver,
|
|
4713
4937
|
transceiverParams
|
|
4714
4938
|
});
|
|
@@ -4716,11 +4940,11 @@ var TransceiverController = class extends Destroyable {
|
|
|
4716
4940
|
await transceiver.sender.replaceTrack(track);
|
|
4717
4941
|
transceiver.direction = transceiverParams.direction;
|
|
4718
4942
|
if (transceiverParams.streams?.some((stream) => Boolean(stream))) {
|
|
4719
|
-
logger$
|
|
4943
|
+
logger$17.debug(`[TransceiverController] Setting streams for transceiver sender for local ${track.kind} track:`, transceiverParams.streams);
|
|
4720
4944
|
transceiver.sender.setStreams(...transceiverParams.streams);
|
|
4721
4945
|
}
|
|
4722
4946
|
} else {
|
|
4723
|
-
logger$
|
|
4947
|
+
logger$17.debug(`[TransceiverController] Adding new transceiver for local ${track.kind} track:`, track.id);
|
|
4724
4948
|
this.peerConnection.addTransceiver(track, transceiverParams);
|
|
4725
4949
|
}
|
|
4726
4950
|
}
|
|
@@ -4734,13 +4958,13 @@ var TransceiverController = class extends Destroyable {
|
|
|
4734
4958
|
if (options.updateTransceiverDirection) transceiver.direction = "inactive";
|
|
4735
4959
|
}
|
|
4736
4960
|
} catch (error) {
|
|
4737
|
-
logger$
|
|
4961
|
+
logger$17.error("[TransceiverController] stopTrackSender error", kind, error);
|
|
4738
4962
|
this.options.onError?.(new require_operators.MediaTrackError("stopTrackSender", kind, error));
|
|
4739
4963
|
}
|
|
4740
4964
|
}
|
|
4741
4965
|
async restoreTrackSender(kind) {
|
|
4742
4966
|
try {
|
|
4743
|
-
logger$
|
|
4967
|
+
logger$17.debug("[TransceiverController] restoreTrackSender called", kind);
|
|
4744
4968
|
const constraints = {};
|
|
4745
4969
|
const transceivers = this.transceiverByKind(kind);
|
|
4746
4970
|
for (const transceiver of transceivers) {
|
|
@@ -4750,23 +4974,23 @@ var TransceiverController = class extends Destroyable {
|
|
|
4750
4974
|
if (trackKind === "audio" || trackKind === "video") constraints[trackKind] = this.getConstraintsFor(trackKind);
|
|
4751
4975
|
}
|
|
4752
4976
|
}
|
|
4753
|
-
logger$
|
|
4977
|
+
logger$17.debug("[TransceiverController] restoreTrackSender constraints:", constraints);
|
|
4754
4978
|
if (Object.keys(constraints).length === 0) {
|
|
4755
|
-
logger$
|
|
4979
|
+
logger$17.warn("[TransceiverController] restoreTrackSender: no tracks need restoration", kind);
|
|
4756
4980
|
return;
|
|
4757
4981
|
}
|
|
4758
4982
|
const newTracks = (await this.options.getUserMedia(constraints)).getTracks();
|
|
4759
|
-
logger$
|
|
4983
|
+
logger$17.debug("[TransceiverController] restoreTrackSender new tracks:", newTracks);
|
|
4760
4984
|
for (const newTrack of newTracks) {
|
|
4761
4985
|
this.options.localStreamController.addTrack(newTrack);
|
|
4762
4986
|
const trackKind = newTrack.kind;
|
|
4763
4987
|
const transceiverOfKind = this.transceiverByKind(trackKind)[0];
|
|
4764
4988
|
transceiverOfKind.direction = trackKind === "audio" ? this.audioDirection : this.videoDirection;
|
|
4765
|
-
logger$
|
|
4989
|
+
logger$17.debug("[TransceiverController] restoreTrackSender setting direction for", trackKind, transceiverOfKind.direction);
|
|
4766
4990
|
await transceiverOfKind.sender.replaceTrack(newTrack);
|
|
4767
4991
|
}
|
|
4768
4992
|
} catch (error) {
|
|
4769
|
-
logger$
|
|
4993
|
+
logger$17.error("[TransceiverController] restoreTrackSender error", kind, error);
|
|
4770
4994
|
this.options.onError?.(new require_operators.MediaTrackError("restoreTrackSender", kind, error));
|
|
4771
4995
|
}
|
|
4772
4996
|
}
|
|
@@ -4807,14 +5031,14 @@ var TransceiverController = class extends Destroyable {
|
|
|
4807
5031
|
};
|
|
4808
5032
|
try {
|
|
4809
5033
|
await track.applyConstraints(constraintsToApply);
|
|
4810
|
-
logger$
|
|
4811
|
-
logger$
|
|
5034
|
+
logger$17.debug(`[TransceiverController] Updated ${kind} sender constraints:`, constraintsToApply);
|
|
5035
|
+
logger$17.debug(`[TransceiverController] Updated ${kind} sender constraints:`, track.getConstraints());
|
|
4812
5036
|
} catch (error) {
|
|
4813
|
-
logger$
|
|
5037
|
+
logger$17.warn(`[TransceiverController] applyConstraints failed for ${kind} track ${track.id}, attempting track replacement fallback:`, error);
|
|
4814
5038
|
try {
|
|
4815
5039
|
await this.replaceTrackFallback(sender, track, kind, constraintsToApply);
|
|
4816
5040
|
} catch (fallbackError) {
|
|
4817
|
-
logger$
|
|
5041
|
+
logger$17.warn(`[TransceiverController] Track replacement fallback also failed for ${kind} track:`, fallbackError);
|
|
4818
5042
|
this.options.onError?.(new require_operators.MediaTrackError("updateSendersConstraints", kind, fallbackError));
|
|
4819
5043
|
}
|
|
4820
5044
|
}
|
|
@@ -4842,7 +5066,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
4842
5066
|
if (!newTrack) throw new require_operators.MediaTrackError("replaceTrackFallback", kind, /* @__PURE__ */ new Error("getUserMedia returned no track of the requested kind"));
|
|
4843
5067
|
await sender.replaceTrack(newTrack);
|
|
4844
5068
|
this.options.localStreamController.addTrack(newTrack);
|
|
4845
|
-
logger$
|
|
5069
|
+
logger$17.debug(`[TransceiverController] Track replacement fallback succeeded for ${kind}. New track: ${newTrack.id}`);
|
|
4846
5070
|
}
|
|
4847
5071
|
getMediaDirections() {
|
|
4848
5072
|
if (this.peerConnection.connectionState === "connected") return this.peerConnection.getTransceivers().reduce((acc, transceiver) => {
|
|
@@ -4872,7 +5096,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
4872
5096
|
|
|
4873
5097
|
//#endregion
|
|
4874
5098
|
//#region src/controllers/RTCPeerConnectionController.ts
|
|
4875
|
-
const logger$
|
|
5099
|
+
const logger$16 = require_operators.getLogger();
|
|
4876
5100
|
var RTCPeerConnectionController = class extends Destroyable {
|
|
4877
5101
|
constructor(options = {}, remoteSessionDescription, deviceController) {
|
|
4878
5102
|
super();
|
|
@@ -4888,43 +5112,43 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
4888
5112
|
this.oniceconnectionstatechangeHandler = () => {
|
|
4889
5113
|
if (this.peerConnection) {
|
|
4890
5114
|
const { iceConnectionState } = this.peerConnection;
|
|
4891
|
-
logger$
|
|
5115
|
+
logger$16.debug(`[RTCPeerConnectionController] ICE connection state changed to: ${iceConnectionState}`);
|
|
4892
5116
|
this._iceConnectionState$.next(this.peerConnection.iceConnectionState);
|
|
4893
5117
|
}
|
|
4894
5118
|
};
|
|
4895
5119
|
this.onconnectionstatechangeHandler = () => {
|
|
4896
5120
|
if (this.peerConnection) {
|
|
4897
5121
|
const { connectionState } = this.peerConnection;
|
|
4898
|
-
logger$
|
|
5122
|
+
logger$16.debug(`[RTCPeerConnectionController] Connection state changed to: ${connectionState}`);
|
|
4899
5123
|
if (connectionState === "connected") this.removeConnectionTimer();
|
|
4900
5124
|
this._connectionState$.next(this.peerConnection.connectionState);
|
|
4901
5125
|
}
|
|
4902
5126
|
};
|
|
4903
5127
|
this.onsignalingstatechangeHandler = () => {
|
|
4904
|
-
logger$
|
|
5128
|
+
logger$16.debug(`[RTCPeerConnectionController] Signaling state changed to: ${this.peerConnection?.signalingState}`);
|
|
4905
5129
|
};
|
|
4906
5130
|
this.onicegatheringstatechangeHandler = () => {
|
|
4907
5131
|
if (this.peerConnection) this._iceGatheringState$.next(this.peerConnection.iceGatheringState);
|
|
4908
5132
|
};
|
|
4909
5133
|
this.onnegotiationneededHandler = (event) => {
|
|
4910
|
-
logger$
|
|
5134
|
+
logger$16.debug("[RTCPeerConnectionController] Negotiation needed event received.", event);
|
|
4911
5135
|
this.negotiationNeeded$.next();
|
|
4912
5136
|
};
|
|
4913
5137
|
this.updateSelectedInputDevice = async (kind, deviceInfo) => {
|
|
4914
5138
|
try {
|
|
4915
5139
|
const { localStream } = this;
|
|
4916
5140
|
if (!localStream) {
|
|
4917
|
-
logger$
|
|
5141
|
+
logger$16.warn("[RTCPeerConnectionController] No local stream available to update input device.");
|
|
4918
5142
|
return;
|
|
4919
5143
|
}
|
|
4920
|
-
logger$
|
|
5144
|
+
logger$16.debug(`[RTCPeerConnectionController] Updating selected ${kind} input device:`, localStream.getTracks());
|
|
4921
5145
|
const track = localStream.getTracks().find((track$1) => track$1.kind === kind);
|
|
4922
5146
|
if (track) {
|
|
4923
5147
|
this.transceiverController?.stopTrackSender(kind);
|
|
4924
5148
|
this.localStream?.removeTrack(track);
|
|
4925
|
-
logger$
|
|
5149
|
+
logger$16.debug(`[RTCPeerConnectionController] Stopped existing ${kind} track: ${track.id}`, localStream.getTracks());
|
|
4926
5150
|
if (!deviceInfo) {
|
|
4927
|
-
logger$
|
|
5151
|
+
logger$16.debug(`[RTCPeerConnectionController] ${kind} input device selected: none`);
|
|
4928
5152
|
return;
|
|
4929
5153
|
}
|
|
4930
5154
|
const streamTrack = (await this.getUserMedia({ [kind]: {
|
|
@@ -4932,15 +5156,15 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
4932
5156
|
...this.deviceController.deviceInfoToConstraints(deviceInfo)
|
|
4933
5157
|
} })).getTracks().find((t) => t.kind === kind);
|
|
4934
5158
|
if (streamTrack) {
|
|
4935
|
-
logger$
|
|
5159
|
+
logger$16.debug(`[RTCPeerConnectionController] Adding new ${kind} track: ${streamTrack.id}`);
|
|
4936
5160
|
this.localStream?.addTrack(streamTrack);
|
|
4937
5161
|
await this.transceiverController?.replaceSenderTrack(kind, streamTrack);
|
|
4938
|
-
logger$
|
|
5162
|
+
logger$16.debug(`[RTCPeerConnectionController] Added new ${kind} track: ${streamTrack.id}`, this.localStream?.getTracks());
|
|
4939
5163
|
}
|
|
4940
5164
|
}
|
|
4941
|
-
logger$
|
|
5165
|
+
logger$16.debug(`[RTCPeerConnectionController] ${kind} input device selected:`, deviceInfo?.label);
|
|
4942
5166
|
} catch (error) {
|
|
4943
|
-
logger$
|
|
5167
|
+
logger$16.error(`[RTCPeerConnectionController] Failed to select ${kind} input device:`, error);
|
|
4944
5168
|
this._errors$.next(toError(error));
|
|
4945
5169
|
throw error;
|
|
4946
5170
|
}
|
|
@@ -4957,6 +5181,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
4957
5181
|
this._remoteDescription$ = this.createReplaySubject(1);
|
|
4958
5182
|
this._remoteStream$ = this.createBehaviorSubject(null);
|
|
4959
5183
|
this._remoteOfferMediaDirections = null;
|
|
5184
|
+
this._localAudioPipeline = null;
|
|
4960
5185
|
this.deviceController = deviceController ?? {};
|
|
4961
5186
|
this.id = options.callId ?? (0, uuid.v4)();
|
|
4962
5187
|
this._type = remoteSessionDescription ? "answer" : "offer";
|
|
@@ -5177,15 +5402,15 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5177
5402
|
this.setupPeerConnection();
|
|
5178
5403
|
this.subscribeTo(this.negotiationNeeded$.pipe((0, rxjs.auditTime)(0), (0, rxjs.exhaustMap)(async () => this.startNegotiation())), {
|
|
5179
5404
|
next: () => {
|
|
5180
|
-
logger$
|
|
5405
|
+
logger$16.debug("[RTCPeerConnectionController] Start Negotiation completed successfully");
|
|
5181
5406
|
},
|
|
5182
5407
|
error: (error) => {
|
|
5183
|
-
logger$
|
|
5408
|
+
logger$16.error("[RTCPeerConnectionController] Start Negotiation error:", error);
|
|
5184
5409
|
this._errors$.next(toError(error));
|
|
5185
5410
|
}
|
|
5186
5411
|
});
|
|
5187
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]) => {
|
|
5188
|
-
logger$
|
|
5413
|
+
logger$16.debug(`[RTCPeerConnectionController] Selected input device changed for:`, {
|
|
5189
5414
|
kind,
|
|
5190
5415
|
deviceInfo
|
|
5191
5416
|
});
|
|
@@ -5202,7 +5427,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5202
5427
|
this._initialized$.next(true);
|
|
5203
5428
|
}
|
|
5204
5429
|
} catch (error) {
|
|
5205
|
-
logger$
|
|
5430
|
+
logger$16.error("[RTCPeerConnectionController] Initialization error:", error);
|
|
5206
5431
|
this._errors$.next(toError(error));
|
|
5207
5432
|
this.destroy();
|
|
5208
5433
|
}
|
|
@@ -5234,22 +5459,22 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5234
5459
|
}
|
|
5235
5460
|
async startNegotiation() {
|
|
5236
5461
|
if (this.isNegotiating) {
|
|
5237
|
-
logger$
|
|
5462
|
+
logger$16.debug("[RTCPeerConnectionController] Negotiation already in progress, skipping.");
|
|
5238
5463
|
return;
|
|
5239
5464
|
}
|
|
5240
5465
|
this.setupEventListeners();
|
|
5241
5466
|
if (this.type === "answer") {
|
|
5242
|
-
logger$
|
|
5467
|
+
logger$16.debug("[RTCPeerConnectionController] This is an answer type still, skipping offer creation.");
|
|
5243
5468
|
return;
|
|
5244
5469
|
}
|
|
5245
5470
|
this._isNegotiating$.next(true);
|
|
5246
|
-
logger$
|
|
5471
|
+
logger$16.debug("[RTCPeerConnectionController] Starting negotiation.");
|
|
5247
5472
|
try {
|
|
5248
5473
|
const { offerOptions } = this;
|
|
5249
|
-
logger$
|
|
5474
|
+
logger$16.debug("[RTCPeerConnectionController] Creating offer with options:", offerOptions);
|
|
5250
5475
|
await this.createOffer(offerOptions);
|
|
5251
5476
|
} catch (error) {
|
|
5252
|
-
logger$
|
|
5477
|
+
logger$16.error("[RTCPeerConnectionController] Error during negotiation:", error);
|
|
5253
5478
|
this._errors$.next(toError(error));
|
|
5254
5479
|
}
|
|
5255
5480
|
}
|
|
@@ -5265,14 +5490,14 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5265
5490
|
let readyToConnect = status !== "failed";
|
|
5266
5491
|
try {
|
|
5267
5492
|
if (status === "received" && sdp) {
|
|
5268
|
-
logger$
|
|
5493
|
+
logger$16.debug("[RTCPeerConnectionController] Received answer SDP:", sdp);
|
|
5269
5494
|
await this._setRemoteDescription({
|
|
5270
5495
|
type: "answer",
|
|
5271
5496
|
sdp
|
|
5272
5497
|
});
|
|
5273
5498
|
}
|
|
5274
5499
|
} catch (error) {
|
|
5275
|
-
logger$
|
|
5500
|
+
logger$16.error("[RTCPeerConnectionController] Error updating answer status:", error);
|
|
5276
5501
|
this._errors$.next(toError(error));
|
|
5277
5502
|
readyToConnect = false;
|
|
5278
5503
|
} finally {
|
|
@@ -5291,7 +5516,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5291
5516
|
await this.handleOfferReceived();
|
|
5292
5517
|
break;
|
|
5293
5518
|
case "failed":
|
|
5294
|
-
logger$
|
|
5519
|
+
logger$16.error("[RTCPeerConnectionController] Offer failed to be processed by remote.");
|
|
5295
5520
|
break;
|
|
5296
5521
|
case "sent":
|
|
5297
5522
|
default:
|
|
@@ -5323,7 +5548,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5323
5548
|
}
|
|
5324
5549
|
await this.setupLocalTracks();
|
|
5325
5550
|
const { answerOptions } = this;
|
|
5326
|
-
logger$
|
|
5551
|
+
logger$16.debug("[RTCPeerConnectionController] Creating inbound answer with options:", answerOptions);
|
|
5327
5552
|
await this.createAnswer(answerOptions);
|
|
5328
5553
|
}
|
|
5329
5554
|
async handleOfferReceived() {
|
|
@@ -5331,7 +5556,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5331
5556
|
this._isNegotiating$.next(true);
|
|
5332
5557
|
await this._setRemoteDescription(this.sdpInit);
|
|
5333
5558
|
const { answerOptions } = this;
|
|
5334
|
-
logger$
|
|
5559
|
+
logger$16.debug("[RTCPeerConnectionController] Creating answer with options:", answerOptions);
|
|
5335
5560
|
await this.createAnswer(answerOptions);
|
|
5336
5561
|
}
|
|
5337
5562
|
readyToConnect() {
|
|
@@ -5339,7 +5564,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5339
5564
|
this.connectionTimer = setTimeout(() => {
|
|
5340
5565
|
this.removeConnectionTimer();
|
|
5341
5566
|
if (this.peerConnection?.connectionState !== "connected") {
|
|
5342
|
-
logger$
|
|
5567
|
+
logger$16.debug("[RTCPeerConnectionController] Connection timeout, restarting ICE gathering with relay only.");
|
|
5343
5568
|
this.iceGatheringController.restartICEGatheringWithRelayOnly();
|
|
5344
5569
|
}
|
|
5345
5570
|
}, this.connectionTimeout);
|
|
@@ -5361,14 +5586,14 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5361
5586
|
const stereo = this.options.stereo ?? PreferencesContainer.instance.stereoAudio;
|
|
5362
5587
|
if (preferredAudioCodecs.length > 0 || preferredVideoCodecs.length > 0) {
|
|
5363
5588
|
result = setCodecPreferences(result, preferredAudioCodecs, preferredVideoCodecs);
|
|
5364
|
-
logger$
|
|
5589
|
+
logger$16.debug("[RTCPeerConnectionController] Applied codec preferences to SDP", {
|
|
5365
5590
|
preferredAudioCodecs,
|
|
5366
5591
|
preferredVideoCodecs
|
|
5367
5592
|
});
|
|
5368
5593
|
}
|
|
5369
5594
|
if (stereo) {
|
|
5370
5595
|
result = enableStereoOpus(result);
|
|
5371
|
-
logger$
|
|
5596
|
+
logger$16.debug("[RTCPeerConnectionController] Applied stereo Opus to SDP");
|
|
5372
5597
|
}
|
|
5373
5598
|
return Promise.resolve(result);
|
|
5374
5599
|
}
|
|
@@ -5424,25 +5649,25 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5424
5649
|
...this.peerConnection.getConfiguration(),
|
|
5425
5650
|
iceTransportPolicy: "relay"
|
|
5426
5651
|
});
|
|
5427
|
-
logger$
|
|
5652
|
+
logger$16.debug("[RTCPeerConnectionController] ICE transport policy set to relay-only");
|
|
5428
5653
|
} catch (error) {
|
|
5429
|
-
logger$
|
|
5654
|
+
logger$16.warn("[RTCPeerConnectionController] Failed to set relay-only policy:", error);
|
|
5430
5655
|
}
|
|
5431
5656
|
this.setupEventListeners();
|
|
5432
5657
|
this._isNegotiating$.next(true);
|
|
5433
|
-
logger$
|
|
5658
|
+
logger$16.debug(`[RTCPeerConnectionController] Triggering ICE restart${relayOnly ? " (relay-only)" : ""}.`);
|
|
5434
5659
|
try {
|
|
5435
5660
|
const offer = await this.peerConnection.createOffer({ iceRestart: true });
|
|
5436
5661
|
await this.setLocalDescription(offer);
|
|
5437
5662
|
} catch (error) {
|
|
5438
|
-
logger$
|
|
5663
|
+
logger$16.error("[RTCPeerConnectionController] ICE restart offer failed:", error);
|
|
5439
5664
|
this._errors$.next(toError(error));
|
|
5440
5665
|
this.negotiationEnded();
|
|
5441
5666
|
if (policyChanged) this.restoreIceTransportPolicy();
|
|
5442
5667
|
throw error;
|
|
5443
5668
|
}
|
|
5444
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) => {
|
|
5445
|
-
logger$
|
|
5670
|
+
logger$16.warn("[RTCPeerConnectionController] Error waiting for ICE gathering to complete:", error);
|
|
5446
5671
|
this.restoreIceTransportPolicy();
|
|
5447
5672
|
});
|
|
5448
5673
|
}
|
|
@@ -5452,9 +5677,9 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5452
5677
|
...this.peerConnection.getConfiguration(),
|
|
5453
5678
|
iceTransportPolicy: this.options.relayOnly ? "relay" : "all"
|
|
5454
5679
|
});
|
|
5455
|
-
logger$
|
|
5680
|
+
logger$16.debug("[RTCPeerConnectionController] ICE transport policy restored");
|
|
5456
5681
|
} catch (error) {
|
|
5457
|
-
logger$
|
|
5682
|
+
logger$16.warn("[RTCPeerConnectionController] Failed to restore ICE transport policy:", error);
|
|
5458
5683
|
}
|
|
5459
5684
|
}
|
|
5460
5685
|
/**
|
|
@@ -5466,13 +5691,13 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5466
5691
|
await this.setupRemoteTracks();
|
|
5467
5692
|
}
|
|
5468
5693
|
async setupLocalTracks() {
|
|
5469
|
-
logger$
|
|
5694
|
+
logger$16.debug("[RTCPeerConnectionController] Setting up local tracks/transceivers.");
|
|
5470
5695
|
const localStream = this.localStream ?? await this.localStreamController.buildLocalStream();
|
|
5471
5696
|
if (this.transceiverController?.useAddStream ?? false) {
|
|
5472
|
-
logger$
|
|
5697
|
+
logger$16.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
|
|
5473
5698
|
this.peerConnection?.addStream(localStream);
|
|
5474
5699
|
if (!this.isNegotiating) {
|
|
5475
|
-
logger$
|
|
5700
|
+
logger$16.debug("[RTCPeerConnectionController] Forcing negotiationneeded after local tracks setup.");
|
|
5476
5701
|
this.negotiationNeeded$.next();
|
|
5477
5702
|
}
|
|
5478
5703
|
return;
|
|
@@ -5488,7 +5713,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5488
5713
|
const transceivers = (kind === "audio" ? this.transceiverController?.audioTransceivers : this.transceiverController?.videoTransceivers) ?? [];
|
|
5489
5714
|
await this.transceiverController?.setupTransceiverSender(track, localStream, transceivers[index]);
|
|
5490
5715
|
} else {
|
|
5491
|
-
logger$
|
|
5716
|
+
logger$16.debug(`[RTCPeerConnectionController] Using addTrack for local ${kind} track:`, track.id);
|
|
5492
5717
|
this.peerConnection?.addTrack(track, localStream);
|
|
5493
5718
|
}
|
|
5494
5719
|
}
|
|
@@ -5505,7 +5730,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5505
5730
|
async setupRemoteTracks() {
|
|
5506
5731
|
if (!this.peerConnection) throw new require_operators.DependencyError("RTCPeerConnection is not initialized");
|
|
5507
5732
|
this.peerConnection.ontrack = (event) => {
|
|
5508
|
-
logger$
|
|
5733
|
+
logger$16.debug("[RTCPeerConnectionController] Remote track received:", event.track.kind);
|
|
5509
5734
|
if (event.streams[0]) this._remoteStream$.next(event.streams[0]);
|
|
5510
5735
|
else {
|
|
5511
5736
|
const existingTracks = this._remoteStream$.value?.getTracks() ?? [];
|
|
@@ -5517,6 +5742,46 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5517
5742
|
}
|
|
5518
5743
|
async restoreTrackSender(kind) {
|
|
5519
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;
|
|
5776
|
+
this._localAudioPipeline.setInputTrack(raw ?? null);
|
|
5777
|
+
const [audioTransceiver] = this.transceiverController?.audioTransceivers ?? [];
|
|
5778
|
+
const sender = audioTransceiver?.sender ?? this.peerConnection.getSenders().find((s) => s.track?.kind === "audio");
|
|
5779
|
+
if (!sender || !raw) return;
|
|
5780
|
+
try {
|
|
5781
|
+
await sender.replaceTrack(this._localAudioPipeline.outputTrack);
|
|
5782
|
+
} catch (error) {
|
|
5783
|
+
logger$16.warn("[RTCPeerConnectionController] Failed to route audio sender through pipeline:", error);
|
|
5784
|
+
}
|
|
5520
5785
|
}
|
|
5521
5786
|
/**
|
|
5522
5787
|
* Add a local media track to the peer connection.
|
|
@@ -5531,9 +5796,9 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5531
5796
|
try {
|
|
5532
5797
|
const localStream = this.localStreamController.addTrack(track);
|
|
5533
5798
|
this.peerConnection.addTrack(track, localStream);
|
|
5534
|
-
logger$
|
|
5799
|
+
logger$16.debug(`[RTCPeerConnectionController] ${track.kind} track added:`, track.id);
|
|
5535
5800
|
} catch (error) {
|
|
5536
|
-
logger$
|
|
5801
|
+
logger$16.error(`[RTCPeerConnectionController] Failed to add ${track.kind} track:`, error);
|
|
5537
5802
|
this._errors$.next(toError(error));
|
|
5538
5803
|
throw error;
|
|
5539
5804
|
}
|
|
@@ -5550,15 +5815,15 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5550
5815
|
}
|
|
5551
5816
|
const sender = this.peerConnection.getSenders().find((sender$1) => sender$1.track?.id === trackId);
|
|
5552
5817
|
if (!sender) {
|
|
5553
|
-
logger$
|
|
5818
|
+
logger$16.debug(`[RTCPeerConnectionController] track not found: ${trackId}`);
|
|
5554
5819
|
return;
|
|
5555
5820
|
}
|
|
5556
5821
|
try {
|
|
5557
5822
|
this.peerConnection.removeTrack(sender);
|
|
5558
5823
|
this.localStreamController.removeTrack(trackId);
|
|
5559
|
-
logger$
|
|
5824
|
+
logger$16.debug(`[RTCPeerConnectionController] ${sender.track?.kind} track removed:`, trackId);
|
|
5560
5825
|
} catch (error) {
|
|
5561
|
-
logger$
|
|
5826
|
+
logger$16.error(`[RTCPeerConnectionController] Failed to remove ${sender.track?.kind} track:`, error);
|
|
5562
5827
|
this._errors$.next(toError(error));
|
|
5563
5828
|
throw error;
|
|
5564
5829
|
}
|
|
@@ -5585,7 +5850,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5585
5850
|
async replaceAudioTrackWithConstraints(constraints) {
|
|
5586
5851
|
const senders = this.peerConnection?.getSenders().filter((s) => s.track?.kind === "audio" && s.track.readyState === "live");
|
|
5587
5852
|
if (!senders || senders.length === 0) {
|
|
5588
|
-
logger$
|
|
5853
|
+
logger$16.warn("[RTCPeerConnectionController] No live audio sender to replace");
|
|
5589
5854
|
return;
|
|
5590
5855
|
}
|
|
5591
5856
|
for (const sender of senders) {
|
|
@@ -5603,7 +5868,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5603
5868
|
const newTrack = (await this.getUserMedia({ audio: mergedConstraints })).getAudioTracks()[0];
|
|
5604
5869
|
await sender.replaceTrack(newTrack);
|
|
5605
5870
|
this.localStreamController.addTrack(newTrack);
|
|
5606
|
-
logger$
|
|
5871
|
+
logger$16.debug(`[RTCPeerConnectionController] Audio track replaced for server-pushed params. New track: ${newTrack.id}`);
|
|
5607
5872
|
}
|
|
5608
5873
|
}
|
|
5609
5874
|
/**
|
|
@@ -5611,9 +5876,11 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5611
5876
|
* Completes all observables to prevent memory leaks.
|
|
5612
5877
|
*/
|
|
5613
5878
|
destroy() {
|
|
5614
|
-
logger$
|
|
5879
|
+
logger$16.debug(`[RTCPeerConnectionController] Destroying RTCPeerConnectionController. ${this.propose}`);
|
|
5615
5880
|
this.removeConnectionTimer();
|
|
5616
5881
|
this._iceGatheringController?.destroy();
|
|
5882
|
+
this._localAudioPipeline?.destroy();
|
|
5883
|
+
this._localAudioPipeline = null;
|
|
5617
5884
|
this.localStreamController.destroy();
|
|
5618
5885
|
this.transceiverController?.destroy();
|
|
5619
5886
|
if (this.peerConnection) {
|
|
@@ -5635,7 +5902,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5635
5902
|
}
|
|
5636
5903
|
stopRemoteTracks() {
|
|
5637
5904
|
this._remoteStream$.value?.getTracks().forEach((track) => {
|
|
5638
|
-
logger$
|
|
5905
|
+
logger$16.debug(`[RTCPeerConnectionController] Stopping remote track: ${track.kind}`);
|
|
5639
5906
|
track.stop();
|
|
5640
5907
|
});
|
|
5641
5908
|
}
|
|
@@ -5652,7 +5919,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5652
5919
|
...params,
|
|
5653
5920
|
sdp: finalRemote
|
|
5654
5921
|
};
|
|
5655
|
-
logger$
|
|
5922
|
+
logger$16.debug("[RTCPeerConnectionController] Setting remote description:", answer);
|
|
5656
5923
|
return this.peerConnection.setRemoteDescription(answer);
|
|
5657
5924
|
}
|
|
5658
5925
|
};
|
|
@@ -5690,7 +5957,7 @@ function isVertoPingInnerParams(value) {
|
|
|
5690
5957
|
|
|
5691
5958
|
//#endregion
|
|
5692
5959
|
//#region src/managers/VertoManager.ts
|
|
5693
|
-
const logger$
|
|
5960
|
+
const logger$15 = require_operators.getLogger();
|
|
5694
5961
|
var VertoManager = class extends Destroyable {
|
|
5695
5962
|
constructor(callSession) {
|
|
5696
5963
|
super();
|
|
@@ -5729,7 +5996,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
5729
5996
|
try {
|
|
5730
5997
|
await this.executeVerto(vertoModifyMessage);
|
|
5731
5998
|
} catch (error) {
|
|
5732
|
-
logger$
|
|
5999
|
+
logger$15.warn("[WebRTCManager] Call might already be disconnected, error sending Verto hold:", error);
|
|
5733
6000
|
throw error;
|
|
5734
6001
|
}
|
|
5735
6002
|
}
|
|
@@ -5742,7 +6009,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
5742
6009
|
try {
|
|
5743
6010
|
await this.executeVerto(vertoModifyMessage);
|
|
5744
6011
|
} catch (error) {
|
|
5745
|
-
logger$
|
|
6012
|
+
logger$15.warn("[WebRTCManager] Call might already be disconnected, error sending Verto unhold:", error);
|
|
5746
6013
|
throw error;
|
|
5747
6014
|
}
|
|
5748
6015
|
}
|
|
@@ -5795,7 +6062,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
5795
6062
|
if (event.member_id) this.setSelfIdIfNull(event.member_id);
|
|
5796
6063
|
});
|
|
5797
6064
|
this.subscribeTo(this.vertoMedia$, (event) => {
|
|
5798
|
-
logger$
|
|
6065
|
+
logger$15.debug("[WebRTCManager] Received Verto media event (early media SDP):", event);
|
|
5799
6066
|
this._signalingStatus$.next("ringing");
|
|
5800
6067
|
const { sdp, callID } = event;
|
|
5801
6068
|
this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
|
|
@@ -5804,7 +6071,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
5804
6071
|
});
|
|
5805
6072
|
});
|
|
5806
6073
|
this.subscribeTo(this.vertoAnswer$, (event) => {
|
|
5807
|
-
logger$
|
|
6074
|
+
logger$15.debug("[WebRTCManager] Received Verto answer event:", event);
|
|
5808
6075
|
this._signalingStatus$.next("connecting");
|
|
5809
6076
|
const { sdp, callID } = event;
|
|
5810
6077
|
this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
|
|
@@ -5813,7 +6080,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
5813
6080
|
});
|
|
5814
6081
|
});
|
|
5815
6082
|
this.subscribeTo(this.vertoMediaParams$, (event) => {
|
|
5816
|
-
logger$
|
|
6083
|
+
logger$15.debug("[WebRTCManager] Received Verto mediaParams event:", event);
|
|
5817
6084
|
const { mediaParams, callID } = event;
|
|
5818
6085
|
const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callID);
|
|
5819
6086
|
const { audio, video } = mediaParams;
|
|
@@ -5827,7 +6094,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
5827
6094
|
timestamp: Date.now()
|
|
5828
6095
|
});
|
|
5829
6096
|
} catch (error) {
|
|
5830
|
-
logger$
|
|
6097
|
+
logger$15.warn("[WebRTCManager] Error applying server-pushed media params:", error);
|
|
5831
6098
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
5832
6099
|
}
|
|
5833
6100
|
})();
|
|
@@ -5849,13 +6116,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
5849
6116
|
*/
|
|
5850
6117
|
setNodeIdIfNull(nodeId) {
|
|
5851
6118
|
if (!this._nodeId$.value && nodeId) {
|
|
5852
|
-
logger$
|
|
6119
|
+
logger$15.debug(`[WebRTCManager] Early node_id set: ${nodeId}`);
|
|
5853
6120
|
this._nodeId$.next(nodeId);
|
|
5854
6121
|
}
|
|
5855
6122
|
}
|
|
5856
6123
|
setSelfIdIfNull(selfId) {
|
|
5857
6124
|
if (!this._selfId$.value && selfId) {
|
|
5858
|
-
logger$
|
|
6125
|
+
logger$15.debug(`[WebRTCManager] Early selfId set: ${selfId}`);
|
|
5859
6126
|
this._selfId$.next(selfId);
|
|
5860
6127
|
}
|
|
5861
6128
|
}
|
|
@@ -5864,7 +6131,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
5864
6131
|
const vertoPongMessage = VertoPong({ ...vertoPing });
|
|
5865
6132
|
await this.executeVerto(vertoPongMessage);
|
|
5866
6133
|
} catch (error) {
|
|
5867
|
-
logger$
|
|
6134
|
+
logger$15.warn("[WebRTCManager] Call might disconnect, error sending Verto pong:", error);
|
|
5868
6135
|
this.onError?.(new require_operators.VertoPongError(error));
|
|
5869
6136
|
}
|
|
5870
6137
|
}
|
|
@@ -5874,7 +6141,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
5874
6141
|
if (audio) await this.mainPeerConnection.updateSendersConstraints("audio", audio);
|
|
5875
6142
|
if (video) await this.mainPeerConnection.updateSendersConstraints("video", video);
|
|
5876
6143
|
} catch (error) {
|
|
5877
|
-
logger$
|
|
6144
|
+
logger$15.warn("[WebRTCManager] Error updating media constraints:", error);
|
|
5878
6145
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
5879
6146
|
throw error;
|
|
5880
6147
|
}
|
|
@@ -5904,20 +6171,20 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
5904
6171
|
try {
|
|
5905
6172
|
const pc = this.mainPeerConnection.peerConnection;
|
|
5906
6173
|
if (!pc) {
|
|
5907
|
-
logger$
|
|
6174
|
+
logger$15.warn("[WebRTCManager] No peer connection for keyframe request");
|
|
5908
6175
|
return;
|
|
5909
6176
|
}
|
|
5910
6177
|
const videoReceiver = pc.getReceivers().find((r) => r.track.kind === "video");
|
|
5911
6178
|
if (!videoReceiver) {
|
|
5912
|
-
logger$
|
|
6179
|
+
logger$15.warn("[WebRTCManager] No video receiver for keyframe request");
|
|
5913
6180
|
return;
|
|
5914
6181
|
}
|
|
5915
6182
|
if (typeof videoReceiver.requestKeyFrame === "function") {
|
|
5916
6183
|
videoReceiver.requestKeyFrame();
|
|
5917
|
-
logger$
|
|
5918
|
-
} else logger$
|
|
6184
|
+
logger$15.debug("[WebRTCManager] Keyframe requested via RTCRtpReceiver.requestKeyFrame()");
|
|
6185
|
+
} else logger$15.debug("[WebRTCManager] requestKeyFrame() not supported, skipping");
|
|
5919
6186
|
} catch (error) {
|
|
5920
|
-
logger$
|
|
6187
|
+
logger$15.warn("[WebRTCManager] Keyframe request failed (non-fatal):", error);
|
|
5921
6188
|
}
|
|
5922
6189
|
}
|
|
5923
6190
|
/**
|
|
@@ -5935,13 +6202,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
5935
6202
|
try {
|
|
5936
6203
|
const controller = this.mainPeerConnection;
|
|
5937
6204
|
if (!controller.peerConnection) {
|
|
5938
|
-
logger$
|
|
6205
|
+
logger$15.warn("[WebRTCManager] No peer connection for ICE restart");
|
|
5939
6206
|
return;
|
|
5940
6207
|
}
|
|
5941
6208
|
await controller.triggerIceRestart(relayOnly);
|
|
5942
|
-
logger$
|
|
6209
|
+
logger$15.info(`[WebRTCManager] ICE restart initiated${relayOnly ? " (relay-only)" : ""}`);
|
|
5943
6210
|
} catch (error) {
|
|
5944
|
-
logger$
|
|
6211
|
+
logger$15.error("[WebRTCManager] ICE restart failed:", error);
|
|
5945
6212
|
throw error;
|
|
5946
6213
|
}
|
|
5947
6214
|
}
|
|
@@ -5959,13 +6226,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
5959
6226
|
const entries = Array.from(this._rtcPeerConnectionsMap.entries());
|
|
5960
6227
|
for (const [id, controller] of entries) try {
|
|
5961
6228
|
if (!controller.peerConnection) {
|
|
5962
|
-
logger$
|
|
6229
|
+
logger$15.debug(`[WebRTCManager] No peer connection for leg ${id}, skipping ICE restart`);
|
|
5963
6230
|
continue;
|
|
5964
6231
|
}
|
|
5965
6232
|
await controller.triggerIceRestart(relayOnly);
|
|
5966
|
-
logger$
|
|
6233
|
+
logger$15.info(`[WebRTCManager] ICE restart initiated for leg ${id}${relayOnly ? " (relay-only)" : ""}`);
|
|
5967
6234
|
} catch (error) {
|
|
5968
|
-
logger$
|
|
6235
|
+
logger$15.warn(`[WebRTCManager] ICE restart failed for leg ${id}:`, error);
|
|
5969
6236
|
}
|
|
5970
6237
|
}
|
|
5971
6238
|
/**
|
|
@@ -5977,7 +6244,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
5977
6244
|
requestKeyframeAll() {
|
|
5978
6245
|
for (const [id, controller] of this._rtcPeerConnectionsMap) {
|
|
5979
6246
|
if (controller.isScreenShare) {
|
|
5980
|
-
logger$
|
|
6247
|
+
logger$15.debug(`[WebRTCManager] Skipping keyframe for send-only screen share leg ${id}`);
|
|
5981
6248
|
continue;
|
|
5982
6249
|
}
|
|
5983
6250
|
try {
|
|
@@ -5987,10 +6254,10 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
5987
6254
|
if (!videoReceiver) continue;
|
|
5988
6255
|
if (typeof videoReceiver.requestKeyFrame === "function") {
|
|
5989
6256
|
videoReceiver.requestKeyFrame();
|
|
5990
|
-
logger$
|
|
6257
|
+
logger$15.debug(`[WebRTCManager] Keyframe requested for leg ${id}`);
|
|
5991
6258
|
}
|
|
5992
6259
|
} catch (error) {
|
|
5993
|
-
logger$
|
|
6260
|
+
logger$15.warn(`[WebRTCManager] Keyframe request failed for leg ${id} (non-fatal):`, error);
|
|
5994
6261
|
}
|
|
5995
6262
|
}
|
|
5996
6263
|
}
|
|
@@ -6051,7 +6318,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6051
6318
|
default:
|
|
6052
6319
|
}
|
|
6053
6320
|
} catch (error) {
|
|
6054
|
-
logger$
|
|
6321
|
+
logger$15.error(`[WebRTCManager] Error sending Verto ${vertoMethod}:`, error);
|
|
6055
6322
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
6056
6323
|
if (vertoMethod === "verto.modify") this.onModifyFailed?.();
|
|
6057
6324
|
}
|
|
@@ -6066,7 +6333,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6066
6333
|
sdp
|
|
6067
6334
|
});
|
|
6068
6335
|
} catch (error) {
|
|
6069
|
-
logger$
|
|
6336
|
+
logger$15.warn("[WebRTCManager] Error processing modify response:", error);
|
|
6070
6337
|
const modifyError = error instanceof Error ? error : new Error(String(error), { cause: error });
|
|
6071
6338
|
this.onError?.(modifyError);
|
|
6072
6339
|
}
|
|
@@ -6078,7 +6345,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6078
6345
|
this._nodeId$.next(require_operators.getValueFrom(response, "result.node_id") ?? null);
|
|
6079
6346
|
const memberId = require_operators.getValueFrom(response, "result.result.result.memberID") ?? null;
|
|
6080
6347
|
const callId = require_operators.getValueFrom(response, "result.result.result.callID") ?? null;
|
|
6081
|
-
logger$
|
|
6348
|
+
logger$15.debug("[WebRTCManager] Verto invite response:", {
|
|
6082
6349
|
callId,
|
|
6083
6350
|
memberId,
|
|
6084
6351
|
response
|
|
@@ -6088,14 +6355,14 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6088
6355
|
if (callId) {
|
|
6089
6356
|
this.webRtcCallSession.addCallId(callId);
|
|
6090
6357
|
this.attachManager.attach(this.buildAttachableCall(callId));
|
|
6091
|
-
} else logger$
|
|
6358
|
+
} else logger$15.warn("[WebRTCManager] Cannot attach call, missing callId:", {
|
|
6092
6359
|
nodeId: this.nodeId,
|
|
6093
6360
|
callId
|
|
6094
6361
|
});
|
|
6095
|
-
logger$
|
|
6096
|
-
logger$
|
|
6362
|
+
logger$15.info("[WebRTCManager] Verto invite successful");
|
|
6363
|
+
logger$15.debug(`[WebRTCManager] nodeid: ${this._nodeId$.value}, selfId: ${this._selfId$.value}`);
|
|
6097
6364
|
} else {
|
|
6098
|
-
logger$
|
|
6365
|
+
logger$15.error("[WebRTCManager] Verto invite failed:", response);
|
|
6099
6366
|
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");
|
|
6100
6367
|
this.onError?.(inviteError);
|
|
6101
6368
|
}
|
|
@@ -6140,17 +6407,17 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6140
6407
|
if (options.initOffer) this.handleInboundAnswer(rtcPeerConnController);
|
|
6141
6408
|
}
|
|
6142
6409
|
async handleInboundAnswer(rtcPeerConnController) {
|
|
6143
|
-
logger$
|
|
6410
|
+
logger$15.debug("[WebRTCManager] Waiting for inbound call to be accepted or rejected");
|
|
6144
6411
|
const vertoByeOrAccepted = await (0, rxjs.firstValueFrom)((0, rxjs.race)(this.vertoBye$, this.webRtcCallSession.answered$).pipe((0, rxjs.takeUntil)(this.destroyed$))).catch(() => null);
|
|
6145
6412
|
if (vertoByeOrAccepted === null) {
|
|
6146
|
-
logger$
|
|
6413
|
+
logger$15.debug("[WebRTCManager] Inbound answer handler aborted (destroyed).");
|
|
6147
6414
|
return;
|
|
6148
6415
|
}
|
|
6149
6416
|
if (isVertoByeMessage(vertoByeOrAccepted)) {
|
|
6150
|
-
logger$
|
|
6417
|
+
logger$15.info("[WebRTCManager] Inbound call ended by remote before answer.");
|
|
6151
6418
|
this.callSession?.destroy();
|
|
6152
6419
|
} else if (!vertoByeOrAccepted) {
|
|
6153
|
-
logger$
|
|
6420
|
+
logger$15.info("[WebRTCManager] Inbound call rejected by user.");
|
|
6154
6421
|
try {
|
|
6155
6422
|
await this.bye("USER_BUSY");
|
|
6156
6423
|
} finally {
|
|
@@ -6158,19 +6425,19 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6158
6425
|
this.callSession?.destroy();
|
|
6159
6426
|
}
|
|
6160
6427
|
} else {
|
|
6161
|
-
logger$
|
|
6428
|
+
logger$15.debug("[WebRTCManager] Inbound call accepted, creating SDP answer");
|
|
6162
6429
|
const answerOptions = this.webRtcCallSession.answerMediaOptions;
|
|
6163
6430
|
try {
|
|
6164
6431
|
await rtcPeerConnController.acceptInbound(answerOptions);
|
|
6165
6432
|
} catch (error) {
|
|
6166
|
-
logger$
|
|
6433
|
+
logger$15.error("[WebRTCManager] Error creating inbound answer:", error);
|
|
6167
6434
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
6168
6435
|
}
|
|
6169
6436
|
}
|
|
6170
6437
|
}
|
|
6171
6438
|
setupVertoAttachHandler() {
|
|
6172
6439
|
this.subscribeTo(this.vertoAttach$, async (vertoAttach) => {
|
|
6173
|
-
logger$
|
|
6440
|
+
logger$15.debug("[WebRTCManager] Received Verto attach event for existing call:", vertoAttach);
|
|
6174
6441
|
const { callID } = vertoAttach;
|
|
6175
6442
|
await this.attachManager.attach({
|
|
6176
6443
|
nodeId: this.nodeId ?? void 0,
|
|
@@ -6239,17 +6506,17 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6239
6506
|
};
|
|
6240
6507
|
}
|
|
6241
6508
|
async sendLocalDescriptionOnceAccepted(vertoMessageRequest, rtcPeerConnectionController) {
|
|
6242
|
-
logger$
|
|
6509
|
+
logger$15.debug("[WebRTCManager] Waiting for call to be accepted or ended before sending answer");
|
|
6243
6510
|
const vertoByeOrAccepted = await (0, rxjs.firstValueFrom)((0, rxjs.race)(this.vertoBye$, this.webRtcCallSession.answered$).pipe((0, rxjs.takeUntil)(this.destroyed$))).catch(() => null);
|
|
6244
6511
|
if (vertoByeOrAccepted === null) {
|
|
6245
|
-
logger$
|
|
6512
|
+
logger$15.debug("[WebRTCManager] Destroyed while waiting for call acceptance");
|
|
6246
6513
|
return;
|
|
6247
6514
|
}
|
|
6248
6515
|
if (isVertoByeMessage(vertoByeOrAccepted)) {
|
|
6249
|
-
logger$
|
|
6516
|
+
logger$15.info("[WebRTCManager] Call ended before answer was sent.");
|
|
6250
6517
|
this.callSession?.destroy();
|
|
6251
6518
|
} else if (!vertoByeOrAccepted) {
|
|
6252
|
-
logger$
|
|
6519
|
+
logger$15.info("[WebRTCManager] Call was not accepted, sending verto.bye.");
|
|
6253
6520
|
try {
|
|
6254
6521
|
await this.bye("USER_BUSY");
|
|
6255
6522
|
} finally {
|
|
@@ -6257,14 +6524,14 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6257
6524
|
this.callSession?.destroy();
|
|
6258
6525
|
}
|
|
6259
6526
|
} else {
|
|
6260
|
-
logger$
|
|
6527
|
+
logger$15.debug("[WebRTCManager] Call accepted, sending answer");
|
|
6261
6528
|
try {
|
|
6262
6529
|
this._signalingStatus$.next("connecting");
|
|
6263
6530
|
await this.sendLocalDescription(vertoMessageRequest, rtcPeerConnectionController);
|
|
6264
6531
|
await rtcPeerConnectionController.updateAnswerStatus({ status: "sent" });
|
|
6265
6532
|
await this.attachManager.attach(this.buildAttachableCall());
|
|
6266
6533
|
} catch (error) {
|
|
6267
|
-
logger$
|
|
6534
|
+
logger$15.error("[WebRTCManager] Error sending Verto answer:", error);
|
|
6268
6535
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
6269
6536
|
await rtcPeerConnectionController.updateAnswerStatus({ status: "failed" });
|
|
6270
6537
|
}
|
|
@@ -6305,6 +6572,14 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6305
6572
|
async unmuteMainVideoInputDevice() {
|
|
6306
6573
|
return this.mainPeerConnection.restoreTrackSender("video");
|
|
6307
6574
|
}
|
|
6575
|
+
/** Get or lazily create the local audio pipeline for the main peer connection. */
|
|
6576
|
+
ensureLocalAudioPipeline() {
|
|
6577
|
+
return this.mainPeerConnection.ensureLocalAudioPipeline();
|
|
6578
|
+
}
|
|
6579
|
+
/** The currently-active local audio pipeline, or null if it hasn't been created. */
|
|
6580
|
+
get localAudioPipeline() {
|
|
6581
|
+
return this.mainPeerConnection.localAudioPipeline;
|
|
6582
|
+
}
|
|
6308
6583
|
async addInputDevice(options = {
|
|
6309
6584
|
audio: false,
|
|
6310
6585
|
video: true
|
|
@@ -6355,10 +6630,10 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6355
6630
|
});
|
|
6356
6631
|
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$)));
|
|
6357
6632
|
this._screenShareStatus$.next("started");
|
|
6358
|
-
logger$
|
|
6633
|
+
logger$15.info("[WebRTCManager] Screen share started successfully.");
|
|
6359
6634
|
return rtcPeerConnController.id;
|
|
6360
6635
|
} catch (error) {
|
|
6361
|
-
logger$
|
|
6636
|
+
logger$15.warn("[WebRTCManager] Error initializing additional peer connection:", error);
|
|
6362
6637
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
6363
6638
|
if (rtcPeerConnController) rtcPeerConnController.destroy();
|
|
6364
6639
|
this._screenShareStatus$.next("none");
|
|
@@ -6377,9 +6652,9 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6377
6652
|
if (removeTrack) return this.mainPeerConnection.stopTrackSender(removeTrack, { updateTransceiverDirection: true });
|
|
6378
6653
|
}
|
|
6379
6654
|
async removeScreenMedia() {
|
|
6380
|
-
if (!["starting", "started"].includes(this._screenShareStatus$.value)) logger$
|
|
6655
|
+
if (!["starting", "started"].includes(this._screenShareStatus$.value)) logger$15.warn("[WebRTCManager] No active screen share to stop.");
|
|
6381
6656
|
if (!this._screenShareId) {
|
|
6382
|
-
logger$
|
|
6657
|
+
logger$15.debug("[WebRTCManager] No screen share peer connection found.");
|
|
6383
6658
|
return;
|
|
6384
6659
|
}
|
|
6385
6660
|
this._screenShareStatus$.next("stopping");
|
|
@@ -6408,7 +6683,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6408
6683
|
dialogParams: this.dialogParams(rtcPeerConnController)
|
|
6409
6684
|
}));
|
|
6410
6685
|
} catch (error) {
|
|
6411
|
-
logger$
|
|
6686
|
+
logger$15.warn("[WebRTCManager] Call might already be disconnected, error sending Verto bye:", error);
|
|
6412
6687
|
throw error;
|
|
6413
6688
|
}
|
|
6414
6689
|
}
|
|
@@ -6426,7 +6701,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6426
6701
|
try {
|
|
6427
6702
|
await this.executeVerto(vertoInfoMessage);
|
|
6428
6703
|
} catch (error) {
|
|
6429
|
-
logger$
|
|
6704
|
+
logger$15.warn("[WebRTCManager] Error sending DTMF digits:", error);
|
|
6430
6705
|
throw error;
|
|
6431
6706
|
}
|
|
6432
6707
|
}
|
|
@@ -6437,10 +6712,10 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6437
6712
|
action: "transfer"
|
|
6438
6713
|
});
|
|
6439
6714
|
try {
|
|
6440
|
-
logger$
|
|
6715
|
+
logger$15.debug("[WebRTCManager] Transferring call with options:", options);
|
|
6441
6716
|
await this.executeVerto(message);
|
|
6442
6717
|
} catch (error) {
|
|
6443
|
-
logger$
|
|
6718
|
+
logger$15.error("[WebRTCManager] Error transferring call:", error);
|
|
6444
6719
|
throw error;
|
|
6445
6720
|
}
|
|
6446
6721
|
}
|
|
@@ -6454,6 +6729,76 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6454
6729
|
}
|
|
6455
6730
|
};
|
|
6456
6731
|
|
|
6732
|
+
//#endregion
|
|
6733
|
+
//#region src/controllers/RemoteAudioMeter.ts
|
|
6734
|
+
const logger$14 = require_operators.getLogger();
|
|
6735
|
+
/**
|
|
6736
|
+
* Read-only audio level meter for a remote MediaStream. Attaches an
|
|
6737
|
+
* AnalyserNode to a MediaStreamAudioSourceNode so it observes the stream
|
|
6738
|
+
* without affecting the caller's playback path (no GainNode, no destination).
|
|
6739
|
+
*
|
|
6740
|
+
* The server delivers all remote audio as a single mixed stream — there is
|
|
6741
|
+
* no per-participant demux — so this meter reports the aggregate remote
|
|
6742
|
+
* level, not per-member.
|
|
6743
|
+
*/
|
|
6744
|
+
var RemoteAudioMeter = class extends Destroyable {
|
|
6745
|
+
constructor(options = {}) {
|
|
6746
|
+
super();
|
|
6747
|
+
this._source = null;
|
|
6748
|
+
this._stream = null;
|
|
6749
|
+
this._audioContext = (options.audioContextFactory ?? (() => new AudioContext()))();
|
|
6750
|
+
this._analyser = this._audioContext.createAnalyser();
|
|
6751
|
+
this._analyser.fftSize = 2048;
|
|
6752
|
+
this._analyser.smoothingTimeConstant = .3;
|
|
6753
|
+
this._analyserBuffer = new Uint8Array(new ArrayBuffer(this._analyser.fftSize));
|
|
6754
|
+
this._pollIntervalMs = options.pollIntervalMs ?? AUDIO_LEVEL_POLL_INTERVAL_MS;
|
|
6755
|
+
}
|
|
6756
|
+
/** RMS level of the remote audio, 0..1. 0 when no stream is attached. */
|
|
6757
|
+
get level$() {
|
|
6758
|
+
return this.deferEmission((0, rxjs.interval)(this._pollIntervalMs, rxjs.animationFrameScheduler).pipe((0, rxjs.map)(() => this.computeLevel())));
|
|
6759
|
+
}
|
|
6760
|
+
/**
|
|
6761
|
+
* Attach (or replace) the MediaStream whose audio track is being metered.
|
|
6762
|
+
* Pass null to detach without destroying the meter.
|
|
6763
|
+
*/
|
|
6764
|
+
setStream(stream) {
|
|
6765
|
+
if (this._source) {
|
|
6766
|
+
try {
|
|
6767
|
+
this._source.disconnect();
|
|
6768
|
+
} catch (error) {
|
|
6769
|
+
logger$14.debug("[RemoteAudioMeter] source disconnect warning:", error);
|
|
6770
|
+
}
|
|
6771
|
+
this._source = null;
|
|
6772
|
+
this._stream = null;
|
|
6773
|
+
}
|
|
6774
|
+
if (!stream || stream.getAudioTracks().length === 0) return;
|
|
6775
|
+
this._stream = new MediaStream(stream.getAudioTracks());
|
|
6776
|
+
this._source = this._audioContext.createMediaStreamSource(this._stream);
|
|
6777
|
+
}
|
|
6778
|
+
destroy() {
|
|
6779
|
+
if (this._source) {
|
|
6780
|
+
try {
|
|
6781
|
+
this._source.disconnect();
|
|
6782
|
+
} catch {}
|
|
6783
|
+
this._source = null;
|
|
6784
|
+
}
|
|
6785
|
+
this._audioContext.close().catch((error) => {
|
|
6786
|
+
logger$14.debug("[RemoteAudioMeter] audio context close warning:", error);
|
|
6787
|
+
});
|
|
6788
|
+
super.destroy();
|
|
6789
|
+
}
|
|
6790
|
+
computeLevel() {
|
|
6791
|
+
if (!this._source) return 0;
|
|
6792
|
+
this._analyser.getByteTimeDomainData(this._analyserBuffer);
|
|
6793
|
+
let sum = 0;
|
|
6794
|
+
for (const sample of this._analyserBuffer) {
|
|
6795
|
+
const normalized = (sample - 128) / 128;
|
|
6796
|
+
sum += normalized * normalized;
|
|
6797
|
+
}
|
|
6798
|
+
return Math.sqrt(sum / this._analyserBuffer.length);
|
|
6799
|
+
}
|
|
6800
|
+
};
|
|
6801
|
+
|
|
6457
6802
|
//#endregion
|
|
6458
6803
|
//#region src/controllers/RTCStatsMonitor.ts
|
|
6459
6804
|
/**
|
|
@@ -7270,6 +7615,8 @@ var WebRTCCall = class extends Destroyable {
|
|
|
7270
7615
|
this._bandwidthConstrained$ = this.createBehaviorSubject(false);
|
|
7271
7616
|
this._mediaParamsUpdated$ = this.createSubject();
|
|
7272
7617
|
this._customSubscriptions = /* @__PURE__ */ new Map();
|
|
7618
|
+
this._pushToTalkEnabled = false;
|
|
7619
|
+
this._remoteAudioMeter = null;
|
|
7273
7620
|
this.id = options.callId ?? (0, uuid.v4)();
|
|
7274
7621
|
this.to = options.to;
|
|
7275
7622
|
this._userVariables$.next({
|
|
@@ -8041,11 +8388,129 @@ var WebRTCCall = class extends Destroyable {
|
|
|
8041
8388
|
async transfer(options) {
|
|
8042
8389
|
return this.vertoManager.transfer(options);
|
|
8043
8390
|
}
|
|
8391
|
+
/**
|
|
8392
|
+
* Set the local microphone gain as a percentage applied before transmission.
|
|
8393
|
+
*
|
|
8394
|
+
* - `0` = silent
|
|
8395
|
+
* - `100` = unity (no change, default)
|
|
8396
|
+
* - `200` = 2× digital boost (max; expect clipping / noise amplification)
|
|
8397
|
+
*
|
|
8398
|
+
* Values are clamped to [0, 200]. Engages the local audio pipeline on
|
|
8399
|
+
* first use (one-time cost).
|
|
8400
|
+
*
|
|
8401
|
+
* Note: this is a **digital** multiplier applied in a Web Audio GainNode
|
|
8402
|
+
* between your mic track and the RTCRtpSender — it does not change the
|
|
8403
|
+
* physical mic's hardware sensitivity. Browsers' autoGainControl can
|
|
8404
|
+
* fight the setting; call {@link setAutoGainControl}(false) for
|
|
8405
|
+
* predictable behaviour.
|
|
8406
|
+
*
|
|
8407
|
+
* @param value - Gain percentage (0..200; 100 = unity).
|
|
8408
|
+
*/
|
|
8409
|
+
setLocalMicrophoneGain(value) {
|
|
8410
|
+
const pipeline = this.vertoManager.ensureLocalAudioPipeline();
|
|
8411
|
+
if (!pipeline) {
|
|
8412
|
+
logger$11.warn("[Call] setLocalMicrophoneGain: audio pipeline unavailable");
|
|
8413
|
+
return;
|
|
8414
|
+
}
|
|
8415
|
+
const percent = Math.max(0, Math.min(200, value));
|
|
8416
|
+
pipeline.setGain(percent / 100);
|
|
8417
|
+
}
|
|
8418
|
+
/** Observable of the current local microphone gain (0..200, where 100 = unity). */
|
|
8419
|
+
get localMicrophoneGain$() {
|
|
8420
|
+
const pipeline = this.vertoManager.ensureLocalAudioPipeline();
|
|
8421
|
+
if (!pipeline) return (0, rxjs.of)(100).pipe((0, rxjs.takeUntil)(this._destroyed$));
|
|
8422
|
+
return pipeline.gain$.pipe((0, rxjs.map)((multiplier) => multiplier * 100), (0, rxjs.takeUntil)(this._destroyed$));
|
|
8423
|
+
}
|
|
8424
|
+
/**
|
|
8425
|
+
* Observable of the RMS audio level of the local microphone, 0..1.
|
|
8426
|
+
* Emits at ~30fps while a mic track is active. Engages the local audio
|
|
8427
|
+
* pipeline on first subscription.
|
|
8428
|
+
*/
|
|
8429
|
+
get localAudioLevel$() {
|
|
8430
|
+
return this.vertoManager.ensureLocalAudioPipeline()?.level$ ?? (0, rxjs.of)(0).pipe((0, rxjs.takeUntil)(this._destroyed$));
|
|
8431
|
+
}
|
|
8432
|
+
/**
|
|
8433
|
+
* Observable that is `true` while the local participant is speaking
|
|
8434
|
+
* (RMS level above the VAD threshold, with hold time to avoid flicker).
|
|
8435
|
+
*/
|
|
8436
|
+
get localSpeaking$() {
|
|
8437
|
+
return this.vertoManager.ensureLocalAudioPipeline()?.speaking$ ?? (0, rxjs.of)(false).pipe((0, rxjs.takeUntil)(this._destroyed$));
|
|
8438
|
+
}
|
|
8439
|
+
/**
|
|
8440
|
+
* Enable push-to-talk: while {@link setPushToTalkActive} has been called
|
|
8441
|
+
* with `false`, the microphone gain is forced to 0; calling
|
|
8442
|
+
* {@link setPushToTalkActive} with `true` restores the configured gain.
|
|
8443
|
+
* Use this instead of mute/unmute for instant talk/silence transitions
|
|
8444
|
+
* because it doesn't rebuild the track.
|
|
8445
|
+
*
|
|
8446
|
+
* This method installs the pipeline but does not attach any keyboard
|
|
8447
|
+
* listener — consumers bind the key themselves and call
|
|
8448
|
+
* {@link setPushToTalkActive} on keydown/keyup.
|
|
8449
|
+
*/
|
|
8450
|
+
enablePushToTalk() {
|
|
8451
|
+
const pipeline = this.vertoManager.ensureLocalAudioPipeline();
|
|
8452
|
+
if (!pipeline) {
|
|
8453
|
+
logger$11.warn("[Call] enablePushToTalk: audio pipeline unavailable");
|
|
8454
|
+
return;
|
|
8455
|
+
}
|
|
8456
|
+
pipeline.setPTTActive(false);
|
|
8457
|
+
this._pushToTalkEnabled = true;
|
|
8458
|
+
}
|
|
8459
|
+
/** Disable push-to-talk; mic gain returns to the configured value. */
|
|
8460
|
+
disablePushToTalk() {
|
|
8461
|
+
this.vertoManager.localAudioPipeline?.setPTTActive(true);
|
|
8462
|
+
this._pushToTalkEnabled = false;
|
|
8463
|
+
}
|
|
8464
|
+
/**
|
|
8465
|
+
* While push-to-talk is enabled, sets the talk state. `true` = transmitting,
|
|
8466
|
+
* `false` = silent. No-op if push-to-talk has not been enabled.
|
|
8467
|
+
*/
|
|
8468
|
+
setPushToTalkActive(active) {
|
|
8469
|
+
if (!this._pushToTalkEnabled) return;
|
|
8470
|
+
this.vertoManager.localAudioPipeline?.setPTTActive(active);
|
|
8471
|
+
}
|
|
8472
|
+
/**
|
|
8473
|
+
* Toggle echo cancellation on the local mic at runtime. Applied via
|
|
8474
|
+
* `track.applyConstraints`; browsers that don't honour runtime constraints
|
|
8475
|
+
* (notably iOS Safari) fall back to re-acquiring the track with the new
|
|
8476
|
+
* constraint set and plumbing the replacement through the local audio
|
|
8477
|
+
* pipeline if one is active.
|
|
8478
|
+
*/
|
|
8479
|
+
async setEchoCancellation(enabled) {
|
|
8480
|
+
await this.vertoManager.updateMediaConstraints({ audio: { echoCancellation: enabled } });
|
|
8481
|
+
}
|
|
8482
|
+
/** Toggle browser noise suppression on the local mic at runtime. */
|
|
8483
|
+
async setNoiseSuppression(enabled) {
|
|
8484
|
+
await this.vertoManager.updateMediaConstraints({ audio: { noiseSuppression: enabled } });
|
|
8485
|
+
}
|
|
8486
|
+
/** Toggle browser automatic gain control on the local mic at runtime. */
|
|
8487
|
+
async setAutoGainControl(enabled) {
|
|
8488
|
+
await this.vertoManager.updateMediaConstraints({ audio: { autoGainControl: enabled } });
|
|
8489
|
+
}
|
|
8490
|
+
/**
|
|
8491
|
+
* Observable of the aggregate remote audio level, 0..1 RMS. The server
|
|
8492
|
+
* delivers a single mixed audio stream for all remote participants — this
|
|
8493
|
+
* meter reports that mix. Per-participant audio is not available client-side.
|
|
8494
|
+
*
|
|
8495
|
+
* Engages a shared AudioContext on first subscription (cheap — one
|
|
8496
|
+
* AnalyserNode, no GainNode, no destination) so it does not affect the
|
|
8497
|
+
* caller's audio element playback.
|
|
8498
|
+
*/
|
|
8499
|
+
get remoteAudioLevel$() {
|
|
8500
|
+
this._remoteAudioMeter ??= new RemoteAudioMeter();
|
|
8501
|
+
const meter = this._remoteAudioMeter;
|
|
8502
|
+
this.subscribeTo(this.vertoManager.remoteStream$, (stream) => {
|
|
8503
|
+
meter.setStream(stream);
|
|
8504
|
+
});
|
|
8505
|
+
return meter.level$.pipe((0, rxjs.takeUntil)(this._destroyed$));
|
|
8506
|
+
}
|
|
8044
8507
|
/** Destroys the call, releasing all resources and subscriptions. */
|
|
8045
8508
|
destroy() {
|
|
8046
8509
|
if (this._status$.value === "destroyed") return;
|
|
8047
8510
|
this._status$.next("destroyed");
|
|
8048
8511
|
this.stopResilienceSubsystems();
|
|
8512
|
+
this._remoteAudioMeter?.destroy();
|
|
8513
|
+
this._remoteAudioMeter = null;
|
|
8049
8514
|
this.vertoManager.destroy();
|
|
8050
8515
|
this.callEventsManager.destroy();
|
|
8051
8516
|
super.destroy();
|
|
@@ -10696,6 +11161,36 @@ var SignalWire = class extends Destroyable {
|
|
|
10696
11161
|
selectAudioOutputDevice(device) {
|
|
10697
11162
|
this._deviceController.selectAudioOutputDevice(device);
|
|
10698
11163
|
}
|
|
11164
|
+
/**
|
|
11165
|
+
* Apply the currently selected audio output device to an HTMLMediaElement
|
|
11166
|
+
* (e.g. the `<audio>` or `<video>` element the consumer attached the
|
|
11167
|
+
* remote stream to). Uses `HTMLMediaElement.setSinkId` under the hood.
|
|
11168
|
+
* Returns a `Promise<boolean>`: `true` if the sink was applied,
|
|
11169
|
+
* `false` if the browser doesn't support `setSinkId` or no device is
|
|
11170
|
+
* selected.
|
|
11171
|
+
*
|
|
11172
|
+
* @example
|
|
11173
|
+
* ```ts
|
|
11174
|
+
* audioEl.srcObject = call.remoteStream;
|
|
11175
|
+
* await client.applySelectedAudioOutputDevice(audioEl);
|
|
11176
|
+
* ```
|
|
11177
|
+
*/
|
|
11178
|
+
async applySelectedAudioOutputDevice(element) {
|
|
11179
|
+
const device = this._deviceController.selectedAudioOutputDevice;
|
|
11180
|
+
if (!device?.deviceId) return false;
|
|
11181
|
+
const withSink = element;
|
|
11182
|
+
if (typeof withSink.setSinkId !== "function") {
|
|
11183
|
+
logger$1.warn("[SignalWire] setSinkId not supported on this element / browser");
|
|
11184
|
+
return false;
|
|
11185
|
+
}
|
|
11186
|
+
try {
|
|
11187
|
+
await withSink.setSinkId(device.deviceId);
|
|
11188
|
+
return true;
|
|
11189
|
+
} catch (error) {
|
|
11190
|
+
logger$1.warn("[SignalWire] Failed to apply audio output device:", error);
|
|
11191
|
+
return false;
|
|
11192
|
+
}
|
|
11193
|
+
}
|
|
10699
11194
|
/** Starts monitoring for media device changes (connect/disconnect). */
|
|
10700
11195
|
enableDeviceMonitoring() {
|
|
10701
11196
|
this._deviceController.enableDeviceMonitoring();
|