@ariaflowagents/livekit-plugin-transport-sip 0.9.0

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.
Files changed (58) hide show
  1. package/README.md +22 -0
  2. package/dist/audio_input.d.ts +24 -0
  3. package/dist/audio_input.d.ts.map +1 -0
  4. package/dist/audio_input.js +58 -0
  5. package/dist/audio_input.js.map +1 -0
  6. package/dist/audio_output.d.ts +34 -0
  7. package/dist/audio_output.d.ts.map +1 -0
  8. package/dist/audio_output.js +133 -0
  9. package/dist/audio_output.js.map +1 -0
  10. package/dist/codec/g711.d.ts +17 -0
  11. package/dist/codec/g711.d.ts.map +1 -0
  12. package/dist/codec/g711.js +123 -0
  13. package/dist/codec/g711.js.map +1 -0
  14. package/dist/index.d.ts +12 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +11 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/jssip_signaling.d.ts +76 -0
  19. package/dist/jssip_signaling.d.ts.map +1 -0
  20. package/dist/jssip_signaling.js +255 -0
  21. package/dist/jssip_signaling.js.map +1 -0
  22. package/dist/rtp/jitter_buffer.d.ts +19 -0
  23. package/dist/rtp/jitter_buffer.d.ts.map +1 -0
  24. package/dist/rtp/jitter_buffer.js +51 -0
  25. package/dist/rtp/jitter_buffer.js.map +1 -0
  26. package/dist/rtp/rtp_packet.d.ts +23 -0
  27. package/dist/rtp/rtp_packet.d.ts.map +1 -0
  28. package/dist/rtp/rtp_packet.js +63 -0
  29. package/dist/rtp/rtp_packet.js.map +1 -0
  30. package/dist/rtp/rtp_session.d.ts +54 -0
  31. package/dist/rtp/rtp_session.d.ts.map +1 -0
  32. package/dist/rtp/rtp_session.js +162 -0
  33. package/dist/rtp/rtp_session.js.map +1 -0
  34. package/dist/sdp_g711.d.ts +18 -0
  35. package/dist/sdp_g711.d.ts.map +1 -0
  36. package/dist/sdp_g711.js +74 -0
  37. package/dist/sdp_g711.js.map +1 -0
  38. package/dist/server.d.ts +76 -0
  39. package/dist/server.d.ts.map +1 -0
  40. package/dist/server.js +197 -0
  41. package/dist/server.js.map +1 -0
  42. package/dist/sip_signaling.d.ts +73 -0
  43. package/dist/sip_signaling.d.ts.map +1 -0
  44. package/dist/sip_signaling.js +553 -0
  45. package/dist/sip_signaling.js.map +1 -0
  46. package/dist/text_output.d.ts +16 -0
  47. package/dist/text_output.d.ts.map +1 -0
  48. package/dist/text_output.js +35 -0
  49. package/dist/text_output.js.map +1 -0
  50. package/dist/transport_adapter.d.ts +22 -0
  51. package/dist/transport_adapter.d.ts.map +1 -0
  52. package/dist/transport_adapter.js +44 -0
  53. package/dist/transport_adapter.js.map +1 -0
  54. package/dist/types.d.ts +46 -0
  55. package/dist/types.d.ts.map +1 -0
  56. package/dist/types.js +2 -0
  57. package/dist/types.js.map +1 -0
  58. package/package.json +34 -0
