@lox-audioserver/node-airplay-sender 0.4.3 → 0.4.5

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/core/rtsp.js CHANGED
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Client = Client;
4
- // @ts-nocheck
5
4
  var net = require('net'), nodeCrypto = require('crypto'), events = require('events'), util = require('util'), fs = require('fs'), dgram = require('dgram');
6
5
  const ntp = require('../utils/ntp').default ?? require('../utils/ntp');
7
6
  const config = require('../utils/config').default ?? require('../utils/config');
@@ -93,6 +92,7 @@ function Client(volume, password, audioOut, options) {
93
92
  this.activeRemote = nu.randomInt(9).toString().toUpperCase();
94
93
  this.dacpId = "04F8191D99BEC6E9";
95
94
  this.session = null;
95
+ this.readySent = false;
96
96
  this.timeout = null;
97
97
  this.volume = volume;
98
98
  this.progress = 0;
@@ -152,7 +152,6 @@ function Client(volume, password, audioOut, options) {
152
152
  this.homekitver = this.transient ? "4" : "3";
153
153
  this.metadataReady = false;
154
154
  this.connectAttempts = 0;
155
- this.lastBackoff = 0;
156
155
  }
157
156
  util.inherits(Client, events.EventEmitter);
158
157
  exports.default = { Client };
@@ -222,8 +221,7 @@ Client.prototype.startHandshake = function (udpServers, host, port) {
222
221
  const baseBackOff = Math.min(config.rtsp_retry_base_ms * Math.pow(2, nextAttempt - 1), config.rtsp_retry_max_ms);
223
222
  const jitter = Math.random() * config.rtsp_retry_jitter_ms;
224
223
  const backOff = baseBackOff + jitter;
225
- this.lastBackoff = backOff;
226
- if (this.debug || config.debug_dump)
224
+ if (this.debug)
227
225
  this.logLine?.('rtsp_retry', { attempt: nextAttempt, backOff, code: err?.code });
228
226
  setTimeout(() => {
229
227
  this.startTimeout();
@@ -242,14 +240,8 @@ Client.prototype.startHandshake = function (udpServers, host, port) {
242
240
  this.cleanup('rtsp_socket', err?.code);
243
241
  });
244
242
  this.socket.on('end', () => {
245
- if (this.debug) {
246
- this.logLine?.('rtsp_end', {
247
- lastStatus: rtsp_methods[this.status + 1] ?? this.status,
248
- cseq: this.cseq,
249
- });
250
- }
251
- this.connectAttempts = 0;
252
- this.lastBackoff = 0;
243
+ if (this.debug)
244
+ this.logLine?.('block2');
253
245
  this.cleanup('disconnected');
254
246
  });
255
247
  };
@@ -411,7 +403,6 @@ Client.prototype.cleanup = function (type, msg) {
411
403
  this.seed = null;
412
404
  this.credentials = null;
413
405
  // this.password = null;
414
- this.connectAttempts = 0;
415
406
  this.removeAllListeners();
416
407
  if (this.timeout) {
417
408
  clearTimeout(this.timeout);
@@ -827,9 +818,6 @@ Client.prototype.sendNextRequest = async function (di) {
827
818
  request += 'Connection: keep-alive\r\n';
828
819
  }
829
820
  request += 'Apple-Challenge: SdX9kFJVxgKVMFof/Znj4Q\r\n\r\n';
830
- this.socket.write(Buffer.from(request, 'utf-8'));
831
- request = '';
832
- this.status = PLAYING;
833
821
  break;
834
822
  case OPTIONS2:
835
823
  request = '';
@@ -1144,10 +1132,6 @@ Client.prototype.sendNextRequest = async function (di) {
1144
1132
  return;
1145
1133
  }
1146
1134
  this.startTimeout();
1147
- if (config.debug_dump) {
1148
- // eslint-disable-next-line no-console
1149
- console.debug('rtsp_req', { status: rtsp_methods[this.status] ?? this.status, len: request.length });
1150
- }
1151
1135
  if (this.encryptedChannel && this.credentials) {
1152
1136
  this.socket.write(this.credentials.encrypt(Buffer.concat([Buffer.from(request, 'utf-8')])));
1153
1137
  }
@@ -1204,10 +1188,6 @@ function parseAuthenticate(auth, field) {
1204
1188
  Client.prototype.processData = function (blob, rawData) {
1205
1189
  this.logLine?.('Receiving request:', this.hostip, rtsp_methods[this.status + 1]);
1206
1190
  var response = parseResponse2(blob, this), headers = response.headers || {};
1207
- if (config.debug_dump) {
1208
- // eslint-disable-next-line no-console
1209
- console.debug('rtsp_res', { code: response.code, status: rtsp_methods[this.status + 1], len: rawData.length });
1210
- }
1211
1191
  if (this.debug) {
1212
1192
  try {
1213
1193
  if ((rawData.toString()).includes("bplist00")) {
@@ -1448,9 +1428,7 @@ Client.prototype.processData = function (blob, rawData) {
1448
1428
  return;
1449
1429
  }
1450
1430
  // this.logLine?.("DEBUG: Device Proof=" + this.deviceProof.toString('hex'));
1451
- if (!this.transient) {
1452
- this.srp.checkM2(this.deviceProof);
1453
- }
1431
+ this.srp.checkM2(this.deviceProof);
1454
1432
  if (this.transient == true) {
1455
1433
  this.credentials = new Credentials("sdsds", "", "", "", this.seed);
1456
1434
  this.credentials.writeKey = enc.HKDF("sha512", Buffer.from("Control-Salt"), this.srp.computeK(), Buffer.from("Control-Write-Encryption-Key"), 32);
@@ -1639,27 +1617,39 @@ Client.prototype.processData = function (blob, rawData) {
1639
1617
  case RECORD:
1640
1618
  this.metadataReady = true;
1641
1619
  this.emit('pair_success');
1642
- if (!this.airplay2) {
1620
+ if (this.airplay2) {
1621
+ // AirPlay2 may not send FLUSH after SETPROGRESS; ensure session exists and start relay once.
1622
+ this.session = this.session ?? "1";
1623
+ if (!this.readySent) {
1624
+ this.readySent = true;
1625
+ this.emit('ready');
1626
+ }
1627
+ }
1628
+ else {
1643
1629
  this.session = this.session ?? "1";
1644
1630
  this.emit('ready');
1645
1631
  }
1646
- ;
1647
1632
  this.status = SETVOLUME;
1648
1633
  break;
1649
1634
  case SETVOLUME:
1650
- if (!this.sentFakeProgess) {
1651
- this.progress = 10;
1652
- this.duration = 2000000;
1653
- this.sentFakeProgess = true;
1654
- this.status = SETPROGRESS;
1635
+ if (this.airplay2) {
1636
+ this.status = PLAYING;
1655
1637
  }
1656
1638
  else {
1657
- this.status = PLAYING;
1639
+ if (!this.sentFakeProgess) {
1640
+ this.progress = 10;
1641
+ this.duration = 2000000;
1642
+ this.sentFakeProgess = true;
1643
+ this.status = SETPROGRESS;
1644
+ }
1645
+ else {
1646
+ this.status = PLAYING;
1647
+ }
1648
+ ;
1658
1649
  }
1659
- ;
1660
1650
  break;
1661
1651
  case SETPROGRESS:
1662
- // After reporting progress, stay in PLAYING; avoid forcing FLUSH on every update.
1652
+ // Keep PLAYING to avoid forcing FLUSH on every progress update.
1663
1653
  this.status = PLAYING;
1664
1654
  break;
1665
1655
  case SETDAAP:
@@ -3,21 +3,6 @@ type ControlSyncTarget = {
3
3
  host: string;
4
4
  controlPort: number;
5
5
  };
6
- type SenderReportCounters = {
7
- rtpTimestamp: number;
8
- ntp: Buffer;
9
- packetCount: number;
10
- octetCount: number;
11
- };
12
- type ReceiverReport = {
13
- ssrc?: number;
14
- };
15
- type ExtendedReport = {
16
- ntp?: Buffer;
17
- ssrc?: number;
18
- lastRr?: number;
19
- delaySinceLastRr?: number;
20
- };
21
6
  /**
22
7
  * Manages control/timing UDP sockets used by RAOP for resend requests and clock sync.
23
8
  * Binds ports for both endpoints and emits events with socket info.
@@ -36,9 +21,6 @@ export default class UDPServers extends EventEmitter {
36
21
  /**
37
22
  * Send an RTCP sync packet to a receiver to align playback.
38
23
  */
39
- sendControlSync(seq: number, dev: ControlSyncTarget, tsOffsetFrames?: number, sr?: SenderReportCounters, rr?: ReceiverReport, xr?: ExtendedReport): void;
40
- private buildSenderReport;
41
- private buildReceiverReport;
42
- private buildExtendedReport;
24
+ sendControlSync(seq: number, dev: ControlSyncTarget): void;
43
25
  }
44
26
  export {};
@@ -133,112 +133,21 @@ class UDPServers extends node_events_1.EventEmitter {
133
133
  /**
134
134
  * Send an RTCP sync packet to a receiver to align playback.
135
135
  */
136
- sendControlSync(seq, dev, tsOffsetFrames = 0, sr, rr, xr) {
136
+ sendControlSync(seq, dev) {
137
137
  if (this.status !== BOUND || !this.control.socket)
138
138
  return;
139
139
  const packet = Buffer.alloc(20);
140
140
  packet.writeUInt16BE(0x80d4, 0);
141
141
  packet.writeUInt16BE(0x0007, 2);
142
- packet.writeUInt32BE((0, numUtil_1.low32)((seq + tsOffsetFrames) * config_1.default.frames_per_packet), 4);
142
+ packet.writeUInt32BE((0, numUtil_1.low32)(seq * config_1.default.frames_per_packet), 4);
143
143
  const ntpTime = ntp_1.default.timestamp();
144
144
  ntpTime.copy(packet, 8);
145
- packet.writeUInt32BE((0, numUtil_1.low32)((seq + tsOffsetFrames) * config_1.default.frames_per_packet + config_1.default.sampling_rate * 2), 16);
145
+ packet.writeUInt32BE((0, numUtil_1.low32)(seq * config_1.default.frames_per_packet + config_1.default.sampling_rate * 2), 16);
146
146
  const delay = Math.max(0, config_1.default.control_sync_base_delay_ms +
147
147
  Math.random() * config_1.default.control_sync_jitter_ms);
148
148
  setTimeout(() => {
149
- if (config_1.default.debug_dump) {
150
- // eslint-disable-next-line no-console
151
- console.debug('rtcp_sync', { seq, tsOffsetFrames, host: dev.host, port: dev.controlPort });
152
- }
153
149
  this.control.socket?.send(packet, 0, packet.length, dev.controlPort, dev.host);
154
- if (sr && config_1.default.send_rtcp_sr) {
155
- const srPacket = this.buildSenderReport(sr);
156
- if (config_1.default.debug_dump) {
157
- // eslint-disable-next-line no-console
158
- console.debug('rtcp_sr', { ssrc: config_1.default.device_magic, rtp: sr.rtpTimestamp, packets: sr.packetCount });
159
- }
160
- this.control.socket?.send(srPacket, 0, srPacket.length, dev.controlPort, dev.host);
161
- }
162
- if (config_1.default.send_rtcp_rr) {
163
- const rrPacket = this.buildReceiverReport(rr);
164
- if (config_1.default.debug_dump) {
165
- // eslint-disable-next-line no-console
166
- console.debug('rtcp_rr', { ssrc: config_1.default.device_magic });
167
- }
168
- this.control.socket?.send(rrPacket, 0, rrPacket.length, dev.controlPort, dev.host);
169
- }
170
- if (xr && config_1.default.send_rtcp_xr) {
171
- const xrPacket = this.buildExtendedReport(xr);
172
- if (config_1.default.debug_dump) {
173
- // eslint-disable-next-line no-console
174
- console.debug('rtcp_xr', { ssrc: config_1.default.device_magic });
175
- }
176
- this.control.socket?.send(xrPacket, 0, xrPacket.length, dev.controlPort, dev.host);
177
- }
178
150
  }, delay);
179
151
  }
180
- buildSenderReport(counters) {
181
- const sr = Buffer.alloc(28);
182
- // V=2, P=0, RC=0
183
- sr.writeUInt8(0x80, 0);
184
- // PT=200 (SR)
185
- sr.writeUInt8(200, 1);
186
- // length in 32-bit words minus 1 -> 6 words (28 bytes) => 6
187
- sr.writeUInt16BE(6, 2);
188
- // SSRC
189
- sr.writeUInt32BE(config_1.default.device_magic, 4);
190
- // NTP timestamp
191
- counters.ntp.copy(sr, 8, 0, 8);
192
- // RTP timestamp
193
- sr.writeUInt32BE((0, numUtil_1.low32)(counters.rtpTimestamp), 16);
194
- // packet count
195
- sr.writeUInt32BE((0, numUtil_1.low32)(counters.packetCount), 20);
196
- // octet count
197
- sr.writeUInt32BE((0, numUtil_1.low32)(counters.octetCount), 24);
198
- return sr;
199
- }
200
- buildReceiverReport(rr) {
201
- const packet = Buffer.alloc(8);
202
- // V=2, P=0, RC=0
203
- packet.writeUInt8(0x80, 0);
204
- // PT=201 (RR)
205
- packet.writeUInt8(201, 1);
206
- // length = 1 (8 bytes / 4 - 1)
207
- packet.writeUInt16BE(1, 2);
208
- // SSRC
209
- packet.writeUInt32BE(rr?.ssrc ?? config_1.default.device_magic, 4);
210
- return packet;
211
- }
212
- buildExtendedReport(xr) {
213
- // XR with RRT (Receiver Reference Time) and optional DLRR.
214
- const rrtBlock = Buffer.alloc(12);
215
- // BT=4 (RRT), reserved+block length=2 (8 octets following header)
216
- rrtBlock.writeUInt8(4, 0);
217
- rrtBlock.writeUInt8(0, 1);
218
- rrtBlock.writeUInt16BE(2, 2);
219
- (xr?.ntp ?? ntp_1.default.timestamp()).copy(rrtBlock, 4, 0, 8);
220
- let dlrrBlock = null;
221
- if (typeof xr?.lastRr === 'number' || typeof xr?.delaySinceLastRr === 'number') {
222
- dlrrBlock = Buffer.alloc(12);
223
- // BT=5 (DLRR)
224
- dlrrBlock.writeUInt8(5, 0);
225
- dlrrBlock.writeUInt8(0, 1);
226
- dlrrBlock.writeUInt16BE(3, 2); // block length (3 words = 12 bytes following header)
227
- dlrrBlock.writeUInt32BE(xr?.lastRr ?? 0, 4);
228
- dlrrBlock.writeUInt32BE(xr?.delaySinceLastRr ?? 0, 8);
229
- }
230
- const blocks = dlrrBlock ? Buffer.concat([rrtBlock, dlrrBlock]) : rrtBlock;
231
- const packet = Buffer.alloc(8 + blocks.length);
232
- // V=2, P=0, RC=0
233
- packet.writeUInt8(0x80, 0);
234
- // PT=207 (XR)
235
- packet.writeUInt8(207, 1);
236
- // length in 32-bit words minus 1
237
- packet.writeUInt16BE(packet.length / 4 - 1, 2);
238
- // SSRC
239
- packet.writeUInt32BE(xr?.ssrc ?? config_1.default.device_magic, 4);
240
- blocks.copy(packet, 8);
241
- return packet;
242
- }
243
152
  }
244
153
  exports.default = UDPServers;
@@ -1,194 +1,69 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
36
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
4
  };
38
5
  Object.defineProperty(exports, "__esModule", { value: true });
39
6
  const node_events_1 = require("node:events");
40
- const node_perf_hooks_1 = require("node:perf_hooks");
41
7
  const config_1 = __importDefault(require("../utils/config"));
42
8
  const numUtil_1 = require("../utils/numUtil");
43
- const ntp_1 = __importStar(require("../utils/ntp"));
44
9
  const SEQ_NUM_WRAP = Math.pow(2, 16);
45
- const FRAC_PER_SEC = 0x1_0000_0000;
46
10
  /**
47
11
  * Generates RTP timestamps and sequence, pulling PCM/ALAC packets from a circular buffer.
48
12
  * Emits `packet` events for devices and sync requests (`need_sync`) at intervals.
49
13
  */
50
14
  class AudioOut extends node_events_1.EventEmitter {
51
15
  lastSeq = -1;
52
- lastWireSeq = 0;
53
16
  hasAirTunes = false;
54
- rtpTimeRef = 0;
17
+ rtpTimeRef = Date.now();
55
18
  startTimeMs;
56
- startTimeNtp;
57
19
  latencyFrames = 0;
58
20
  latencyApplied = false;
59
- seqOffset = 0;
60
- tsOffset = 0;
61
- syncOffsetFrames = 0;
62
- deviceMagic = 0;
63
- packetCount = 0;
64
- octetCount = 0;
65
- frameDurationMs = (config_1.default.frames_per_packet / config_1.default.sampling_rate) * 1000;
66
- muteUntilMs = 0;
67
21
  /**
68
22
  * Begin pulling from the buffer and emitting packets at the configured cadence.
69
23
  * @param devices Device manager for sync events.
70
24
  * @param circularBuffer PCM/ALAC buffer.
71
25
  * @param startTimeMs Optional unix ms to align playback.
72
- * @param startTimeNtp Optional NTP uint64 (sec<<32|frac) to align playback.
73
26
  */
74
- init(devices, circularBuffer, startTimeMs, startTimeNtp, deviceMagic, underrunMuteMs) {
27
+ init(devices, circularBuffer, startTimeMs) {
75
28
  this.startTimeMs =
76
29
  typeof startTimeMs === 'number' && Number.isFinite(startTimeMs)
77
30
  ? startTimeMs
78
31
  : undefined;
79
- this.startTimeNtp = startTimeNtp;
80
- this.seqOffset = Math.floor(Math.random() * SEQ_NUM_WRAP);
81
- this.tsOffset = Math.floor(Math.random() * 0xffffffff);
82
- this.syncOffsetFrames = this.tsOffset / config_1.default.frames_per_packet;
83
- this.deviceMagic = typeof deviceMagic === 'number' ? deviceMagic : config_1.default.device_magic;
84
- if (typeof underrunMuteMs === 'number') {
85
- config_1.default.underrun_mute_ms = underrunMuteMs;
86
- }
87
- circularBuffer.on('underrun', () => this.handleUnderrun());
88
- const monoNow = node_perf_hooks_1.performance.now();
89
- const wallToMonoOffset = Date.now() - monoNow;
90
- if (typeof this.startTimeNtp === 'bigint' || typeof this.startTimeNtp === 'number') {
91
- const { sec, frac } = (0, ntp_1.parseNtpTimestamp)(this.startTimeNtp);
92
- const unixMs = (sec - config_1.default.ntp_epoch) * 1000 + Math.floor((frac * 1000) / FRAC_PER_SEC);
93
- const nowMs = config_1.default.use_monotonic_clock ? monoNow : Date.now();
94
- const delta = unixMs - nowMs;
95
- // If target is too close/past, clamp to now.
96
- this.rtpTimeRef = delta > -config_1.default.stream_latency ? unixMs - wallToMonoOffset : nowMs;
97
- }
98
- else if (this.startTimeMs !== undefined) {
99
- const nowMs = config_1.default.use_monotonic_clock ? monoNow : Date.now();
100
- const delta = this.startTimeMs - nowMs;
101
- this.rtpTimeRef = delta > -config_1.default.stream_latency ? this.startTimeMs - wallToMonoOffset : nowMs;
102
- }
103
- else if (config_1.default.use_monotonic_clock) {
104
- this.rtpTimeRef = monoNow;
105
- }
106
- else {
107
- this.rtpTimeRef = Date.now();
108
- }
32
+ this.rtpTimeRef = this.startTimeMs ?? Date.now();
109
33
  devices.on('airtunes_devices', (hasAirTunes) => {
110
34
  this.hasAirTunes = hasAirTunes;
111
35
  });
112
36
  devices.on('need_sync', () => {
113
- this.emit('need_sync', { seq: this.lastWireSeq, tsOffsetFrames: this.syncOffsetFrames });
37
+ this.emit('need_sync', this.lastSeq);
114
38
  });
115
- const syncEvery = config_1.default.sync_period && config_1.default.sync_period > 0
116
- ? config_1.default.sync_period
117
- : Math.max(1, Math.round(config_1.default.sampling_rate / config_1.default.frames_per_packet));
118
39
  const sendPacket = (seq) => {
119
- const wireSeq = (this.seqOffset + seq) % SEQ_NUM_WRAP;
120
40
  const packet = circularBuffer.readPacket();
121
- packet.seq = wireSeq;
122
- packet.timestamp = (0, numUtil_1.low32)(this.tsOffset + wireSeq * config_1.default.frames_per_packet + 2 * config_1.default.sampling_rate);
123
- this.packetCount += 1;
124
- this.octetCount += packet.pcm.length;
125
- if (this.hasAirTunes && seq % syncEvery === 0) {
126
- this.emit('need_sync', {
127
- seq: wireSeq,
128
- tsOffsetFrames: this.syncOffsetFrames,
129
- rtcp: {
130
- rtpTimestamp: packet.timestamp,
131
- ntp: ntp_1.default.timestamp(),
132
- packetCount: this.packetCount,
133
- octetCount: this.octetCount,
134
- xr: { ntp: ntp_1.default.timestamp() },
135
- },
136
- });
137
- const nowMs = config_1.default.use_monotonic_clock ? node_perf_hooks_1.performance.now() : Date.now();
41
+ packet.seq = seq % SEQ_NUM_WRAP;
42
+ packet.timestamp = (0, numUtil_1.low32)(seq * config_1.default.frames_per_packet + 2 * config_1.default.sampling_rate);
43
+ if (this.hasAirTunes && seq % config_1.default.sync_period === 0) {
44
+ this.emit('need_sync', seq);
138
45
  const expectedTimeMs = this.rtpTimeRef +
139
- (((seq + this.syncOffsetFrames) * config_1.default.frames_per_packet) / config_1.default.sampling_rate) * 1000;
140
- const deltaMs = nowMs - expectedTimeMs;
141
- this.emit('metrics', { type: 'sync', seq: wireSeq, deltaMs, latencyFrames: this.latencyFrames });
46
+ ((seq * config_1.default.frames_per_packet) / config_1.default.sampling_rate) * 1000;
47
+ const deltaMs = Date.now() - expectedTimeMs;
48
+ this.emit('metrics', { type: 'sync', seq, deltaMs, latencyFrames: this.latencyFrames });
142
49
  }
143
50
  this.emit('packet', packet);
144
- if (this.muteUntilMs > 0) {
145
- const nowMsSend = config_1.default.use_monotonic_clock ? node_perf_hooks_1.performance.now() : Date.now();
146
- if (nowMsSend < this.muteUntilMs) {
147
- packet.pcm.fill(0);
148
- }
149
- else {
150
- this.muteUntilMs = 0;
151
- }
152
- }
153
51
  packet.release();
154
- this.lastWireSeq = wireSeq;
155
52
  };
156
53
  const syncAudio = () => {
157
- const nowMs = config_1.default.use_monotonic_clock ? node_perf_hooks_1.performance.now() : Date.now();
158
- const elapsed = nowMs - this.rtpTimeRef;
54
+ const elapsed = Date.now() - this.rtpTimeRef;
159
55
  if (elapsed < 0) {
160
56
  setTimeout(syncAudio, Math.min(config_1.default.stream_latency, Math.abs(elapsed)));
161
57
  return;
162
58
  }
163
- let currentSeq = Math.floor((elapsed * config_1.default.sampling_rate) / (config_1.default.frames_per_packet * 1000));
164
- // If we're lagging behind significantly, jump forward to avoid long hitches.
165
- if (config_1.default.jump_forward_enabled) {
166
- const expectedTimeMs = this.rtpTimeRef + currentSeq * this.frameDurationMs;
167
- const deltaMs = nowMs - expectedTimeMs;
168
- if (deltaMs > config_1.default.jump_forward_threshold_ms) {
169
- const jumpSeq = Math.ceil((config_1.default.jump_forward_lead_ms * config_1.default.sampling_rate) /
170
- (config_1.default.frames_per_packet * 1000));
171
- const newSeq = currentSeq + jumpSeq;
172
- this.rtpTimeRef = nowMs - newSeq * this.frameDurationMs;
173
- this.lastSeq = newSeq - 1;
174
- currentSeq = newSeq;
175
- }
176
- }
59
+ const currentSeq = Math.floor((elapsed * config_1.default.sampling_rate) / (config_1.default.frames_per_packet * 1000));
177
60
  for (let i = this.lastSeq + 1; i <= currentSeq; i += 1) {
178
61
  sendPacket(i);
179
62
  }
180
63
  this.lastSeq = currentSeq;
181
64
  setTimeout(syncAudio, config_1.default.stream_latency);
182
65
  };
183
- // If a future start is scheduled, defer until then; otherwise start immediately.
184
- const nowMs = config_1.default.use_monotonic_clock ? node_perf_hooks_1.performance.now() : Date.now();
185
- const delayMs = Math.max(0, this.rtpTimeRef - nowMs);
186
- if (delayMs > 0) {
187
- setTimeout(syncAudio, delayMs);
188
- }
189
- else {
190
- syncAudio();
191
- }
66
+ syncAudio();
192
67
  }
193
68
  /**
194
69
  * Apply latency (in audio frames) when aligning start time.
@@ -198,23 +73,12 @@ class AudioOut extends node_events_1.EventEmitter {
198
73
  return;
199
74
  }
200
75
  this.latencyFrames = latencyFrames;
201
- if ((this.startTimeMs === undefined && this.startTimeNtp === undefined) || this.latencyApplied) {
76
+ if (this.startTimeMs === undefined || this.latencyApplied) {
202
77
  return;
203
78
  }
204
79
  const latencyMs = (this.latencyFrames / config_1.default.sampling_rate) * 1000;
205
- this.rtpTimeRef -= latencyMs;
80
+ this.rtpTimeRef = this.startTimeMs - latencyMs;
206
81
  this.latencyApplied = true;
207
82
  }
208
- handleUnderrun() {
209
- const nowMs = config_1.default.use_monotonic_clock ? node_perf_hooks_1.performance.now() : Date.now();
210
- const currentSeq = Math.max(0, this.lastSeq);
211
- const currentFrames = currentSeq * config_1.default.frames_per_packet;
212
- const offsetMs = (currentFrames / config_1.default.sampling_rate) * 1000;
213
- this.rtpTimeRef = nowMs - offsetMs;
214
- if (config_1.default.underrun_mute_ms > 0) {
215
- this.muteUntilMs = nowMs + config_1.default.underrun_mute_ms;
216
- }
217
- this.emit('underrun');
218
- }
219
83
  }
220
84
  exports.default = AudioOut;