alphatheta-connect 0.23.2 → 0.25.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.
package/lib/index.js CHANGED
@@ -631,8 +631,10 @@ exports.MAX_CDJ_DEVICE_ID = exports.MIN_CDJ_DEVICE_ID = exports.VIRTUAL_CDJ_FIRM
631
631
  /**
632
632
  * The default virtual CDJ ID to use.
633
633
  *
634
- * This particular ID is out of the 1-6 range, thus will not be able to request
635
- * metadata via the remotedb for CDJs.
634
+ * Out of the 1-6 player range, so it can never collide with a real CDJ.
635
+ * Remotedb metadata still works from here: the 1-6 restriction applies to the
636
+ * device-ID byte inside remotedb messages, which RemoteDatabase picks
637
+ * per-connection independently of the announced ID.
636
638
  */
637
639
  exports.DEFAULT_VCDJ_ID = 0x07;
638
640
  /**
@@ -1536,7 +1538,6 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
1536
1538
  };
1537
1539
  var _Database_instances, _Database_deviceManager, _Database_localDatabase, _Database_remoteDatabase, _Database_logger, _Database_getTrackLookupStrategy, _Database_getMediaLookupStrategy, _Database_metadataViaRemoteFallback;
1538
1540
  Object.defineProperty(exports, "__esModule", ({ value: true }));
1539
- const constants_1 = __webpack_require__(/*! src/constants */ "./src/constants.ts");
1540
1541
  const logger_1 = __webpack_require__(/*! src/logger */ "./src/logger.ts");
1541
1542
  const types_1 = __webpack_require__(/*! src/types */ "./src/types.ts");
1542
1543
  const utils_1 = __webpack_require__(/*! src/utils */ "./src/utils/index.ts");
@@ -1836,20 +1837,14 @@ _Database_deviceManager = new WeakMap(), _Database_localDatabase = new WeakMap()
1836
1837
  /**
1837
1838
  * Second chance for a track the local database could not answer for.
1838
1839
  *
1839
- * CDJs only answer remote database queries from a host announcing a device
1840
- * ID in the 1-6 range, so this is unavailable whenever the virtual CDJ sits
1841
- * outside it (the package default is 7). That is a real configuration, not
1842
- * an error — log why the fallback was skipped rather than throwing, so the
1843
- * next report says which of the two happened.
1840
+ * Available regardless of the virtual CDJ's announced ID: CDJs restrict
1841
+ * remote database queries to a device-ID byte in the 1-6 range, but that
1842
+ * byte lives inside the remotedb messages and RemoteDatabase picks an
1843
+ * in-range one per connection — the announced ID never constrains this
1844
+ * lookup. A failure here is logged rather than thrown, so a field report
1845
+ * says what actually went wrong.
1844
1846
  */
