arnacon-webrtc-service 0.1.113 → 0.1.115

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arnacon-webrtc-service",
3
- "version": "0.1.113",
3
+ "version": "0.1.115",
4
4
  "description": "Arnacon WebRTC core runtime and service modules",
5
5
  "main": "./webRTCservice/core.js",
6
6
  "type": "commonjs",
@@ -378,6 +378,7 @@ function createOpenAiSipGateway({
378
378
  sessionId: call.sessionId,
379
379
  callId: call.callId,
380
380
  openAiCallId: call.openAiCallId || null,
381
+ mode: call.mode || "default",
381
382
  phase: session.phase || null,
382
383
  established: Boolean(call.established),
383
384
  };
@@ -139,65 +139,69 @@ function patchRouterForDynamicSsrc(pc, logger = console) {
139
139
  if (!router || router._ssrcPatchApplied) return false;
140
140
  const origRouteRtp = router.routeRtp.bind(router);
141
141
  router._rtpInCount = 0;
142
+ router._inboundRtpSubscribers = router._inboundRtpSubscribers || new Set();
142
143
  router._ssrcPatchApplied = true;
143
144
  router.routeRtp = (packet) => {
144
145
  router._rtpInCount++;
145
146
  const incomingSsrc = packet.header.ssrc;
146
147
  if (router.ssrcTable && router.ssrcTable[incomingSsrc]) {
147
148
  origRouteRtp(packet);
148
- return;
149
- }
150
- const recvs = pc.getReceivers ? pc.getReceivers() : [];
151
- for (const recv of recvs) {
152
- const bySsrc = recv.trackBySSRC || {};
153
- if (bySsrc[incomingSsrc]) break;
154
-
155
- const tracks = recv.tracks || [];
156
- if (tracks.length === 1) {
157
- // Keep using the already-created track object so existing RTP subscriptions survive.
158
- const existingTrack = tracks[0];
159
- const oldSsrc = existingTrack.ssrc;
160
- const oldNum = Number(oldSsrc);
161
- const canLateBind =
162
- !Number.isFinite(oldNum) ||
163
- oldNum <= 0 ||
164
- oldNum === 1;
165
- if (!canLateBind) {
166
- continue;
167
- }
168
- if (oldSsrc !== incomingSsrc) {
169
- if (router.ssrcTable) {
170
- delete router.ssrcTable[oldSsrc];
171
- router.ssrcTable[incomingSsrc] = recv;
149
+ } else {
150
+ const recvs = pc.getReceivers ? pc.getReceivers() : [];
151
+ for (const recv of recvs) {
152
+ const bySsrc = recv.trackBySSRC || {};
153
+ if (bySsrc[incomingSsrc]) break;
154
+
155
+ const tracks = recv.tracks || [];
156
+ if (tracks.length === 1) {
157
+ // Keep using the already-created track object so existing RTP subscriptions survive.
158
+ const existingTrack = tracks[0];
159
+ const oldSsrc = existingTrack.ssrc;
160
+ const oldNum = Number(oldSsrc);
161
+ const canLateBind =
162
+ !Number.isFinite(oldNum) ||
163
+ oldNum <= 0 ||
164
+ oldNum === 1;
165
+ if (!canLateBind) {
166
+ continue;
172
167
  }
173
- if (recv.trackBySSRC && recv.trackBySSRC[oldSsrc] === existingTrack) {
174
- delete recv.trackBySSRC[oldSsrc];
168
+ if (oldSsrc !== incomingSsrc) {
169
+ if (router.ssrcTable) {
170
+ delete router.ssrcTable[oldSsrc];
171
+ router.ssrcTable[incomingSsrc] = recv;
172
+ }
173
+ if (recv.trackBySSRC && recv.trackBySSRC[oldSsrc] === existingTrack) {
174
+ delete recv.trackBySSRC[oldSsrc];
175
+ }
176
+ existingTrack.ssrc = incomingSsrc;
177
+ if (recv.trackBySSRC) recv.trackBySSRC[incomingSsrc] = existingTrack;
178
+ logger.log(`[SSRC-FIX] Rebound existing track: ssrc ${oldSsrc} -> ${incomingSsrc}`);
175
179
  }
176
- existingTrack.ssrc = incomingSsrc;
177
- if (recv.trackBySSRC) recv.trackBySSRC[incomingSsrc] = existingTrack;
178
- logger.log(`[SSRC-FIX] Rebound existing track: ssrc ${oldSsrc} -> ${incomingSsrc}`);
180
+ break;
179
181
  }
180
- break;
181
- }
182
182
 
183
- // Legacy placeholder behavior: receivers that start with SSRC=1.
184
- if (tracks.length > 0 && tracks.every((t) => t.ssrc === 1)) {
185
- const existingTrack = recv.trackBySSRC?.[1];
186
- if (existingTrack) {
187
- const oldSsrc = 1;
188
- if (router.ssrcTable) {
189
- delete router.ssrcTable[oldSsrc];
190
- router.ssrcTable[incomingSsrc] = recv;
183
+ // Legacy placeholder behavior: receivers that start with SSRC=1.
184
+ if (tracks.length > 0 && tracks.every((t) => t.ssrc === 1)) {
185
+ const existingTrack = recv.trackBySSRC?.[1];
186
+ if (existingTrack) {
187
+ const oldSsrc = 1;
188
+ if (router.ssrcTable) {
189
+ delete router.ssrcTable[oldSsrc];
190
+ router.ssrcTable[incomingSsrc] = recv;
191
+ }
192
+ existingTrack.ssrc = incomingSsrc;
193
+ delete recv.trackBySSRC[oldSsrc];
194
+ recv.trackBySSRC[incomingSsrc] = existingTrack;
195
+ logger.log(`[SSRC-FIX] Rebound existing track: ssrc ${oldSsrc} -> ${incomingSsrc}`);
196
+ break;
191
197
  }
192
- existingTrack.ssrc = incomingSsrc;
193
- delete recv.trackBySSRC[oldSsrc];
194
- recv.trackBySSRC[incomingSsrc] = existingTrack;
195
- logger.log(`[SSRC-FIX] Rebound existing track: ssrc ${oldSsrc} -> ${incomingSsrc}`);
196
- break;
197
198
  }
198
199
  }
200
+ origRouteRtp(packet);
201
+ }
202
+ for (const fn of router._inboundRtpSubscribers || []) {
203
+ try { fn(packet); } catch (_) {}
199
204
  }
200
- origRouteRtp(packet);
201
205
  };
202
206
  logger.log("[SSRC-FIX] Router patched for late SSRC binding");
203
207
  return true;
@@ -306,6 +310,9 @@ function createPeerConnectionFactory({
306
310
  let clientToKamTranscoded = 0;
307
311
  let kamToClientTranscoded = 0;
308
312
  let kamUnsubscribe = null;
313
+ let kamTrackPackets = 0;
314
+ let kamRouterFallbackPackets = 0;
315
+ let lastKamTrackRtpAt = 0;
309
316
 
310
317
  if (pc1 && session.sipLocalAudioTrack) {
311
318
  for (const t of pc1.getTransceivers()) {
@@ -329,6 +336,8 @@ function createPeerConnectionFactory({
329
336
 
330
337
  const kamHandler = (rtp) => {
331
338
  if (!session.mediaRelayActive) return;
339
+ kamTrackPackets++;
340
+ lastKamTrackRtpAt = Date.now();
332
341
  if (!Number.isFinite(sipPayloadType)) sipPayloadType = Number(rtp?.header?.payloadType);
333
342
  if (!kamSourceNotified && session.localAudioTrack) {
334
343
  kamSourceNotified = true;
@@ -345,6 +354,27 @@ function createPeerConnectionFactory({
345
354
  }
346
355
  };
347
356
 
357
+ const kamRouterFallbackHandler = (rtp) => {
358
+ if (!session.mediaRelayActive || !session.localAudioTrack || !rtp?.header) return;
359
+ // werift can keep counting inbound RTP while a receiver track subscription
360
+ // stops firing after late SSRC binding. Use router packets only when the
361
+ // normal track path has gone quiet, to avoid duplicate audio.
362
+ if (lastKamTrackRtpAt && Date.now() - lastKamTrackRtpAt < 500) return;
363
+ if (!Number.isFinite(sipPayloadType)) sipPayloadType = Number(rtp.header.payloadType);
364
+ if (!kamSourceNotified) {
365
+ kamSourceNotified = true;
366
+ session.localAudioTrack.onSourceChanged.execute({
367
+ sequenceNumber: rtp.header.sequenceNumber,
368
+ timestamp: rtp.header.timestamp,
369
+ });
370
+ }
371
+ const outgoing = adaptG711RtpPacket(rtp, clientPayloadType);
372
+ if (outgoing !== rtp) kamToClientTranscoded++;
373
+ kamRouterFallbackPackets++;
374
+ kamToClient++;
375
+ session.localAudioTrack.writeRtp(outgoing);
376
+ };
377
+
348
378
  const subscribeKamTrack = (track) => {
349
379
  if (!track || track.kind !== "audio" || !session.localAudioTrack || !session.mediaRelayActive) return;
350
380
  if (kamUnsubscribe) {
@@ -373,6 +403,12 @@ function createPeerConnectionFactory({
373
403
  pc2.onTrack.subscribe((track) => {
374
404
  if (track.kind === "audio") subscribeKamTrack(track);
375
405
  });
406
+ if (pc2.router?._inboundRtpSubscribers) {
407
+ pc2.router._inboundRtpSubscribers.add(kamRouterFallbackHandler);
408
+ session._relayDisposers.push(() => {
409
+ try { pc2.router._inboundRtpSubscribers.delete(kamRouterFallbackHandler); } catch (_) {}
410
+ });
411
+ }
376
412
  }
377
413
  // Temporary diagnostics: verify whether RTP is flowing both directions.
378
414
  // Remove after media-path issue is resolved.
@@ -383,6 +419,7 @@ function createPeerConnectionFactory({
383
419
  `[${sessionId}] RTP-STATS pc1_in=${pc1RtpIn} pc2_in=${pc2RtpIn} client_to_kam=${clientToKam} kam_to_client=${kamToClient} ` +
384
420
  `client_pt=${clientPayloadType ?? "?"} sip_pt=${sipPayloadType ?? "?"} ` +
385
421
  `c2k_xcode=${clientToKamTranscoded} k2c_xcode=${kamToClientTranscoded} ` +
422
+ `kam_track=${kamTrackPackets} kam_fallback=${kamRouterFallbackPackets} ` +
386
423
  `pc2_present=${!!pc2} kam_pipe_active=${kamPipeActive}`
387
424
  );
388
425
  }, 2000);
@@ -51,6 +51,12 @@ function createSignalingHandlers({
51
51
  const payload = msg.payload;
52
52
 
53
53
  if (action === "end-call" && payload) {
54
+ if (sess && sess.phase === "ringing") {
55
+ handleEndCallRenegotiation(sessionId, payload).catch((err) => {
56
+ logger.error(`[${sessionId}] Immediate end-call failed: ${err.message}`);
57
+ });
58
+ return;
59
+ }
54
60
  enqueueSignaling(sessionId, "end-call", () => handleEndCallRenegotiation(sessionId, payload));
55
61
  return;
56
62
  }
@@ -84,10 +90,22 @@ function createSignalingHandlers({
84
90
  if (msgType === "call") {
85
91
  const action = msg.action;
86
92
  if (action === "end") {
93
+ if (sess && sess.phase === "ringing") {
94
+ handleCallEnd(sessionId, "client-initiated").catch((err) => {
95
+ logger.error(`[${sessionId}] Immediate call-end failed: ${err.message}`);
96
+ });
97
+ return;
98
+ }
87
99
  enqueueSignaling(sessionId, "call-end", () => handleCallEnd(sessionId, "client-initiated"));
88
100
  return;
89
101
  }
90
102
  if (action === "reject") {
103
+ if (sess && sess.phase === "ringing") {
104
+ handleCallEnd(sessionId, "client-reject").catch((err) => {
105
+ logger.error(`[${sessionId}] Immediate call-reject failed: ${err.message}`);
106
+ });
107
+ return;
108
+ }
91
109
  enqueueSignaling(sessionId, "call-reject", () => handleCallEnd(sessionId, "client-reject"));
92
110
  return;
93
111
  }