@signalwire/js 4.0.0-dev-20260309151622 → 4.0.0-dev-20260311174824
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 +146 -18
- package/dist/browser.mjs.map +1 -1
- package/dist/browser.umd.js +146 -18
- package/dist/browser.umd.js.map +1 -1
- package/dist/index.cjs +144 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +14 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +145 -17
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/browser.mjs
CHANGED
|
@@ -6915,7 +6915,7 @@ var require_startWith = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
6915
6915
|
var concat_1$2 = require_concat$1();
|
|
6916
6916
|
var args_1$2 = require_args();
|
|
6917
6917
|
var lift_1$14 = require_lift();
|
|
6918
|
-
function startWith() {
|
|
6918
|
+
function startWith$1() {
|
|
6919
6919
|
var values = [];
|
|
6920
6920
|
for (var _i = 0; _i < arguments.length; _i++) values[_i] = arguments[_i];
|
|
6921
6921
|
var scheduler = args_1$2.popScheduler(values);
|
|
@@ -6923,7 +6923,7 @@ var require_startWith = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
6923
6923
|
(scheduler ? concat_1$2.concat(values, source, scheduler) : concat_1$2.concat(values, source)).subscribe(subscriber);
|
|
6924
6924
|
});
|
|
6925
6925
|
}
|
|
6926
|
-
exports.startWith = startWith;
|
|
6926
|
+
exports.startWith = startWith$1;
|
|
6927
6927
|
}));
|
|
6928
6928
|
|
|
6929
6929
|
//#endregion
|
|
@@ -9669,7 +9669,7 @@ var PreferencesContainer = class PreferencesContainer {
|
|
|
9669
9669
|
skipDeviceMonitoring: false,
|
|
9670
9670
|
savePreferences: false
|
|
9671
9671
|
};
|
|
9672
|
-
this.receiveVideo =
|
|
9672
|
+
this.receiveVideo = false;
|
|
9673
9673
|
this.receiveAudio = true;
|
|
9674
9674
|
this.preferredAudioInput = null;
|
|
9675
9675
|
this.preferredAudioOutput = null;
|
|
@@ -12639,12 +12639,53 @@ var CallEventsManager = class extends Destroyable {
|
|
|
12639
12639
|
|
|
12640
12640
|
//#endregion
|
|
12641
12641
|
//#region src/helpers/SDPHelper.ts
|
|
12642
|
+
/** Valid SDP direction attribute values. */
|
|
12643
|
+
const SDP_DIRECTIONS = new Set([
|
|
12644
|
+
"sendrecv",
|
|
12645
|
+
"sendonly",
|
|
12646
|
+
"recvonly",
|
|
12647
|
+
"inactive"
|
|
12648
|
+
]);
|
|
12642
12649
|
/**
|
|
12643
|
-
*
|
|
12650
|
+
* Extracts the media directions (audio/video) from an SDP string.
|
|
12644
12651
|
*
|
|
12645
|
-
*
|
|
12646
|
-
*
|
|
12652
|
+
* Parses each media section (`m=audio` / `m=video`) and reads the `a=` direction
|
|
12653
|
+
* attribute (`sendrecv`, `sendonly`, `recvonly`, `inactive`).
|
|
12654
|
+
* If no explicit direction attribute is found for a media section, defaults to `sendrecv`
|
|
12655
|
+
* per RFC 4566.
|
|
12656
|
+
*
|
|
12657
|
+
* @param sdp - The SDP string to parse
|
|
12658
|
+
* @returns The extracted audio and video directions
|
|
12659
|
+
*
|
|
12660
|
+
* @example
|
|
12661
|
+
* ```typescript
|
|
12662
|
+
* const sdp = `v=0\r\nm=audio 9 UDP/TLS/RTP/SAVPF 111\r\na=sendrecv\r\nm=video 9 UDP/TLS/RTP/SAVPF 96\r\na=recvonly`;
|
|
12663
|
+
* extractMediaDirectionsFromSDP(sdp);
|
|
12664
|
+
* // { audio: 'sendrecv', video: 'recvonly' }
|
|
12665
|
+
* ```
|
|
12647
12666
|
*/
|
|
12667
|
+
function extractMediaDirectionsFromSDP(sdp) {
|
|
12668
|
+
const result = {
|
|
12669
|
+
audio: "inactive",
|
|
12670
|
+
video: "inactive"
|
|
12671
|
+
};
|
|
12672
|
+
if (!sdp) return result;
|
|
12673
|
+
const lines = sdp.split(/\r?\n/);
|
|
12674
|
+
let currentMediaKind = null;
|
|
12675
|
+
let currentDirection = null;
|
|
12676
|
+
for (const line of lines) if (line.startsWith("m=")) {
|
|
12677
|
+
if (currentMediaKind) result[currentMediaKind] = currentDirection ?? "sendrecv";
|
|
12678
|
+
if (line.startsWith("m=audio")) currentMediaKind = "audio";
|
|
12679
|
+
else if (line.startsWith("m=video")) currentMediaKind = "video";
|
|
12680
|
+
else currentMediaKind = null;
|
|
12681
|
+
currentDirection = null;
|
|
12682
|
+
} else if (currentMediaKind && line.startsWith("a=")) {
|
|
12683
|
+
const attr = line.substring(2).trim();
|
|
12684
|
+
if (SDP_DIRECTIONS.has(attr)) currentDirection = attr;
|
|
12685
|
+
}
|
|
12686
|
+
if (currentMediaKind) result[currentMediaKind] = currentDirection ?? "sendrecv";
|
|
12687
|
+
return result;
|
|
12688
|
+
}
|
|
12648
12689
|
/**
|
|
12649
12690
|
* Validates that an SDP string has at least one non-host ICE candidate
|
|
12650
12691
|
* for each media section (m= line).
|
|
@@ -12947,6 +12988,15 @@ var LocalStreamController = class extends Destroyable {
|
|
|
12947
12988
|
track.addEventListener("ended", this.mediaTrackEndedHandler);
|
|
12948
12989
|
}
|
|
12949
12990
|
/**
|
|
12991
|
+
* Update the controller options (e.g., when media overrides are applied).
|
|
12992
|
+
*/
|
|
12993
|
+
updateOptions(options) {
|
|
12994
|
+
this.options = {
|
|
12995
|
+
...this.options,
|
|
12996
|
+
...options
|
|
12997
|
+
};
|
|
12998
|
+
}
|
|
12999
|
+
/**
|
|
12950
13000
|
* Stop all local tracks and clean up.
|
|
12951
13001
|
*/
|
|
12952
13002
|
stopAllTracks() {
|
|
@@ -13290,6 +13340,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
13290
13340
|
this._initialized$ = this.createReplaySubject(1);
|
|
13291
13341
|
this._remoteDescription$ = this.createReplaySubject(1);
|
|
13292
13342
|
this._remoteStream$ = this.createBehaviorSubject(null);
|
|
13343
|
+
this._remoteOfferMediaDirections = null;
|
|
13293
13344
|
this.deviceController = deviceController ?? {};
|
|
13294
13345
|
this.id = options.callId ?? v4_default();
|
|
13295
13346
|
this._type = remoteSessionDescription ? "answer" : "offer";
|
|
@@ -13297,10 +13348,19 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
13297
13348
|
type: "offer",
|
|
13298
13349
|
sdp: remoteSessionDescription
|
|
13299
13350
|
} : void 0;
|
|
13351
|
+
this._remoteOfferMediaDirections = remoteSessionDescription ? extractMediaDirectionsFromSDP(remoteSessionDescription) : null;
|
|
13352
|
+
const offerDefaults = this._remoteOfferMediaDirections ? {
|
|
13353
|
+
audio: this._remoteOfferMediaDirections.audio.includes("recv"),
|
|
13354
|
+
video: this._remoteOfferMediaDirections.video.includes("recv"),
|
|
13355
|
+
receiveAudio: this._remoteOfferMediaDirections.audio.includes("send"),
|
|
13356
|
+
receiveVideo: this._remoteOfferMediaDirections.video.includes("send")
|
|
13357
|
+
} : {};
|
|
13300
13358
|
this.options = {
|
|
13301
|
-
|
|
13302
|
-
|
|
13303
|
-
|
|
13359
|
+
...options,
|
|
13360
|
+
audio: options.audio ?? offerDefaults.audio,
|
|
13361
|
+
video: options.video ?? offerDefaults.video,
|
|
13362
|
+
receiveAudio: options.receiveAudio ?? offerDefaults.receiveAudio ?? PreferencesContainer.instance.receiveAudio,
|
|
13363
|
+
receiveVideo: options.receiveVideo ?? offerDefaults.receiveVideo ?? PreferencesContainer.instance.receiveVideo
|
|
13304
13364
|
};
|
|
13305
13365
|
this.localStreamController = new LocalStreamController({
|
|
13306
13366
|
propose: this.propose,
|
|
@@ -13467,12 +13527,12 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
13467
13527
|
default: return {
|
|
13468
13528
|
...options,
|
|
13469
13529
|
offerToReceiveAudio: true,
|
|
13470
|
-
offerToReceiveVideo: Boolean(this.inputVideoDeviceConstraints)
|
|
13530
|
+
offerToReceiveVideo: this.options.receiveVideo ?? Boolean(this.inputVideoDeviceConstraints)
|
|
13471
13531
|
};
|
|
13472
13532
|
}
|
|
13473
13533
|
}
|
|
13474
13534
|
get answerOptions() {
|
|
13475
|
-
return {};
|
|
13535
|
+
return { iceRestart: this.firstSDPExchangeCompleted ? true : void 0 };
|
|
13476
13536
|
}
|
|
13477
13537
|
/**
|
|
13478
13538
|
* Initialize the RTCPeerConnection and setup event listeners.
|
|
@@ -13507,11 +13567,15 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
13507
13567
|
});
|
|
13508
13568
|
await this.updateSelectedInputDevice(kind, deviceInfo);
|
|
13509
13569
|
});
|
|
13510
|
-
await this.setupTrackHandling();
|
|
13511
|
-
this._initialized$.next(true);
|
|
13512
13570
|
if (this.type === "answer" && this.sdpInit) {
|
|
13571
|
+
await this.setupRemoteTracks();
|
|
13572
|
+
this._initialized$.next(true);
|
|
13513
13573
|
this.setupEventListeners();
|
|
13514
|
-
|
|
13574
|
+
this._isNegotiating$.next(true);
|
|
13575
|
+
await this._setRemoteDescription(this.sdpInit);
|
|
13576
|
+
} else {
|
|
13577
|
+
await this.setupTrackHandling();
|
|
13578
|
+
this._initialized$.next(true);
|
|
13515
13579
|
}
|
|
13516
13580
|
} catch (error) {
|
|
13517
13581
|
logger$11.error("[RTCPeerConnectionController] Initialization error:", error);
|
|
@@ -13609,6 +13673,35 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
13609
13673
|
default:
|
|
13610
13674
|
}
|
|
13611
13675
|
}
|
|
13676
|
+
/**
|
|
13677
|
+
* Accept an inbound call by creating the SDP answer.
|
|
13678
|
+
* Optionally override media options before the answer is generated.
|
|
13679
|
+
* Must be called after initialization for inbound (answer-type) connections.
|
|
13680
|
+
*/
|
|
13681
|
+
async acceptInbound(mediaOverrides) {
|
|
13682
|
+
if (mediaOverrides) {
|
|
13683
|
+
const { audio, video, receiveAudio, receiveVideo } = mediaOverrides;
|
|
13684
|
+
this.options = {
|
|
13685
|
+
...this.options,
|
|
13686
|
+
...audio !== void 0 ? { audio } : {},
|
|
13687
|
+
...video !== void 0 ? { video } : {},
|
|
13688
|
+
...receiveAudio !== void 0 ? { receiveAudio } : {},
|
|
13689
|
+
...receiveVideo !== void 0 ? { receiveVideo } : {}
|
|
13690
|
+
};
|
|
13691
|
+
this.transceiverController?.updateOptions({
|
|
13692
|
+
receiveAudio: this.receiveAudio,
|
|
13693
|
+
receiveVideo: this.receiveVideo
|
|
13694
|
+
});
|
|
13695
|
+
this.localStreamController.updateOptions({
|
|
13696
|
+
inputAudioDeviceConstraints: this.inputAudioDeviceConstraints,
|
|
13697
|
+
inputVideoDeviceConstraints: this.inputVideoDeviceConstraints
|
|
13698
|
+
});
|
|
13699
|
+
}
|
|
13700
|
+
await this.setupLocalTracks();
|
|
13701
|
+
const { answerOptions } = this;
|
|
13702
|
+
logger$11.debug("[RTCPeerConnectionController] Creating inbound answer with options:", answerOptions);
|
|
13703
|
+
await this.createAnswer(answerOptions);
|
|
13704
|
+
}
|
|
13612
13705
|
async handleOfferReceived() {
|
|
13613
13706
|
if (!this.sdpInit) throw new DependencyError("SDP initialization parameters are not set");
|
|
13614
13707
|
this._isNegotiating$.next(true);
|
|
@@ -13829,7 +13922,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
13829
13922
|
});
|
|
13830
13923
|
}
|
|
13831
13924
|
get mediaDirections() {
|
|
13832
|
-
return this.transceiverController?.getMediaDirections() ?? {
|
|
13925
|
+
return this.transceiverController?.getMediaDirections() ?? this._remoteOfferMediaDirections ?? {
|
|
13833
13926
|
audio: "inactive",
|
|
13834
13927
|
video: "inactive"
|
|
13835
13928
|
};
|
|
@@ -14160,6 +14253,36 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
14160
14253
|
this.subscribeTo(rtcPeerConnController.errors$, (error) => {
|
|
14161
14254
|
this.onError?.(error);
|
|
14162
14255
|
});
|
|
14256
|
+
if (options.initOffer) this.handleInboundAnswer(rtcPeerConnController);
|
|
14257
|
+
}
|
|
14258
|
+
async handleInboundAnswer(rtcPeerConnController) {
|
|
14259
|
+
logger$10.debug("[WebRTCManager] Waiting for inbound call to be accepted or rejected");
|
|
14260
|
+
const vertoByeOrAccepted = await (0, import_cjs$10.firstValueFrom)((0, import_cjs$10.race)(this.vertoBye$, this.webRtcCallSession.answered$).pipe((0, import_cjs$10.takeUntil)(this.destroyed$))).catch(() => null);
|
|
14261
|
+
if (vertoByeOrAccepted === null) {
|
|
14262
|
+
logger$10.debug("[WebRTCManager] Inbound answer handler aborted (destroyed).");
|
|
14263
|
+
return;
|
|
14264
|
+
}
|
|
14265
|
+
if (isVertoByeMessage(vertoByeOrAccepted)) {
|
|
14266
|
+
logger$10.info("[WebRTCManager] Inbound call ended by remote before answer.");
|
|
14267
|
+
this.callSession?.destroy();
|
|
14268
|
+
} else if (!vertoByeOrAccepted) {
|
|
14269
|
+
logger$10.info("[WebRTCManager] Inbound call rejected by user.");
|
|
14270
|
+
try {
|
|
14271
|
+
await this.bye("USER_BUSY");
|
|
14272
|
+
} finally {
|
|
14273
|
+
this._signalingStatus$.next("disconnected");
|
|
14274
|
+
this.callSession?.destroy();
|
|
14275
|
+
}
|
|
14276
|
+
} else {
|
|
14277
|
+
logger$10.debug("[WebRTCManager] Inbound call accepted, creating SDP answer");
|
|
14278
|
+
const answerOptions = this.webRtcCallSession.answerMediaOptions;
|
|
14279
|
+
try {
|
|
14280
|
+
await rtcPeerConnController.acceptInbound(answerOptions);
|
|
14281
|
+
} catch (error) {
|
|
14282
|
+
logger$10.error("[WebRTCManager] Error creating inbound answer:", error);
|
|
14283
|
+
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
14284
|
+
}
|
|
14285
|
+
}
|
|
14163
14286
|
}
|
|
14164
14287
|
setupVertoAttachHandler() {
|
|
14165
14288
|
this.subscribeTo(this.vertoAttach$, async (vertoAttach) => {
|
|
@@ -14177,7 +14300,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
14177
14300
|
});
|
|
14178
14301
|
}
|
|
14179
14302
|
initObservables(rtcPeerConnController) {
|
|
14180
|
-
this.mediaDirections$ = rtcPeerConnController.connectionState$.pipe((0, import_cjs$10.filter)((state) => state === "connected"), (0, import_cjs$10.
|
|
14303
|
+
this.mediaDirections$ = rtcPeerConnController.connectionState$.pipe((0, import_cjs$10.filter)((state) => state === "connected"), (0, import_cjs$10.map)(() => rtcPeerConnController.mediaDirections), (0, import_cjs$10.startWith)(rtcPeerConnController.mediaDirections), (0, import_cjs$10.takeUntil)(this.destroyed$));
|
|
14181
14304
|
this.localStream$ = rtcPeerConnController.localStream$.pipe(filterNull(), (0, import_cjs$10.takeUntil)(this.destroyed$));
|
|
14182
14305
|
this.remoteStream$ = rtcPeerConnController.remoteStream$.pipe(filterNull(), (0, import_cjs$10.takeUntil)(this.destroyed$));
|
|
14183
14306
|
}
|
|
@@ -14859,10 +14982,15 @@ var WebRTCCall = class extends Destroyable {
|
|
|
14859
14982
|
async sendDigits(dtmf) {
|
|
14860
14983
|
return this.vertoManager.sendDigits(dtmf);
|
|
14861
14984
|
}
|
|
14862
|
-
/** Accepts an inbound call. */
|
|
14863
|
-
answer() {
|
|
14985
|
+
/** Accepts an inbound call, optionally overriding media options for the answer. */
|
|
14986
|
+
answer(options) {
|
|
14987
|
+
this._answerMediaOptions = options;
|
|
14864
14988
|
this._answered$.next(true);
|
|
14865
14989
|
}
|
|
14990
|
+
/** Media options provided when answering. Used internally by the VertoManager. */
|
|
14991
|
+
get answerMediaOptions() {
|
|
14992
|
+
return this._answerMediaOptions;
|
|
14993
|
+
}
|
|
14866
14994
|
/** Rejects an inbound call. */
|
|
14867
14995
|
reject() {
|
|
14868
14996
|
this._answered$.next(false);
|