@@ -0,0 +1,255 @@
1
+ /**
2
+ * SIP Signaling implementation using JsSIP library for WebSocket transport.
3
+ *
4
+ * @experimental
5
+ *
6
+ * WARNING: JsSIP is a WebRTC-only library. Its `session.answer()` API is a
7
+ * WebRTC API and cannot bridge to the raw RTP pipeline used by `SIPAudioInput`
8
+ * and `SIPAudioOutput`. This file is kept for reference but the WebSocket SIP
9
+ * signaling path is architecturally incompatible with the RTP transport.
10
+ *
11
+ * For production use, use `sip_signaling.ts` (UDP-based, compatible with RTP).
12
+ *
13
+ * JsSIP supports:
14
+ * - SIP over WebSocket (secure WSS and non-secure WS)
15
+ * - WebRTC for media (NOT compatible with raw RTP)
16
+ * - Full SIP protocol compliance (INVITE, ACK, BYE, etc.)
17
+ */
18
+ import { UA, WebSocketInterface } from 'jssip';
19
+ /**
20
+ * SDP offer for G.711 μ-law audio.
21
+ */
22
+ const G711_SDP_OFFER = (localAddress, rtpPort) => `v=0\r\n` +
23
+ `o=- ${Date.now()} ${Date.now()} IN IP4 ${localAddress}\r\n` +
24
+ `s=AriaFlow Voice Agent\r\n` +
25
+ `c=IN IP4 ${localAddress}\r\n` +
26
+ `t=0 0\r\n` +
27
+ `m=audio ${rtpPort} RTP/AVP 0 8\r\n` + // 0 = G.711 μ-law (PCMU), 8 = G.711 A-law (PCMA)
28
+ `a=rtpmap:0 PCMU/8000\r\n` +
29
+ `a=rtpmap:8 PCMA/8000\r\n` +
30
+ `a=fmtp:0\r\n` +
31
+ `a=fmtp:8\r\n` +
32
+ `a=ptime:20\r\n` +
33
+ `a=sendrecv\r\n`;
34
+ /**
35
+ * SIP Signaling handler using JsSIP over WebSocket.
36
+ *
37
+ * This implementation uses the JsSIP library for WebSocket-based SIP
38
+ * signaling, commonly used with WebRTC and modern SIP providers.
39
+ */
40
+ export class JsSIPSignaling {
41
+ options;
42
+ ua = null;
43
+ activeSessions = new Map();
44
+ nextRtpPort;
45
+ localAddress;
46
+ constructor(options) {
47
+ this.options = options;
48
+ this.localAddress = options.localAddress;
49
+ this.nextRtpPort = options.rtpPortStart ?? 10000;
50
+ }
51
+ /**
52
+ * Start the SIP user agent and begin listening for calls over WebSocket.
53
+ *
54
+ * @param onInvite - Callback when a new INVITE is received
55
+ * @param onBye - Callback when a BYE is received (remote hangup)
56
+ */
57
+ async start(onInvite, onBye) {
58
+ // Create WebSocket URL
59
+ const wsProtocol = this.options.secureWebSocket === false ? 'ws' : 'wss';
60
+ const wsUrl = `${wsProtocol}://${this.options.wsServerHost || this.localAddress}:${this.options.wsServerPort || 8080}/ws`;
61
+ // Create SIP URI
62
+ const sipUri = `sip:${this.options.sipUsername || 'agent'}@${this.options.sipDomain || this.localAddress}`;
63
+ // Create JsSIP User Agent
64
+ this.ua = new UA({
65
+ uri: sipUri,
66
+ sockets: [new WebSocketInterface(wsUrl)],
67
+ password: this.options.sipPassword,
68
+ register: this.options.shouldRegister !== false, // Register by default
69
+ });
70
+ // Set up event handlers for incoming calls
71
+ this.ua.on('newRTCSession', (data) => {
72
+ this.handleIncomingInvite(data.session, onInvite, onBye);
73
+ });
74
+ // Set up connection event handlers
75
+ this.ua.on('connected', () => {
76
+ console.log(`[JsSIPSignaling] Connected to SIP server: ${wsUrl}`);
77
+ });
78
+ this.ua.on('disconnected', () => {
79
+ console.warn('[JsSIPSignaling] Disconnected from SIP server');
80
+ });
81
+ this.ua.on('registered', () => {
82
+ console.log(`[JsSIPSignaling] Registered as: ${sipUri}`);
83
+ });
84
+ this.ua.on('registrationFailed', () => {
85
+ console.error('[JsSIPSignaling] Registration failed');
86
+ });
87
+ // Start the user agent
88
+ this.ua.start();
89
+ console.log(`[JsSIPSignaling] Started with WebSocket: ${wsUrl}`);
90
+ }
91
+ /**
92
+ * Handle an incoming INVITE request.
93
+ */
94
+ async handleIncomingInvite(session, onInvite, onBye) {
95
+ const callId = session.request?.callId || `call-${Date.now()}`;
96
+ const remoteSdp = session.request?.body || '';
97
+ console.log(`[JsSIPSignaling] Received INVITE for call: ${callId}`);
98
+ console.log(`[JsSIPSignaling] From: ${session.remote_identity?.uri?.toString() || session}`);
99
+ // Allocate RTP port for this call
100
+ const rtpPort = this.nextRtpPort;
101
+ this.nextRtpPort += 2; // RTP uses even ports
102
+ // Generate SDP answer with our RTP port
103
+ const sdpAnswer = G711_SDP_OFFER(this.localAddress, rtpPort);
104
+ // Set up session state handlers
105
+ session.on('progress', () => {
106
+ console.log(`[JsSIPSignaling] Call ${callId} in progress`);
107
+ });
108
+ session.on('confirmed', () => {
109
+ console.log(`[JsSIPSignaling] Call ${callId} established`);
110
+ });
111
+ session.on('ended', () => {
112
+ console.log(`[JsSIPSignaling] Call ${callId} ended`);
113
+ this.activeSessions.delete(callId);
114
+ if (onBye) {
115
+ onBye(callId);
116
+ }
117
+ });
118
+ session.on('failed', (error) => {
119
+ console.error(`[JsSIPSignaling] Call ${callId} failed:`, error);
120
+ this.activeSessions.delete(callId);
121
+ });
122
+ // Accept the invitation with our SDP
123
+ try {
124
+ await session.answer({
125
+ extraHeaders: ['Content-Type: application/sdp'],
126
+ });
127
+ // Track the active session
128
+ this.activeSessions.set(callId, { session, rtpPort });
129
+ // Notify the application about the new call
130
+ onInvite(callId, remoteSdp, rtpPort);
131
+ }
132
+ catch (error) {
133
+ console.error(`[JsSIPSignaling] Error accepting call ${callId}:`, error);
134
+ try {
135
+ session.terminate();
136
+ }
137
+ catch {
138
+ // Ignore terminate errors
139
+ }
140
+ }
141
+ }
142
+ /**
143
+ * Hang up an active call.
144
+ */
145
+ async hangup(callId) {
146
+ const activeSession = this.activeSessions.get(callId);
147
+ if (!activeSession) {
148
+ console.warn(`[JsSIPSignaling] No active session for call: ${callId}`);
149
+ return;
150
+ }
151
+ try {
152
+ await activeSession.session.terminate();
153
+ this.activeSessions.delete(callId);
154
+ console.log(`[JsSIPSignaling] Hung up call: ${callId}`);
155
+ }
156
+ catch (error) {
157
+ console.error(`[JsSIPSignaling] Error hanging up call ${callId}:`, error);
158
+ }
159
+ }
160
+ /**
161
+ * Make an outgoing call.
162
+ */
163
+ async makeCall(targetUri, onInvite, onBye) {
164
+ if (!this.ua) {
165
+ throw new Error('[JsSIPSignaling] User agent not started');
166
+ }
167
+ // Allocate RTP port for this call
168
+ const rtpPort = this.nextRtpPort;
169
+ this.nextRtpPort += 2;
170
+ // Generate SDP offer with our RTP port
171
+ const sdpOffer = G711_SDP_OFFER(this.localAddress, rtpPort);
172
+ // Create the call session
173
+ const session = this.ua.call(targetUri, {
174
+ extraHeaders: ['Content-Type: application/sdp'],
175
+ eventHandlers: {
176
+ progress: () => {
177
+ const callId = session.request?.callId || `call-${Date.now()}`;
178
+ console.log(`[JsSIPSignaling] Outgoing call ${callId} in progress`);
179
+ },
180
+ confirmed: () => {
181
+ const callId = session.request?.callId || `call-${Date.now()}`;
182
+ console.log(`[JsSIPSignaling] Outgoing call ${callId} accepted`);
183
+ this.activeSessions.set(callId, { session, rtpPort });
184
+ if (onInvite) {
185
+ onInvite(callId, session.request?.body || '', rtpPort);
186
+ }
187
+ },
188
+ ended: () => {
189
+ const callId = session.request?.callId || `call-${Date.now()}`;
190
+ console.log(`[JsSIPSignaling] Outgoing call ${callId} ended`);
191
+ this.activeSessions.delete(callId);
192
+ if (onBye) {
193
+ onBye(callId);
194
+ }
195
+ },
196
+ failed: (error) => {
197
+ const callId = session.request?.callId || `call-${Date.now()}`;
198
+ console.error(`[JsSIPSignaling] Outgoing call ${callId} failed:`, error);
199
+ this.activeSessions.delete(callId);
200
+ },
201
+ },
202
+ });
203
+ return session.request?.callId || `call-${Date.now()}`;
204
+ }
205
+ /**
206
+ * Stop the SIP user agent and hang up all active calls.
207
+ */
208
+ async stop() {
209
+ if (!this.ua) {
210
+ return;
211
+ }
212
+ // Hang up all active calls
213
+ const hangupPromises = Array.from(this.activeSessions.entries()).map(async ([callId, { session }]) => {
214
+ try {
215
+ await session.terminate();
216
+ }
217
+ catch (error) {
218
+ console.error(`[JsSIPSignaling] Error hanging up call ${callId} during shutdown:`, error);
219
+ }
220
+ });
221
+ await Promise.allSettled(hangupPromises);
222
+ this.activeSessions.clear();
223
+ // Stop the user agent
224
+ this.ua.stop();
225
+ this.ua = null;
226
+ console.log('[JsSIPSignaling] Stopped');
227
+ }
228
+ /**
229
+ * Get the RTP port allocated for a specific call.
230
+ */
231
+ getRtpPort(callId) {
232
+ return this.activeSessions.get(callId)?.rtpPort;
233
+ }
234
+ /**
235
+ * Check if the user agent is registered.
236
+ */
237
+ get isRegistered() {
238
+ return this.ua?.isRegistered() ?? false;
239
+ }
240
+ /**
241
+ * Get the user agent status.
242
+ */
243
+ get status() {
244
+ if (!this.ua)
245
+ return 'disconnected';
246
+ const uaStatus = this.ua.status;
247
+ // JsSIP UA status: 0 = Init, 1 = Ready, 2 = UserRegistered, 3 = Unregistered
248
+ if (uaStatus === 0)
249
+ return 'connecting';
250
+ if (uaStatus === 1 || uaStatus === 2)
251
+ return 'connected';
252
+ return 'disconnected';
253
+ }
254
+ }
255
+ //# sourceMappingURL=jssip_signaling.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jssip_signaling.js","sourceRoot":"","sources":["../src/jssip_signaling.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { RtpPacket } from './rtp_packet.js';
2
+ /**
3
+ * Simple jitter buffer that reorders RTP packets by sequence number.
4
+ *
5
+ * Fixed depth (default 3 packets = 60ms at 20ms/packet).
6
+ * Late packets are dropped. Missing packets cause the buffer to skip ahead
7
+ * when it reaches max depth.
8
+ */
9
+ export declare class JitterBuffer {
10
+ private buffer;
11
+ private lastDeliveredSeq;
12
+ private nextExpectedSeq;
13
+ private maxDepth;
14
+ constructor(maxDepth?: number);
15
+ push(packet: RtpPacket): void;
16
+ pull(): RtpPacket | null;
17
+ private isOlder;
18
+ }
19
+ //# sourceMappingURL=jitter_buffer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jitter_buffer.d.ts","sourceRoot":"","sources":["../../src/rtp/jitter_buffer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD;;;;;;GAMG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAqC;IACnD,OAAO,CAAC,gBAAgB,CAAc;IACtC,OAAO,CAAC,eAAe,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAS;gBAEb,QAAQ,GAAE,MAAU;IAIhC,IAAI,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI;IAe7B,IAAI,IAAI,SAAS,GAAG,IAAI;IA0BxB,OAAO,CAAC,OAAO;CAIhB"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Simple jitter buffer that reorders RTP packets by sequence number.
3
+ *
4
+ * Fixed depth (default 3 packets = 60ms at 20ms/packet).
5
+ * Late packets are dropped. Missing packets cause the buffer to skip ahead
6
+ * when it reaches max depth.
7
+ */
8
+ export class JitterBuffer {
9
+ buffer = new Map();
10
+ lastDeliveredSeq = -1;
11
+ nextExpectedSeq = -1;
12
+ maxDepth;
13
+ constructor(maxDepth = 3) {
14
+ this.maxDepth = maxDepth;
15
+ }
16
+ push(packet) {
17
+ if (this.lastDeliveredSeq >= 0 &&
18
+ this.isOlder(packet.sequenceNumber, this.lastDeliveredSeq)) {
19
+ return; // Late packet, drop
20
+ }
21
+ this.buffer.set(packet.sequenceNumber, packet);
22
+ if (this.nextExpectedSeq < 0) {
23
+ this.nextExpectedSeq = packet.sequenceNumber;
24
+ }
25
+ }
26
+ pull() {
27
+ if (this.buffer.has(this.nextExpectedSeq)) {
28
+ const packet = this.buffer.get(this.nextExpectedSeq);
29
+ this.buffer.delete(this.nextExpectedSeq);
30
+ this.lastDeliveredSeq = this.nextExpectedSeq;
31
+ this.nextExpectedSeq = (this.nextExpectedSeq + 1) & 0xffff;
32
+ return packet;
33
+ }
34
+ // Buffer full -- skip to oldest available
35
+ if (this.buffer.size >= this.maxDepth) {
36
+ const sorted = Array.from(this.buffer.keys()).sort((a, b) => this.isOlder(a, b) ? -1 : 1);
37
+ const oldestSeq = sorted[0];
38
+ const packet = this.buffer.get(oldestSeq);
39
+ this.buffer.delete(oldestSeq);
40
+ this.lastDeliveredSeq = oldestSeq;
41
+ this.nextExpectedSeq = (oldestSeq + 1) & 0xffff;
42
+ return packet;
43
+ }
44
+ return null;
45
+ }
46
+ isOlder(a, b) {
47
+ const diff = (b - a) & 0xffff;
48
+ return diff > 0 && diff < 0x8000;
49
+ }
50
+ }
51
+ //# sourceMappingURL=jitter_buffer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jitter_buffer.js","sourceRoot":"","sources":["../../src/rtp/jitter_buffer.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,OAAO,YAAY;IACf,MAAM,GAA2B,IAAI,GAAG,EAAE,CAAC;IAC3C,gBAAgB,GAAW,CAAC,CAAC,CAAC;IAC9B,eAAe,GAAW,CAAC,CAAC,CAAC;IAC7B,QAAQ,CAAS;IAEzB,YAAY,WAAmB,CAAC;QAC9B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAED,IAAI,CAAC,MAAiB;QACpB,IACE,IAAI,CAAC,gBAAgB,IAAI,CAAC;YAC1B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,IAAI,CAAC,gBAAgB,CAAC,EAC1D,CAAC;YACD,OAAO,CAAC,oBAAoB;QAC9B,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;QAE/C,IAAI,IAAI,CAAC,eAAe,GAAG,CAAC,EAAE,CAAC;YAC7B,IAAI,CAAC,eAAe,GAAG,MAAM,CAAC,cAAc,CAAC;QAC/C,CAAC;IACH,CAAC;IAED,IAAI;QACF,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC;YAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,eAAe,CAAE,CAAC;YACtD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YACzC,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,eAAe,CAAC;YAC7C,IAAI,CAAC,eAAe,GAAG,CAAC,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC;YAC3D,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,0CAA0C;QAC1C,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACtC,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAC1D,IAAI,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAC5B,CAAC;YAEF,MAAM,SAAS,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;YAC5B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAE,CAAC;YAC3C,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAC9B,IAAI,CAAC,gBAAgB,GAAG,SAAS,CAAC;YAClC,IAAI,CAAC,eAAe,GAAG,CAAC,SAAS,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC;YAChD,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,OAAO,CAAC,CAAS,EAAE,CAAS;QAClC,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC;QAC9B,OAAO,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,MAAM,CAAC;IACnC,CAAC;CACF"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * RTP packet parser and builder (RFC 3550).
3
+ *
4
+ * For G.711 at 8000 Hz with 20ms packets:
5
+ * Payload: 160 bytes (160 samples * 1 byte per G.711 sample)
6
+ * Timestamp increment: 160 per packet
7
+ * Total: 12 (header) + 160 (payload) = 172 bytes
8
+ */
9
+ export interface RtpPacket {
10
+ version: number;
11
+ padding: boolean;
12
+ extension: boolean;
13
+ csrcCount: number;
14
+ marker: boolean;
15
+ payloadType: number;
16
+ sequenceNumber: number;
17
+ timestamp: number;
18
+ ssrc: number;
19
+ payload: Uint8Array;
20
+ }
21
+ export declare function parseRtpPacket(data: Buffer): RtpPacket | null;
22
+ export declare function buildRtpPacket(payloadType: number, sequenceNumber: number, timestamp: number, ssrc: number, payload: Uint8Array, marker?: boolean): Buffer;
23
+ //# sourceMappingURL=rtp_packet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rtp_packet.d.ts","sourceRoot":"","sources":["../../src/rtp/rtp_packet.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,UAAU,CAAC;CACrB;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAuD7D;AAED,wBAAgB,cAAc,CAC5B,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,UAAU,EACnB,MAAM,GAAE,OAAe,GACtB,MAAM,CAUR"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * RTP packet parser and builder (RFC 3550).
3
+ *
4
+ * For G.711 at 8000 Hz with 20ms packets:
5
+ * Payload: 160 bytes (160 samples * 1 byte per G.711 sample)
6
+ * Timestamp increment: 160 per packet
7
+ * Total: 12 (header) + 160 (payload) = 172 bytes
8
+ */
9
+ export function parseRtpPacket(data) {
10
+ if (data.length < 12)
11
+ return null;
12
+ const firstByte = data[0];
13
+ const version = (firstByte >> 6) & 0x03;
14
+ if (version !== 2)
15
+ return null;
16
+ const padding = ((firstByte >> 5) & 0x01) === 1;
17
+ const extension = ((firstByte >> 4) & 0x01) === 1;
18
+ const csrcCount = firstByte & 0x0f;
19
+ const secondByte = data[1];
20
+ const marker = ((secondByte >> 7) & 0x01) === 1;
21
+ const payloadType = secondByte & 0x7f;
22
+ const sequenceNumber = data.readUInt16BE(2);
23
+ const timestamp = data.readUInt32BE(4);
24
+ const ssrc = data.readUInt32BE(8);
25
+ const headerLength = 12 + csrcCount * 4;
26
+ let payloadOffset = headerLength;
27
+ if (extension && data.length > headerLength + 4) {
28
+ const extensionLength = data.readUInt16BE(headerLength + 2);
29
+ payloadOffset = headerLength + 4 + extensionLength * 4;
30
+ }
31
+ if (payloadOffset >= data.length)
32
+ return null;
33
+ let payloadLength = data.length - payloadOffset;
34
+ if (padding && payloadLength > 0) {
35
+ const paddingLength = data[data.length - 1];
36
+ payloadLength -= paddingLength;
37
+ }
38
+ if (payloadLength <= 0)
39
+ return null;
40
+ const payload = new Uint8Array(data.buffer, data.byteOffset + payloadOffset, payloadLength);
41
+ return {
42
+ version,
43
+ padding,
44
+ extension,
45
+ csrcCount,
46
+ marker,
47
+ payloadType,
48
+ sequenceNumber,
49
+ timestamp,
50
+ ssrc,
51
+ payload,
52
+ };
53
+ }
54
+ export function buildRtpPacket(payloadType, sequenceNumber, timestamp, ssrc, payload, marker = false) {
55
+ const header = Buffer.alloc(12);
56
+ header[0] = 0x80; // Version=2
57
+ header[1] = (marker ? 0x80 : 0x00) | (payloadType & 0x7f);
58
+ header.writeUInt16BE(sequenceNumber & 0xffff, 2);
59
+ header.writeUInt32BE(timestamp >>> 0, 4);
60
+ header.writeUInt32BE(ssrc >>> 0, 8);
61
+ return Buffer.concat([header, Buffer.from(payload)]);
62
+ }
63
+ //# sourceMappingURL=rtp_packet.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rtp_packet.js","sourceRoot":"","sources":["../../src/rtp/rtp_packet.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { type Codec } from '@ariaflow/livekit-plugin/codec/g711';
3
+ export interface RtpSessionOptions {
4
+ localPort: number;
5
+ jitterDepth?: number;
6
+ packetDurationMs?: number;
7
+ /**
8
+ * When true, outbound RTP is clocked at `packetDurationMs` (default 20ms)
9
+ * regardless of application timing: queue drains one frame per tick; idle
10
+ * ticks send codec-encoded silence. Default false preserves immediate
11
+ * `sendAudio` behavior.
12
+ */
13
+ continuousPacing?: boolean;
14
+ }
15
+ /**
16
+ * Manages a bidirectional RTP stream over UDP.
17
+ *
18
+ * Inbound: receives UDP datagrams, parses RTP, passes through jitter buffer,
19
+ * decodes with codec selected by RTP payload type (PCMU=0, PCMA=8), emits
20
+ * 'audio' events with PCM Int16Array data.
21
+ *
22
+ * Outbound: accepts PCM Int16Array, encodes with the negotiated outbound
23
+ * codec, builds RTP packets, sends as UDP datagrams.
24
+ */
25
+ export declare class RtpSession extends EventEmitter {
26
+ private socket;
27
+ private jitterBuffer;
28
+ private outboundCodec;
29
+ private sendSequenceNumber;
30
+ private sendTimestamp;
31
+ private sendSsrc;
32
+ private remoteAddress;
33
+ private remotePort;
34
+ private samplesPerPacket;
35
+ private packetDurationMs;
36
+ private continuousPacing;
37
+ private pacedQueue;
38
+ private pacePending;
39
+ private paceTimer;
40
+ private nextPaceTime;
41
+ constructor(codec: Codec, options: RtpSessionOptions);
42
+ private decodeByPayloadType;
43
+ setRemote(address: string, port: number): void;
44
+ private sendRtpPayload;
45
+ /**
46
+ * Append PCM to the paced buffer and queue full 20ms (or packetDurationMs) frames.
47
+ */
48
+ private enqueuePacedPcm;
49
+ private runPacedTick;
50
+ private schedulePaceTick;
51
+ sendAudio(pcm: Int16Array): void;
52
+ close(): void;
53
+ }
54
+ //# sourceMappingURL=rtp_session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rtp_session.d.ts","sourceRoot":"","sources":["../../src/rtp/rtp_session.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAO3C,OAAO,EAGL,KAAK,KAAK,EACX,MAAM,qCAAqC,CAAC;AAE7C,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;;;;;GASG;AACH,qBAAa,UAAW,SAAQ,YAAY;IAC1C,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,aAAa,CAAQ;IAE7B,OAAO,CAAC,kBAAkB,CAAa;IACvC,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,aAAa,CAAc;IACnC,OAAO,CAAC,UAAU,CAAa;IAE/B,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,gBAAgB,CAAS;IAEjC,OAAO,CAAC,gBAAgB,CAAU;IAClC,OAAO,CAAC,UAAU,CAAoB;IACtC,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,SAAS,CAA8C;IAC/D,OAAO,CAAC,YAAY,CAAa;gBAErB,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,iBAAiB;IAkDpD,OAAO,CAAC,mBAAmB;IAa3B,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAK9C,OAAO,CAAC,cAAc;IAmBtB;;OAEG;IACH,OAAO,CAAC,eAAe;IAmBvB,OAAO,CAAC,YAAY;IAcpB,OAAO,CAAC,gBAAgB;IAiBxB,SAAS,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI;IAUhC,KAAK,IAAI,IAAI;CAad"}
@@ -0,0 +1,162 @@
1
+ import * as dgram from 'node:dgram';
2
+ import { EventEmitter } from 'node:events';
3
+ import { parseRtpPacket, buildRtpPacket, } from './rtp_packet.js';
4
+ import { JitterBuffer } from './jitter_buffer.js';
5
+ import { PCMU, PCMA, } from '@ariaflow/livekit-plugin/codec/g711';
6
+ /**
7
+ * Manages a bidirectional RTP stream over UDP.
8
+ *
9
+ * Inbound: receives UDP datagrams, parses RTP, passes through jitter buffer,
10
+ * decodes with codec selected by RTP payload type (PCMU=0, PCMA=8), emits
11
+ * 'audio' events with PCM Int16Array data.
12
+ *
13
+ * Outbound: accepts PCM Int16Array, encodes with the negotiated outbound
14
+ * codec, builds RTP packets, sends as UDP datagrams.
15
+ */
16
+ export class RtpSession extends EventEmitter {
17
+ socket;
18
+ jitterBuffer;
19
+ outboundCodec;
20
+ sendSequenceNumber = 0;
21
+ sendTimestamp = 0;
22
+ sendSsrc;
23
+ remoteAddress = '';
24
+ remotePort = 0;
25
+ samplesPerPacket;
26
+ packetDurationMs;
27
+ continuousPacing;
28
+ pacedQueue = [];
29
+ pacePending = new Int16Array(0);
30
+ paceTimer = null;
31
+ nextPaceTime = 0;
32
+ constructor(codec, options) {
33
+ super();
34
+ this.outboundCodec = codec;
35
+ this.jitterBuffer = new JitterBuffer(options.jitterDepth ?? 3);
36
+ this.sendSsrc = Math.floor(Math.random() * 0xffffffff);
37
+ this.packetDurationMs = options.packetDurationMs ?? 20;
38
+ this.samplesPerPacket =
39
+ (codec.sampleRate * this.packetDurationMs) / 1000;
40
+ this.continuousPacing = options.continuousPacing === true;
41
+ this.socket = dgram.createSocket('udp4');
42
+ this.socket.on('message', (msg, rinfo) => {
43
+ if (!this.remoteAddress) {
44
+ this.remoteAddress = rinfo.address;
45
+ this.remotePort = rinfo.port;
46
+ }
47
+ const packet = parseRtpPacket(msg);
48
+ if (!packet)
49
+ return;
50
+ this.jitterBuffer.push(packet);
51
+ let buffered;
52
+ while ((buffered = this.jitterBuffer.pull()) !== null) {
53
+ const pcm = this.decodeByPayloadType(buffered.payloadType, buffered.payload);
54
+ this.emit('audio', pcm);
55
+ }
56
+ });
57
+ this.socket.on('error', (err) => {
58
+ console.error('[RtpSession] UDP socket error:', err.message);
59
+ });
60
+ this.socket.bind(options.localPort);
61
+ if (this.continuousPacing) {
62
+ // First `schedulePaceTick` advances by one `packetDurationMs`; start from "now"
63
+ // so the initial delay is exactly one frame (not two).
64
+ this.nextPaceTime = performance.now();
65
+ this.schedulePaceTick();
66
+ }
67
+ }
68
+ decodeByPayloadType(payloadType, payload) {
69
+ if (payloadType === PCMU.payloadType) {
70
+ return PCMU.decode(payload);
71
+ }
72
+ if (payloadType === PCMA.payloadType) {
73
+ return PCMA.decode(payload);
74
+ }
75
+ return this.outboundCodec.decode(payload);
76
+ }
77
+ setRemote(address, port) {
78
+ this.remoteAddress = address;
79
+ this.remotePort = port;
80
+ }
81
+ sendRtpPayload(encoded, samplesThisPacket) {
82
+ if (!this.remoteAddress || !this.remotePort)
83
+ return;
84
+ const packet = buildRtpPacket(this.outboundCodec.payloadType, this.sendSequenceNumber, this.sendTimestamp, this.sendSsrc, encoded, this.sendSequenceNumber === 0);
85
+ this.socket.send(packet, this.remotePort, this.remoteAddress);
86
+ this.sendSequenceNumber = (this.sendSequenceNumber + 1) & 0xffff;
87
+ this.sendTimestamp =
88
+ (this.sendTimestamp + samplesThisPacket) >>> 0;
89
+ }
90
+ /**
91
+ * Append PCM to the paced buffer and queue full 20ms (or packetDurationMs) frames.
92
+ */
93
+ enqueuePacedPcm(pcm) {
94
+ const merged = this.pacePending.length === 0
95
+ ? pcm
96
+ : concatInt16(this.pacePending, pcm);
97
+ let offset = 0;
98
+ while (offset + this.samplesPerPacket <= merged.length) {
99
+ this.pacedQueue.push(merged.subarray(offset, offset + this.samplesPerPacket));
100
+ offset += this.samplesPerPacket;
101
+ }
102
+ this.pacePending =
103
+ offset === merged.length
104
+ ? new Int16Array(0)
105
+ : merged.subarray(offset);
106
+ }
107
+ runPacedTick() {
108
+ if (!this.remoteAddress || !this.remotePort) {
109
+ return;
110
+ }
111
+ const pcm = this.pacedQueue.length > 0
112
+ ? this.pacedQueue.shift()
113
+ : new Int16Array(this.samplesPerPacket);
114
+ const encoded = this.outboundCodec.encode(pcm);
115
+ this.sendRtpPayload(encoded, pcm.length);
116
+ }
117
+ schedulePaceTick() {
118
+ const now = performance.now();
119
+ const drift = now - this.nextPaceTime;
120
+ if (drift > 2 * this.packetDurationMs) {
121
+ this.nextPaceTime = now + this.packetDurationMs;
122
+ }
123
+ else {
124
+ this.nextPaceTime += this.packetDurationMs;
125
+ }
126
+ const delay = Math.max(0, this.nextPaceTime - performance.now());
127
+ this.paceTimer = setTimeout(() => {
128
+ this.paceTimer = null;
129
+ this.runPacedTick();
130
+ this.schedulePaceTick();
131
+ }, delay);
132
+ }
133
+ sendAudio(pcm) {
134
+ if (this.continuousPacing) {
135
+ this.enqueuePacedPcm(pcm);
136
+ return;
137
+ }
138
+ const encoded = this.outboundCodec.encode(pcm);
139
+ this.sendRtpPayload(encoded, this.samplesPerPacket);
140
+ }
141
+ close() {
142
+ if (this.paceTimer !== null) {
143
+ clearTimeout(this.paceTimer);
144
+ this.paceTimer = null;
145
+ }
146
+ this.pacedQueue = [];
147
+ this.pacePending = new Int16Array(0);
148
+ try {
149
+ this.socket.close();
150
+ }
151
+ catch {
152
+ // Already closed
153
+ }
154
+ }
155
+ }
156
+ function concatInt16(a, b) {
157
+ const out = new Int16Array(a.length + b.length);
158
+ out.set(a, 0);
159
+ out.set(b, a.length);
160
+ return out;
161
+ }
162
+ //# sourceMappingURL=rtp_session.js.map