1845
1847
  async function _Database_metadataViaRemoteFallback(opts) {
1846
- const hostId = __classPrivateFieldGet(this, _Database_remoteDatabase, "f").hostDevice.id;
1847
- if (hostId < constants_1.MIN_CDJ_DEVICE_ID || hostId > constants_1.MAX_CDJ_DEVICE_ID) {
1848
- __classPrivateFieldGet(this, _Database_logger, "f").warn(`No remote fallback for track ${opts.trackId}: this player is device ` +
1849
- `${hostId}, and CDJs only answer remote database queries from ` +
1850
- `devices ${constants_1.MIN_CDJ_DEVICE_ID}-${constants_1.MAX_CDJ_DEVICE_ID}`);
1851
- return null;
1852
- }
1853
1848
  try {
1854
1849
  const track = await GetMetadata.viaRemote(__classPrivateFieldGet(this, _Database_remoteDatabase, "f"), opts);
1855
1850
  if (track === null) {
@@ -2134,7 +2129,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
2134
2129
  return (mod && mod.__esModule) ? mod : { "default": mod };
2135
2130
  };
2136
2131
  Object.defineProperty(exports, "__esModule", ({ value: true }));
2137
- exports.noopLogger = exports.REMOTEDB_MAX_DEVICE_ID = exports.playerNumberCeiling = exports.pickAvailableDeviceId = exports.MIXER_PLAYER_CEILING = exports.MAX_DEVICE_ID = exports.DEFAULT_MIXER_PLAYER_CEILING = exports.OneLibraryAdapter = exports.fetchFile = exports.loadAnlz = exports.isMetadataExtractionSupported = exports.extractMetadataFromDevice = exports.extractFullMetadata = exports.PictureType = exports.isArtworkExtractionSupported = exports.extractArtworkFromDevice = exports.extractArtwork = exports.PositionEmitter = void 0;
2132
+ exports.noopLogger = exports.REMOTEDB_MAX_DEVICE_ID = exports.playerNumberCeiling = exports.pickRemoteDbQueryId = exports.pickAvailableDeviceId = exports.MIXER_PLAYER_CEILING = exports.MAX_DEVICE_ID = exports.DEFAULT_MIXER_PLAYER_CEILING = exports.OneLibraryAdapter = exports.fetchFile = exports.loadAnlz = exports.isMetadataExtractionSupported = exports.extractMetadataFromDevice = exports.extractFullMetadata = exports.PictureType = exports.isArtworkExtractionSupported = exports.extractArtworkFromDevice = exports.extractArtwork = exports.PositionEmitter = void 0;
2138
2133
  __exportStar(__webpack_require__(/*! ./entities */ "./src/entities.ts"), exports);
2139
2134
  __exportStar(__webpack_require__(/*! ./mixstatus */ "./src/mixstatus/index.ts"), exports);
2140
2135
  __exportStar(__webpack_require__(/*! ./network */ "./src/network.ts"), exports);
@@ -2162,6 +2157,7 @@ Object.defineProperty(exports, "DEFAULT_MIXER_PLAYER_CEILING", ({ enumerable: tr
2162
2157
  Object.defineProperty(exports, "MAX_DEVICE_ID", ({ enumerable: true, get: function () { return device_id_1.MAX_DEVICE_ID; } }));
2163
2158
  Object.defineProperty(exports, "MIXER_PLAYER_CEILING", ({ enumerable: true, get: function () { return device_id_1.MIXER_PLAYER_CEILING; } }));
2164
2159
  Object.defineProperty(exports, "pickAvailableDeviceId", ({ enumerable: true, get: function () { return device_id_1.pickAvailableDeviceId; } }));
2160
+ Object.defineProperty(exports, "pickRemoteDbQueryId", ({ enumerable: true, get: function () { return device_id_1.pickRemoteDbQueryId; } }));
2165
2161
  Object.defineProperty(exports, "playerNumberCeiling", ({ enumerable: true, get: function () { return device_id_1.playerNumberCeiling; } }));
2166
2162
  Object.defineProperty(exports, "REMOTEDB_MAX_DEVICE_ID", ({ enumerable: true, get: function () { return device_id_1.REMOTEDB_MAX_DEVICE_ID; } }));
2167
2163
  var logger_1 = __webpack_require__(/*! ./logger */ "./src/logger.ts");
@@ -6793,7 +6789,7 @@ class MixstatusProcessor {
6793
6789
  // able to report it as live
6794
6790
  const startedAt = __classPrivateFieldGet(this, _MixstatusProcessor_lastStartTime, "f").get(deviceId);
6795
6791
  const requiredPlayTime = __classPrivateFieldGet(this, _MixstatusProcessor_config, "f").beatsUntilReported *
6796
- (0, utils_1.bpmToSeconds)(state.trackBPM, state.sliderPitch) *
6792
+ (0, utils_1.bpmToSeconds)(state.trackBPM, state.effectivePitch) *
6797
6793
  1000;
6798
6794
  if (__classPrivateFieldGet(this, _MixstatusProcessor_config, "f").mode === types_1.MixstatusMode.SmartTiming &&
6799
6795
  startedAt !== undefined &&
@@ -6804,7 +6800,7 @@ class MixstatusProcessor {
6804
6800
  // we can mark the track as truly stopped.
6805
6801
  const stoppedAt = __classPrivateFieldGet(this, _MixstatusProcessor_lastStoppedTimes, "f").get(deviceId);
6806
6802
  const requiredStopTime = __classPrivateFieldGet(this, _MixstatusProcessor_config, "f").allowedInterruptBeats *
6807
- (0, utils_1.bpmToSeconds)(state.trackBPM, state.sliderPitch) *
6803
+ (0, utils_1.bpmToSeconds)(state.trackBPM, state.effectivePitch) *
6808
6804
  1000;
6809
6805
  if (stoppedAt !== undefined && requiredStopTime <= Date.now() - stoppedAt) {
6810
6806
  __classPrivateFieldGet(this, _MixstatusProcessor_markPlayerStopped, "f").call(this, state);
@@ -7095,6 +7091,14 @@ class ProlinkNetwork {
7095
7091
  announcer = new virtualcdj_1.Announcer(vcdj, __classPrivateFieldGet(this, _ProlinkNetwork_announceSocket, "f"), this.deviceManager, __classPrivateFieldGet(this, _ProlinkNetwork_config, "f").iface, (_d = __classPrivateFieldGet(this, _ProlinkNetwork_config, "f").fullStartup) !== null && _d !== void 0 ? _d : false, (_e = __classPrivateFieldGet(this, _ProlinkNetwork_config, "f").announceToStagehand) !== null && _e !== void 0 ? _e : false, __classPrivateFieldGet(this, _ProlinkNetwork_logger, "f"));
7096
7092
  }
7097
7093
  announcer.start();
7094
+ // In Stagehand mode, unicast keep-alives to discovered players and the
7095
+ // mixer so they push live state to us (mixer 0x39/0x58, CDJ 0x69/waveforms).
7096
+ // Broadcast presence alone does not bootstrap that stream.
7097
+ let heartbeat = null;
7098
+ if (connectMethod === 'stagehand') {
7099
+ heartbeat = new virtualcdj_1.StagehandHeartbeat(vcdj, __classPrivateFieldGet(this, _ProlinkNetwork_statusSocket, "f"), __classPrivateFieldGet(this, _ProlinkNetwork_deviceManager, "f"), __classPrivateFieldGet(this, _ProlinkNetwork_logger, "f"));
7100
+ heartbeat.start();
7101
+ }
7098
7102
  // Create remote and local databases
7099
7103
  const remotedb = new remotedb_1.default(__classPrivateFieldGet(this, _ProlinkNetwork_deviceManager, "f"), vcdj);
7100
7104
  const localdb = new localdb_1.default(vcdj, __classPrivateFieldGet(this, _ProlinkNetwork_deviceManager, "f"), __classPrivateFieldGet(this, _ProlinkNetwork_statusEmitter, "f"), (_f = __classPrivateFieldGet(this, _ProlinkNetwork_config, "f").databasePreference) !== null && _f !== void 0 ? _f : 'auto');
@@ -7103,23 +7107,24 @@ class ProlinkNetwork {
7103
7107
  // Create controller service
7104
7108
  const control = new control_1.default(__classPrivateFieldGet(this, _ProlinkNetwork_beatSocket, "f"), vcdj);
7105
7109
  __classPrivateFieldSet(this, _ProlinkNetwork_state, types_1.NetworkState.Connected, "f");
7106
- __classPrivateFieldSet(this, _ProlinkNetwork_connection, { announcer, control, remotedb, localdb, database }, "f");
7110
+ __classPrivateFieldSet(this, _ProlinkNetwork_connection, { announcer, heartbeat, control, remotedb, localdb, database }, "f");
7107
7111
  tx.finish();
7108
7112
  }
7109
7113
  /**
7110
7114
  * Disconnect from the network
7111
7115
  */
7112
7116
  disconnect() {
7113
- var _a, _b, _c;
7117
+ var _a, _b, _c, _d, _e;
7114
7118
  if (__classPrivateFieldGet(this, _ProlinkNetwork_config, "f") === null) {
7115
7119
  throw new Error(connectErrorHelp);
7116
7120
  }
7117
7121
  // Stop announcing ourself
7118
7122
  (_a = __classPrivateFieldGet(this, _ProlinkNetwork_connection, "f")) === null || _a === void 0 ? void 0 : _a.announcer.stop();
7123
+ (_c = (_b = __classPrivateFieldGet(this, _ProlinkNetwork_connection, "f")) === null || _b === void 0 ? void 0 : _b.heartbeat) === null || _c === void 0 ? void 0 : _c.stop();
7119
7124
  // Disconnect devices from the remote and local databases
7120
7125
  for (const device of this.deviceManager.devices.values()) {
7121
- (_b = this.remotedb) === null || _b === void 0 ? void 0 : _b.disconnectFromDevice(device);
7122
- (_c = this.localdb) === null || _c === void 0 ? void 0 : _c.disconnectForDevice(device);
7126
+ (_d = this.remotedb) === null || _d === void 0 ? void 0 : _d.disconnectFromDevice(device);
7127
+ (_e = this.localdb) === null || _e === void 0 ? void 0 : _e.disconnectForDevice(device);
7123
7128
  }
7124
7129
  return this.close;
7125
7130
  }
@@ -10649,15 +10654,17 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
10649
10654
  var __importDefault = (this && this.__importDefault) || function (mod) {
10650
10655
  return (mod && mod.__esModule) ? mod : { "default": mod };
10651
10656
  };
10652
- var _Connection_socket, _Connection_txId, _Connection_lock, _QueryInterface_conn, _QueryInterface_hostDevice, _QueryInterface_lock, _RemoteDatabase_hostDevice, _RemoteDatabase_deviceManager, _RemoteDatabase_connections, _RemoteDatabase_deviceLocks;
10657
+ var _Connection_socket, _Connection_txId, _Connection_lock, _QueryInterface_conn, _QueryInterface_hostDevice, _QueryInterface_lock, _RemoteDatabase_instances, _RemoteDatabase_hostDevice, _RemoteDatabase_deviceManager, _RemoteDatabase_connections, _RemoteDatabase_deviceLocks, _RemoteDatabase_queryIds, _RemoteDatabase_pickQueryId, _RemoteDatabase_queryIdFor;
10653
10658
  Object.defineProperty(exports, "__esModule", ({ value: true }));
10654
10659
  exports.QueryInterface = exports.Connection = exports.Query = exports.MenuTarget = void 0;
10655
10660
  exports.getQueryName = getQueryName;
10656
10661
  const async_mutex_1 = __webpack_require__(/*! async-mutex */ "async-mutex");
10657
10662
  const promise_socket_1 = __importDefault(__webpack_require__(/*! promise-socket */ "promise-socket"));
10658
10663
  const net_1 = __webpack_require__(/*! net */ "net");
10664
+ const types_1 = __webpack_require__(/*! src/types */ "./src/types.ts");
10659
10665
  const Telemetry = __importStar(__webpack_require__(/*! src/utils/telemetry */ "./src/utils/telemetry.ts"));
10660
- const types_1 = __webpack_require__(/*! ./message/types */ "./src/remotedb/message/types.ts");
10666
+ const device_id_1 = __webpack_require__(/*! src/virtualcdj/device-id */ "./src/virtualcdj/device-id.ts");
10667
+ const types_2 = __webpack_require__(/*! ./message/types */ "./src/remotedb/message/types.ts");
10661
10668
  const constants_1 = __webpack_require__(/*! ./constants */ "./src/remotedb/constants.ts");
10662
10669
  const fields_1 = __webpack_require__(/*! ./fields */ "./src/remotedb/fields.ts");
10663
10670
  const message_1 = __webpack_require__(/*! ./message */ "./src/remotedb/message/index.ts");
@@ -10670,7 +10677,7 @@ var MenuTarget;
10670
10677
  (function (MenuTarget) {
10671
10678
  MenuTarget[MenuTarget["Main"] = 1] = "Main";
10672
10679
  })(MenuTarget || (exports.MenuTarget = MenuTarget = {}));
10673
- exports.Query = types_1.Request;
10680
+ exports.Query = types_2.Request;
10674
10681
  const QueryInverse = Object.fromEntries(Object.entries(exports.Query).map(e => [e[1], e[0]]));
10675
10682
  /**
10676
10683
  * Returns a string representation of a remote query
@@ -10716,7 +10723,7 @@ class Connection {
10716
10723
  var _a;
10717
10724
  const tx = span.startChild({
10718
10725
  op: 'writeMessage',
10719
- description: (0, types_1.getMessageName)(message.type),
10726
+ description: (0, types_2.getMessageName)(message.type),
10720
10727
  });
10721
10728
  message.transactionId = __classPrivateFieldSet(this, _Connection_txId, (_a = __classPrivateFieldGet(this, _Connection_txId, "f"), ++_a), "f");
10722
10729
  await __classPrivateFieldGet(this, _Connection_socket, "f").write(message.buffer);
@@ -10777,7 +10784,8 @@ _QueryInterface_conn = new WeakMap(), _QueryInterface_hostDevice = new WeakMap()
10777
10784
  * Service that maintains remote database connections with devices on the network.
10778
10785
  */
10779
10786
  class RemoteDatabase {
10780
- constructor(deviceManager, hostDevice) {
10787
+ constructor(deviceManager, hostDevice, { pickQueryId = true } = {}) {
10788
+ _RemoteDatabase_instances.add(this);
10781
10789
  _RemoteDatabase_hostDevice.set(this, void 0);
10782
10790
  _RemoteDatabase_deviceManager.set(this, void 0);
10783
10791
  /**
@@ -10788,6 +10796,17 @@ class RemoteDatabase {
10788
10796
  * Locks for each device when locating the connection
10789
10797
  */
10790
10798
  _RemoteDatabase_deviceLocks.set(this, new Map());
10799
+ /**
10800
+ * The device ID each connection introduced itself with. Queries on that
10801
+ * connection must carry the same ID.
10802
+ */
10803
+ _RemoteDatabase_queryIds.set(this, new Map());
10804
+ /**
10805
+ * Whether to substitute an in-range query ID when the host device sits
10806
+ * outside 1-6. Disabled only by protocol probes that need to observe how a
10807
+ * device treats the raw announced ID (see examples/remotedb-id-probe.ts).
10808
+ */
10809
+ _RemoteDatabase_pickQueryId.set(this, void 0);
10791
10810
  /**
10792
10811
  * Open a connection to the specified device for querying
10793
10812
  */
@@ -10812,20 +10831,22 @@ class RemoteDatabase {
10812
10831
  throw new Error(`Expected 0x01 during preamble handshake. Got ${data.value}`);
10813
10832
  }
10814
10833
  // Send introduction message to set context for querying
10834
+ const queryId = __classPrivateFieldGet(this, _RemoteDatabase_instances, "m", _RemoteDatabase_queryIdFor).call(this, device);
10815
10835
  const intro = new message_1.Message({
10816
10836
  transactionId: 0xfffffffe,
10817
- type: types_1.MessageType.Introduce,
10818
- args: [new fields_1.UInt32(__classPrivateFieldGet(this, _RemoteDatabase_hostDevice, "f").id)],
10837
+ type: types_2.MessageType.Introduce,
10838
+ args: [new fields_1.UInt32(queryId)],
10819
10839
  });
10820
10840
  await socket.write(intro.buffer);
10821
- const resp = await message_1.Message.fromStream(socket, types_1.MessageType.Success, tx);
10822
- if (resp.type !== types_1.MessageType.Success) {
10841
+ const resp = await message_1.Message.fromStream(socket, types_2.MessageType.Success, tx);
10842
+ if (resp.type !== types_2.MessageType.Success) {
10823
10843
  throw new Error(`Failed to introduce self to device ID: ${device.id}`);
10824
10844
  }
10825
10845
  // Clear socket timeout after successful connection — the per-device mutex
10826
10846
  // and application-level Promise.race timeouts handle query timeouts
10827
10847
  socket.stream.setTimeout(0);
10828
10848
  __classPrivateFieldGet(this, _RemoteDatabase_connections, "f").set(device.id, new Connection(device, socket));
10849
+ __classPrivateFieldGet(this, _RemoteDatabase_queryIds, "f").set(device.id, queryId);
10829
10850
  tx.finish();
10830
10851
  };
10831
10852
  /**
@@ -10839,22 +10860,26 @@ class RemoteDatabase {
10839
10860
  }
10840
10861
  const goodbye = new message_1.Message({
10841
10862
  transactionId: 0xfffffffe,
10842
- type: types_1.MessageType.Disconnect,
10863
+ type: types_2.MessageType.Disconnect,
10843
10864
  args: [],
10844
10865
  });
10845
10866
  await conn.writeMessage(goodbye, tx);
10846
10867
  conn.close();
10847
10868
  __classPrivateFieldGet(this, _RemoteDatabase_connections, "f").delete(device.id);
10869
+ __classPrivateFieldGet(this, _RemoteDatabase_queryIds, "f").delete(device.id);
10848
10870
  tx.finish();
10849
10871
  };
10850
10872
  __classPrivateFieldSet(this, _RemoteDatabase_deviceManager, deviceManager, "f");
10851
10873
  __classPrivateFieldSet(this, _RemoteDatabase_hostDevice, hostDevice, "f");
10874
+ __classPrivateFieldSet(this, _RemoteDatabase_pickQueryId, pickQueryId, "f");
10852
10875
  }
10853
10876
  /**
10854
- * The device we introduce ourselves as when opening a remote database
10855
- * connection. CDJs only answer queries from a host announcing an ID in the
10856
- * 1-6 range, so callers need to read this before deciding a remote lookup is
10857
- * even possible.
10877
+ * The device this service belongs to — the virtual CDJ announced on the
10878
+ * network. Note that this is NOT necessarily the ID carried inside remotedb
10879
+ * messages: CDJs only answer queries whose in-protocol device-ID byte is
10880
+ * 1-6, so when this device sits outside that range (announced above the
10881
+ * player range to avoid collisions), each connection picks an in-range
10882
+ * query ID via {@link pickRemoteDbQueryId} instead.
10858
10883
  */
10859
10884
  get hostDevice() {
10860
10885
  return __classPrivateFieldGet(this, _RemoteDatabase_hostDevice, "f");
@@ -10868,7 +10893,7 @@ class RemoteDatabase {
10868
10893
  * @returns null if the device does not export a remote database service
10869
10894
  */
10870
10895
  async get(deviceId) {
10871
- var _a;
10896
+ var _a, _b;
10872
10897
  const device = __classPrivateFieldGet(this, _RemoteDatabase_deviceManager, "f").devices.get(deviceId);
10873
10898
  if (device === undefined) {
10874
10899
  return null;
@@ -10883,14 +10908,28 @@ class RemoteDatabase {
10883
10908
  conn = __classPrivateFieldGet(this, _RemoteDatabase_connections, "f").get(deviceId);
10884
10909
  // NOTE: We pass the same lock we use for this device to the query
10885
10910
  // interface to ensure all query interfaces use the same lock.
10886
- return new QueryInterface(conn, lock, __classPrivateFieldGet(this, _RemoteDatabase_hostDevice, "f"));
10911
+ // Queries must carry the same device ID the connection introduced
10912
+ // itself with, which is not always the announced host ID.
10913
+ const queryId = (_b = __classPrivateFieldGet(this, _RemoteDatabase_queryIds, "f").get(device.id)) !== null && _b !== void 0 ? _b : __classPrivateFieldGet(this, _RemoteDatabase_hostDevice, "f").id;
10914
+ return new QueryInterface(conn, lock, { ...__classPrivateFieldGet(this, _RemoteDatabase_hostDevice, "f"), id: queryId });
10887
10915
  }
10888
10916
  finally {
10889
10917
  releaseLock();
10890
10918
  }
10891
10919
  }
10892
10920
  }
10893
- _RemoteDatabase_hostDevice = new WeakMap(), _RemoteDatabase_deviceManager = new WeakMap(), _RemoteDatabase_connections = new WeakMap(), _RemoteDatabase_deviceLocks = new WeakMap();
10921
+ _RemoteDatabase_hostDevice = new WeakMap(), _RemoteDatabase_deviceManager = new WeakMap(), _RemoteDatabase_connections = new WeakMap(), _RemoteDatabase_deviceLocks = new WeakMap(), _RemoteDatabase_queryIds = new WeakMap(), _RemoteDatabase_pickQueryId = new WeakMap(), _RemoteDatabase_instances = new WeakSet(), _RemoteDatabase_queryIdFor = function _RemoteDatabase_queryIdFor(device) {
10922
+ const hostId = __classPrivateFieldGet(this, _RemoteDatabase_hostDevice, "f").id;
10923
+ // An announced ID already inside the answered range keeps working exactly
10924
+ // as it always has. Rekordbox (which numbers itself far above 6) answers
10925
+ // queries regardless, so only CDJ targets need an in-range stand-in.
10926
+ if (!__classPrivateFieldGet(this, _RemoteDatabase_pickQueryId, "f") ||
10927
+ hostId <= device_id_1.REMOTEDB_MAX_DEVICE_ID ||
10928
+ device.type !== types_1.DeviceType.CDJ) {
10929
+ return hostId;
10930
+ }
10931
+ return (0, device_id_1.pickRemoteDbQueryId)(device.id, __classPrivateFieldGet(this, _RemoteDatabase_deviceManager, "f").devices.values());
10932
+ };
10894
10933
  exports["default"] = RemoteDatabase;
10895
10934
 
10896
10935
 
@@ -12184,10 +12223,29 @@ exports["default"] = PositionEmitter;
12184
12223
  Object.defineProperty(exports, "__esModule", ({ value: true }));
12185
12224
  exports.PlayState = exports.StatusFlag = void 0;
12186
12225
  /**
12187
- * Status flag bitmasks
12226
+ * Status flag bitmasks (byte 0x89 of the CDJ status packet).
12227
+ *
12228
+ * Verified against CDJ-3000 firmware (EP122 FW3.20): the deck assembles this
12229
+ * byte in `sub_d7d3c8` (0xd7d3c8) from the BeatSyncMaster state struct, one
12230
+ * source boolean per bit. The bits not named here:
12231
+ *
12232
+ * - bit 0 (0x01): structurally unused — no code path ever sets it (always 0).
12233
+ * - bit 2 (0x04): a real, dedicated beat-sync boolean (struct +5), but its
12234
+ * name did not survive in the stripped `usecase::sync` async-task code. It
12235
+ * is normally 0, which is why it has never been observed on the wire.
12236
+ * - bit 7 (0x80): a real bit (struct +0xa) that is default-set at init and
12237
+ * whose de-assertion itself triggers a state-change notification — best read
12238
+ * as a "sync-master active / handoff-in-progress" toggle. Also normally 0 in
12239
+ * steady state.
12188
12240
  */
12189
12241
  var StatusFlag;
12190
12242
  (function (StatusFlag) {
12243
+ /**
12244
+ * Degraded to BPM Sync: the player is still tracking the master's tempo, but
12245
+ * beat alignment was dropped after a pitch-bend / jog nudge. Firmware sources
12246
+ * this from BeatSyncMaster struct +4.
12247
+ */
12248
+ StatusFlag[StatusFlag["BpmSync"] = 2] = "BpmSync";
12191
12249
  StatusFlag[StatusFlag["OnAir"] = 8] = "OnAir";
12192
12250
  StatusFlag[StatusFlag["Sync"] = 16] = "Sync";
12193
12251
  StatusFlag[StatusFlag["Master"] = 32] = "Master";
@@ -12260,14 +12318,24 @@ function statusFromPacket(packet) {
12260
12318
  playState: packet[0x7b],
12261
12319
  isOnAir: (packet[0x89] & types_1.CDJStatus.StatusFlag.OnAir) !== 0,
12262
12320
  isSync: (packet[0x89] & types_1.CDJStatus.StatusFlag.Sync) !== 0,
12321
+ isBpmSync: (packet[0x89] & types_1.CDJStatus.StatusFlag.BpmSync) !== 0,
12263
12322
  isMaster: (packet[0x89] & types_1.CDJStatus.StatusFlag.Master) !== 0,
12264
12323
  isEmergencyMode: !!packet[0xba],
12265
12324
  trackBPM,
12266
- sliderPitch: calcPitch(packet.slice(0x8d, 0x8d + 3)),
12267
- effectivePitch: calcPitch(packet.slice(0x99, 0x99 + 3)),
12325
+ // The CDJ status packet carries four pitch values (firmware CDJ-3000 FW3.20,
12326
+ // sub_d7d610): Pitch1@0x8c and Pitch3@0xc0 are the *effective* pitch (in
12327
+ // effect / on the BPM display, from the fader or a synced master), while
12328
+ // Pitch2@0x98 and Pitch4@0xc4 track the *local fader* position. We expose
12329
+ // Pitch1 as `effectivePitch` and Pitch2 as `sliderPitch`.
12330
+ effectivePitch: calcPitch(packet.slice(0x8d, 0x8d + 3)),
12331
+ sliderPitch: calcPitch(packet.slice(0x99, 0x99 + 3)),
12268
12332
  beatInMeasure: packet[0xa6],
12269
12333
  beatsUntilCue,
12270
12334
  beat,
12335
+ // Player-type / capability byte (dysentery's "nx"). Guarded because the
12336
+ // length check above only guarantees 0xc8; older/short packets may not
12337
+ // reach 0xcc, in which case the field reads as 0 (version-gated to 0 anyway).
12338
+ deviceType: packet.length > 0xcc ? packet[0xcc] : 0,
12271
12339
  packetNum: packet.readUInt32BE(0xc8),
12272
12340
  };
12273
12341
  return status;
@@ -13012,11 +13080,17 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
13012
13080
  exports.DEFAULT_MIXER_PLAYER_CEILING = exports.MIXER_PLAYER_CEILING = exports.MAX_DEVICE_ID = exports.REMOTEDB_MAX_DEVICE_ID = void 0;
13013
13081
  exports.playerNumberCeiling = playerNumberCeiling;
13014
13082
  exports.pickAvailableDeviceId = pickAvailableDeviceId;
13083
+ exports.pickRemoteDbQueryId = pickRemoteDbQueryId;
13015
13084
  /**
13016
13085
  * The highest device ID a CDJ will answer remotedb queries from.
13017
13086
  *
13018
- * CDJs ignore metadata requests from devices numbered above this, which is why
13019
- * the 1-6 range is worth preferring even though real players live there too.
13087
+ * This limit applies to the device-ID byte inside the remotedb protocol (the
13088
+ * Introduce message and every query header), NOT to the ID we announce on the
13089
+ * network. Hardware-verified on a CDJ-3000 (2026-08-30, two players + DJM-V5,
13090
+ * announced as VCDJ 7 throughout): queries carrying 1-6 are all answered —
13091
+ * including IDs of absent players and the target's own ID — while 7 and above
13092
+ * are silently ignored (the TCP connection and Introduce succeed, the query
13093
+ * never gets a response). See {@link pickRemoteDbQueryId}.
13020
13094
  */
13021
13095
  exports.REMOTEDB_MAX_DEVICE_ID = 6;
13022
13096
  /**
@@ -13024,11 +13098,13 @@ exports.REMOTEDB_MAX_DEVICE_ID = 6;
13024
13098
  */
13025
13099
  exports.MAX_DEVICE_ID = 32;
13026
13100
  /**
13027
- * `DeviceType.Mixer`, inlined so this module stays free of runtime imports —
13028
- * it is re-exported into consumers' test environments, where pulling in the
13029
- * whole type module (and its ip-address dependency) is dead weight.
13101
+ * `DeviceType.Mixer` and `DeviceType.CDJ`, inlined so this module stays free
13102
+ * of runtime imports — it is re-exported into consumers' test environments,
13103
+ * where pulling in the whole type module (and its ip-address dependency) is
13104
+ * dead weight.
13030
13105
  */
13031
13106
  const MIXER_DEVICE_TYPE = 0x03;
13107
+ const CDJ_DEVICE_TYPE = 0x01;
13032
13108
  /**
13033
13109
  * How many player numbers each mixer can hand out.
13034
13110
  *
@@ -13127,24 +13203,233 @@ const ascending = (from, to) => {
13127
13203
  * Returns null when every ID from 1 to 32 is occupied, in which case there is
13128
13204
  * no safe ID and the caller must not join the network.
13129
13205
  */
13130
- function pickAvailableDeviceId(usedIds, { preferRemoteDb = false, playerCeiling } = {}) {
13206
+ function pickAvailableDeviceId(usedIds, { preferPlayerRange = false, playerCeiling } = {}) {
13131
13207
  var _a;
13132
13208
  const used = new Set(usedIds);
13133
13209
  const aboveThePlayers = playerCeiling === undefined
13134
13210
  ? descending(exports.REMOTEDB_MAX_DEVICE_ID, 1)
13135
13211
  : ascending(playerCeiling + 1, exports.REMOTEDB_MAX_DEVICE_ID);
13136
- const outsideRemoteDb = ascending(exports.REMOTEDB_MAX_DEVICE_ID + 1, exports.MAX_DEVICE_ID);
13212
+ const outsidePlayerRange = ascending(exports.REMOTEDB_MAX_DEVICE_ID + 1, exports.MAX_DEVICE_ID);
13137
13213
  // Numbers a player on this rig could be assigned. Last resort: better to
13138
13214
  // contend for a slot than not to join at all, and the announcer moves us if
13139
13215
  // the player that owns it shows up.
13140
13216
  const amongThePlayers = playerCeiling === undefined
13141
13217
  ? []
13142
13218
  : descending(Math.min(playerCeiling, exports.REMOTEDB_MAX_DEVICE_ID), 1);
13143
- const search = preferRemoteDb
13144
- ? [...aboveThePlayers, ...outsideRemoteDb, ...amongThePlayers]
13145
- : [...outsideRemoteDb, ...aboveThePlayers, ...amongThePlayers];
13219
+ const search = preferPlayerRange
13220
+ ? [...aboveThePlayers, ...outsidePlayerRange, ...amongThePlayers]
13221
+ : [...outsidePlayerRange, ...aboveThePlayers, ...amongThePlayers];
13146
13222
  return (_a = search.find(id => !used.has(id))) !== null && _a !== void 0 ? _a : null;
13147
13223
  }
13224
+ /**
13225
+ * Choose the device ID to carry inside remotedb messages when querying a CDJ.
13226
+ *
13227
+ * CDJs only answer remotedb queries whose in-protocol device-ID byte is in
13228
+ * 1-6, but that byte is independent of the ID we announce on the network — so
13229
+ * a virtual CDJ announced safely above the player range (say 7 behind a
13230
+ * DJM-V10) can still query metadata by carrying an in-range ID here. Verified
13231
+ * on a CDJ-3000 (2026-08-30): absent IDs and even the target's own ID are
13232
+ * answered; 7+ is silently dropped.
13233
+ *
13234
+ * Older players are documented (dysentery, nexus era) as stricter: the byte
13235
+ * had to be 1-4, belong to a player actually on the network, and not be the
13236
+ * player being queried. The preference order below satisfies those rules
13237
+ * whenever the rig makes it possible, then falls back through choices newer
13238
+ * players accept.
13239
+ */
13240
+ function pickRemoteDbQueryId(targetId, devices) {
13241
+ const playerIds = new Set();
13242
+ for (const device of devices) {
13243
+ if (device.type === CDJ_DEVICE_TYPE && device.id <= exports.REMOTEDB_MAX_DEVICE_ID) {
13244
+ playerIds.add(device.id);
13245
+ }
13246
+ }
13247
+ // A live player that isn't the target, lowest first — 1-4 satisfies every
13248
+ // documented era of the restriction.
13249
+ const otherPlayers = [...playerIds].filter(id => id !== targetId).sort((a, b) => a - b);
13250
+ if (otherPlayers.length > 0) {
13251
+ return otherPlayers[0];
13252
+ }
13253
+ // No other player on the rig: take the lowest 1-6 ID no player holds.
13254
+ for (let id = 1; id <= exports.REMOTEDB_MAX_DEVICE_ID; id++) {
13255
+ if (id !== targetId && !playerIds.has(id)) {
13256
+ return id;
13257
+ }
13258
+ }
13259
+ // Six players and every one of them is the target? Impossible, but the
13260
+ // target's own ID is answered too, so it is a safe closing fallback.
13261
+ return targetId;
13262
+ }
13263
+
13264
+
13265
+ /***/ },
13266
+
13267
+ /***/ "./src/virtualcdj/heartbeat.ts"
13268
+ /*!*************************************!*\
13269
+ !*** ./src/virtualcdj/heartbeat.ts ***!
13270
+ \*************************************/
13271
+ (__unused_webpack_module, exports, __webpack_require__) {
13272
+
13273
+ "use strict";
13274
+
13275
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
13276
+ if (kind === "m") throw new TypeError("Private method is not writable");
13277
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
13278
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
13279
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
13280
+ };
13281
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
13282
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
13283
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
13284
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
13285
+ };
13286
+ var _StagehandHeartbeat_instances, _StagehandHeartbeat_statusSocket, _StagehandHeartbeat_vcdj, _StagehandHeartbeat_deviceManager, _StagehandHeartbeat_logger, _StagehandHeartbeat_intervalHandle, _StagehandHeartbeat_sendHeartbeats;
13287
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
13288
+ exports.StagehandHeartbeat = exports.STAGEHAND_HEARTBEAT_INTERVAL = void 0;
13289
+ exports.makeStagehandMixerHeartbeat = makeStagehandMixerHeartbeat;
13290
+ exports.makeStagehandPlayerHeartbeat = makeStagehandPlayerHeartbeat;
13291
+ const constants_1 = __webpack_require__(/*! src/constants */ "./src/constants.ts");
13292
+ const logger_1 = __webpack_require__(/*! src/logger */ "./src/logger.ts");
13293
+ const types_1 = __webpack_require__(/*! src/types */ "./src/types.ts");
13294
+ /**
13295
+ * Cadence of the Stagehand unicast keep-alive, in milliseconds.
13296
+ *
13297
+ * The real iOS Stagehand app heartbeats every discovered player and the mixer
13298
+ * at ~4 Hz (250ms median inter-packet gap in captured traffic). This is the
13299
+ * stream that keeps AlphaTheta hardware unicasting live state (`0x39` mixer
13300
+ * fader/EQ, `0x58` VU on the mixer; `0x69` slim status, waveform families on
13301
+ * the CDJs) to our IP — subnet-broadcast presence alone (the `0x06` keep-alive)
13302
+ * is not enough to bootstrap it.
13303
+ */
13304
+ exports.STAGEHAND_HEARTBEAT_INTERVAL = 250;
13305
+ /**
13306
+ * Builds a Stagehand name field for a unicast frame.
13307
+ *
13308
+ * Unlike the 20-byte {@link buildName} used by the broadcast announce/claim
13309
+ * frames, unicast frames carry a 19-byte name field (offsets 11..29) with the
13310
+ * device type / model bytes beginning at offset 30. The name is ASCII, NUL
13311
+ * padded.
13312
+ */
13313
+ function buildUnicastName(name) {
13314
+ const field = new Uint8Array(19);
13315
+ field.set(Buffer.from(name, 'ascii').subarray(0, 19));
13316
+ return field;
13317
+ }
13318
+ /**
13319
+ * Build the Stagehand mixer keep-alive (`0x3a`, 40 bytes).
13320
+ *
13321
+ * Verified byte-for-byte against the real iOS Stagehand app unicasting to a
13322
+ * DJM-A9 at 4 Hz (Phase 4/5 captures §4.14.2). The 10-byte trailer
13323
+ * `21 02 00 fe 00 04 00 1c 00 00` is constant across every observed sample —
13324
+ * byte 33 (`0xfe`) is a VCDJ sentinel, not our runtime device number, so no
13325
+ * per-device bytes vary here.
13326
+ */
13327
+ function makeStagehandMixerHeartbeat(vcdj) {
13328
+ const parts = [
13329
+ ...constants_1.PROLINK_HEADER, // 0-9: magic
13330
+ 0x3a, // 10: type 0x3a (mixer keep-alive)
13331
+ ...buildUnicastName(vcdj.name), // 11-29: name (19 bytes)
13332
+ 0x21,
13333
+ 0x02,
13334
+ 0x00,
13335
+ 0xfe,
13336
+ 0x00,
13337
+ 0x04,
13338
+ 0x00,
13339
+ 0x1c,
13340
+ 0x00,
13341
+ 0x00, // 30-39: constant trailer
13342
+ ];
13343
+ return Uint8Array.from(parts);
13344
+ }
13345
+ /**
13346
+ * Build the Stagehand player keep-alive (`0x68`, 36 bytes).
13347
+ *
13348
+ * Verified byte-for-byte against the real iOS Stagehand app unicasting to a
13349
+ * CDJ-3000 at ~2-4 Hz. Body (offsets 30-35) is `03 01 00 3a 00 00`: `0x03`
13350
+ * persona/channel, `0x01` device subkind, `0x00` flag, `0x3a` Stagehand
13351
+ * model-code stamp, two reserved bytes. Constant across every observed sample.
13352
+ */
13353
+ function makeStagehandPlayerHeartbeat(vcdj) {
13354
+ const parts = [
13355
+ ...constants_1.PROLINK_HEADER, // 0-9: magic
13356
+ 0x68, // 10: type 0x68 (player keep-alive)
13357
+ ...buildUnicastName(vcdj.name), // 11-29: name (19 bytes)
13358
+ 0x03,
13359
+ 0x01,
13360
+ 0x00,
13361
+ 0x3a,
13362
+ 0x00,
13363
+ 0x00, // 30-35: constant body
13364
+ ];
13365
+ return Uint8Array.from(parts);
13366
+ }
13367
+ /**
13368
+ * Unicasts Stagehand keep-alive frames to every discovered player and mixer so
13369
+ * that AlphaTheta hardware begins (and keeps) pushing live state to our IP.
13370
+ *
13371
+ * The {@link StagehandAnnouncer} makes us *visible* on the network via subnet
13372
+ * broadcast; this heartbeat is what makes hardware actually *talk back*. It
13373
+ * targets the mixer with `0x3a` and each CDJ with `0x68`, mirroring the real
13374
+ * iOS Stagehand app. Frames are sent from the status socket (port 50002) to
13375
+ * each device's port 50002, which is where their unicast state replies land and
13376
+ * where {@link StatusEmitter} listens for them.
13377
+ */
13378
+ class StagehandHeartbeat {
13379
+ constructor(vcdj, statusSocket, deviceManager, logger = logger_1.noopLogger) {
13380
+ _StagehandHeartbeat_instances.add(this);
13381
+ _StagehandHeartbeat_statusSocket.set(this, void 0);
13382
+ _StagehandHeartbeat_vcdj.set(this, void 0);
13383
+ _StagehandHeartbeat_deviceManager.set(this, void 0);
13384
+ _StagehandHeartbeat_logger.set(this, void 0);
13385
+ _StagehandHeartbeat_intervalHandle.set(this, void 0);
13386
+ __classPrivateFieldSet(this, _StagehandHeartbeat_vcdj, vcdj, "f");
13387
+ __classPrivateFieldSet(this, _StagehandHeartbeat_statusSocket, statusSocket, "f");
13388
+ __classPrivateFieldSet(this, _StagehandHeartbeat_deviceManager, deviceManager, "f");
13389
+ __classPrivateFieldSet(this, _StagehandHeartbeat_logger, logger, "f");
13390
+ }
13391
+ start() {
13392
+ if (__classPrivateFieldGet(this, _StagehandHeartbeat_intervalHandle, "f") !== undefined) {
13393
+ return;
13394
+ }
13395
+ __classPrivateFieldGet(this, _StagehandHeartbeat_logger, "f").info('Starting Stagehand heartbeat (unicast keep-alive)');
13396
+ __classPrivateFieldGet(this, _StagehandHeartbeat_instances, "m", _StagehandHeartbeat_sendHeartbeats).call(this);
13397
+ __classPrivateFieldSet(this, _StagehandHeartbeat_intervalHandle, setInterval(() => __classPrivateFieldGet(this, _StagehandHeartbeat_instances, "m", _StagehandHeartbeat_sendHeartbeats).call(this), exports.STAGEHAND_HEARTBEAT_INTERVAL), "f");
13398
+ }
13399
+ stop() {
13400
+ __classPrivateFieldGet(this, _StagehandHeartbeat_logger, "f").info('Stopping Stagehand heartbeat');
13401
+ if (__classPrivateFieldGet(this, _StagehandHeartbeat_intervalHandle, "f") !== undefined) {
13402
+ clearInterval(__classPrivateFieldGet(this, _StagehandHeartbeat_intervalHandle, "f"));
13403
+ __classPrivateFieldSet(this, _StagehandHeartbeat_intervalHandle, undefined, "f");
13404
+ }
13405
+ }
13406
+ }
13407
+ exports.StagehandHeartbeat = StagehandHeartbeat;
13408
+ _StagehandHeartbeat_statusSocket = new WeakMap(), _StagehandHeartbeat_vcdj = new WeakMap(), _StagehandHeartbeat_deviceManager = new WeakMap(), _StagehandHeartbeat_logger = new WeakMap(), _StagehandHeartbeat_intervalHandle = new WeakMap(), _StagehandHeartbeat_instances = new WeakSet(), _StagehandHeartbeat_sendHeartbeats = function _StagehandHeartbeat_sendHeartbeats() {
13409
+ for (const device of __classPrivateFieldGet(this, _StagehandHeartbeat_deviceManager, "f").devices.values()) {
13410
+ // Never heartbeat ourselves.
13411
+ if (device.id === __classPrivateFieldGet(this, _StagehandHeartbeat_vcdj, "f").id) {
13412
+ continue;
13413
+ }
13414
+ let packet = null;
13415
+ switch (device.type) {
13416
+ case types_1.DeviceType.Mixer:
13417
+ packet = makeStagehandMixerHeartbeat(__classPrivateFieldGet(this, _StagehandHeartbeat_vcdj, "f"));
13418
+ break;
13419
+ case types_1.DeviceType.CDJ:
13420
+ packet = makeStagehandPlayerHeartbeat(__classPrivateFieldGet(this, _StagehandHeartbeat_vcdj, "f"));
13421
+ break;
13422
+ default:
13423
+ // Rekordbox / other Stagehand peers are not heartbeat targets.
13424
+ continue;
13425
+ }
13426
+ __classPrivateFieldGet(this, _StagehandHeartbeat_statusSocket, "f").send(packet, constants_1.STATUS_PORT, device.ip.address, err => {
13427
+ if (err) {
13428
+ __classPrivateFieldGet(this, _StagehandHeartbeat_logger, "f").debug(`Stagehand heartbeat to ${device.name} (${device.ip.address}) failed: ${err.message}`);
13429
+ }
13430
+ });
13431
+ }
13432
+ };
13148
13433
 
13149
13434
 
13150
13435
  /***/ },
@@ -13573,16 +13858,16 @@ _Announcer_announceSocket = new WeakMap(), _Announcer_deviceManager = new WeakMa
13573
13858
  }, _Announcer_handleConflict = function _Announcer_handleConflict() {
13574
13859
  // Stop announcing under the contested ID
13575
13860
  __classPrivateFieldGet(this, _Announcer_instances, "m", _Announcer_clearTimer).call(this);
13576
- // Stay in whichever range we were already in. Dropping out of 1-6 would
13577
- // silently lose remotedb metadata for unanalyzed and streaming tracks, so
13578
- // a conflict on player 5 looks for another free player number first.
13861
+ // Stay in whichever range we were already in, so a conflict on player 5
13862
+ // looks for another free player number first rather than jumping the
13863
+ // whole device out of the range the DJ configured it into.
13579
13864
  //
13580
13865
  // The mixer decides which of those numbers are actually safe: a player can
13581
13866
  // only be assigned a number its mixer has a channel for, so anything above
13582
13867
  // that is out of reach of the rig that just contested us.
13583
13868
  const devices = [...__classPrivateFieldGet(this, _Announcer_deviceManager, "f").devices.values()];
13584
13869
  const newId = (0, device_id_1.pickAvailableDeviceId)(devices.map(d => d.id), {
13585
- preferRemoteDb: __classPrivateFieldGet(this, _Announcer_vcdj, "f").id <= device_id_1.REMOTEDB_MAX_DEVICE_ID,
13870
+ preferPlayerRange: __classPrivateFieldGet(this, _Announcer_vcdj, "f").id <= device_id_1.REMOTEDB_MAX_DEVICE_ID,
13586
13871
  playerCeiling: (0, device_id_1.playerNumberCeiling)(devices),
13587
13872
  });
13588
13873
  if (newId === null) {
@@ -13685,6 +13970,7 @@ _Announcer_announceSocket = new WeakMap(), _Announcer_deviceManager = new WeakMa
13685
13970
  __classPrivateFieldSet(this, _Announcer_intervalHandle, setInterval(sendKeepAlive, constants_1.ANNOUNCE_INTERVAL), "f");
13686
13971
  };
13687
13972
  __exportStar(__webpack_require__(/*! ./device-id */ "./src/virtualcdj/device-id.ts"), exports);
13973
+ __exportStar(__webpack_require__(/*! ./heartbeat */ "./src/virtualcdj/heartbeat.ts"), exports);
13688
13974
  __exportStar(__webpack_require__(/*! ./stagehand */ "./src/virtualcdj/stagehand.ts"), exports);
13689
13975
 
13690
13976