magmastream 2.9.0-dev.4 → 2.9.0-dev.41

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.
@@ -1,17 +1,21 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ManagerEventTypes = exports.PlayerStateEventTypes = exports.AutoPlayPlatform = exports.SearchPlatform = exports.UseNodeOptions = exports.TrackPartial = exports.Manager = void 0;
3
+ exports.Manager = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const Utils_1 = require("./Utils");
6
6
  const collection_1 = require("@discordjs/collection");
7
7
  const events_1 = require("events");
8
+ const Node_1 = require("./Node");
8
9
  const __1 = require("..");
9
10
  const managerCheck_1 = tslib_1.__importDefault(require("../utils/managerCheck"));
10
11
  const blockedWords_1 = require("../config/blockedWords");
11
12
  const promises_1 = tslib_1.__importDefault(require("fs/promises"));
12
13
  const path_1 = tslib_1.__importDefault(require("path"));
14
+ const ioredis_1 = tslib_1.__importDefault(require("ioredis"));
15
+ const Enums_1 = require("./Enums");
16
+ const package_json_1 = require("../../package.json");
13
17
  /**
14
- * The main hub for interacting with Lavalink and using Magmastream,
18
+ * The main hub for interacting with Lavalink and using Magmastream.
15
19
  */
16
20
  class Manager extends events_1.EventEmitter {
17
21
  /** The map of players. */
@@ -21,6 +25,9 @@ class Manager extends events_1.EventEmitter {
21
25
  /** The options that were set. */
22
26
  options;
23
27
  initiated = false;
28
+ redis;
29
+ _send;
30
+ loadedPlugins = new Set();
24
31
  /**
25
32
  * Initiates the Manager class.
26
33
  * @param options
@@ -39,35 +46,53 @@ class Manager extends events_1.EventEmitter {
39
46
  constructor(options) {
40
47
  super();
41
48
  (0, managerCheck_1.default)(options);
49
+ // Initialize structures
42
50
  Utils_1.Structure.get("Player").init(this);
43
- Utils_1.Structure.get("Node").init(this);
44
51
  Utils_1.TrackUtils.init(this);
45
- Utils_1.AutoPlayUtils.init(this);
46
52
  if (options.trackPartial) {
47
53
  Utils_1.TrackUtils.setTrackPartial(options.trackPartial);
48
54
  delete options.trackPartial;
49
55
  }
56
+ if (options.clientId)
57
+ this.options.clientId = options.clientId;
58
+ if (options.clusterId)
59
+ this.options.clusterId = options.clusterId;
60
+ if (options.send && !this._send)
61
+ this._send = options.send;
50
62
  this.options = {
51
- enabledPlugins: [],
52
- nodes: [
63
+ ...options,
64
+ enabledPlugins: options.enabledPlugins ?? [],
65
+ nodes: options.nodes ?? [
53
66
  {
54
- identifier: "default",
55
- host: "localhost",
67
+ identifier: "Cheap lavalink hosting @",
68
+ host: "https://blackforthosting.com/products?category=lavalink",
69
+ port: 443,
70
+ password: "Try BlackForHosting",
71
+ useSSL: true,
56
72
  enableSessionResumeOption: false,
57
- sessionTimeoutMs: 1000,
73
+ sessionTimeoutSeconds: 1000,
74
+ nodePriority: 69,
58
75
  },
59
76
  ],
60
- playNextOnEnd: true,
61
- enablePriorityMode: false,
62
- clientName: "Magmastream",
63
- defaultSearchPlatform: SearchPlatform.YouTube,
64
- useNode: UseNodeOptions.LeastPlayers,
77
+ playNextOnEnd: options.playNextOnEnd ?? true,
78
+ enablePriorityMode: options.enablePriorityMode ?? false,
79
+ clientName: options.clientName ?? `Magmastream/${package_json_1.version}`,
80
+ defaultSearchPlatform: options.defaultSearchPlatform ?? Enums_1.SearchPlatform.YouTube,
81
+ useNode: options.useNode ?? Enums_1.UseNodeOptions.LeastPlayers,
65
82
  maxPreviousTracks: options.maxPreviousTracks ?? 20,
66
- ...options,
83
+ normalizeYouTubeTitles: options.normalizeYouTubeTitles ?? false,
84
+ stateStorage: {
85
+ ...options.stateStorage,
86
+ type: options.stateStorage?.type ?? Enums_1.StateStorageType.Memory,
87
+ deleteInactivePlayers: options.stateStorage?.deleteInactivePlayers ?? true,
88
+ },
89
+ autoPlaySearchPlatforms: options.autoPlaySearchPlatforms ?? [Enums_1.AutoPlayPlatform.YouTube],
90
+ send: this._send,
67
91
  };
92
+ Utils_1.AutoPlayUtils.init(this);
68
93
  if (this.options.nodes) {
69
94
  for (const nodeOptions of this.options.nodes)
70
- new (Utils_1.Structure.get("Node"))(nodeOptions);
95
+ new Node_1.Node(this, nodeOptions);
71
96
  }
72
97
  process.on("SIGINT", async () => {
73
98
  console.warn("\x1b[33mSIGINT received! Graceful shutdown initiated...\x1b[0m");
@@ -80,7 +105,7 @@ class Manager extends events_1.EventEmitter {
80
105
  }, 2000);
81
106
  }
82
107
  catch (error) {
83
- console.error("Error during shutdown:", error);
108
+ console.error(`[MANAGER] Error during shutdown: ${error}`);
84
109
  process.exit(1);
85
110
  }
86
111
  });
@@ -92,45 +117,53 @@ class Manager extends events_1.EventEmitter {
92
117
  process.exit(0);
93
118
  }
94
119
  catch (error) {
95
- console.error("Error during SIGTERM shutdown:", error);
120
+ console.error(`[MANAGER] Error during SIGTERM shutdown: ${error}`);
96
121
  process.exit(1);
97
122
  }
98
123
  });
99
124
  }
100
125
  /**
101
126
  * Initiates the Manager.
102
- * @param clientId - The Discord client ID (required).
127
+ * @param clientId - The Discord client ID (only required when not using any of the magmastream wrappers).
103
128
  * @param clusterId - The cluster ID which runs the current process (required).
104
129
  * @returns The manager instance.
105
130
  */
106
- init(clientId, clusterId = 0) {
131
+ async init(options = {}) {
107
132
  if (this.initiated) {
108
133
  return this;
109
134
  }
110
- if (typeof clientId !== "string" || !/^\d+$/.test(clientId)) {
111
- throw new Error('"clientId" must be a valid Discord client ID.');
135
+ const { clientId, clusterId = 0 } = options;
136
+ if (clientId !== undefined) {
137
+ if (typeof clientId !== "string" || !/^\d+$/.test(clientId)) {
138
+ throw new Error('"clientId" must be a valid Discord client ID.');
139
+ }
140
+ this.options.clientId = clientId;
112
141
  }
113
- this.options.clientId = clientId;
114
142
  if (typeof clusterId !== "number") {
115
- console.warn('"clusterId" is not a valid number, defaulting to 0.');
116
- clusterId = 0;
143
+ console.warn(`[MANAGER] "clusterId" is not a valid number, defaulting to 0.`);
144
+ this.options.clusterId = 0;
145
+ }
146
+ else {
147
+ this.options.clusterId = clusterId;
148
+ }
149
+ if (this.options.stateStorage.type === Enums_1.StateStorageType.Redis) {
150
+ const config = this.options.stateStorage.redisConfig;
151
+ this.redis = new ioredis_1.default({
152
+ host: config.host,
153
+ port: Number(config.port),
154
+ password: config.password,
155
+ db: config.db ?? 0,
156
+ });
117
157
  }
118
- this.options.clusterId = clusterId;
119
158
  for (const node of this.nodes.values()) {
120
159
  try {
121
- node.connect(); // Connect the node
160
+ await node.connect();
122
161
  }
123
162
  catch (err) {
124
- this.emit(ManagerEventTypes.NodeError, node, err);
125
- }
126
- }
127
- if (this.options.enabledPlugins) {
128
- for (const [index, plugin] of this.options.enabledPlugins.entries()) {
129
- if (!(plugin instanceof __1.Plugin))
130
- throw new RangeError(`Plugin at index ${index} does not extend Plugin.`);
131
- plugin.load(this);
163
+ this.emit(Enums_1.ManagerEventTypes.NodeError, node, err);
132
164
  }
133
165
  }
166
+ this.loadPlugins();
134
167
  this.initiated = true;
135
168
  return this;
136
169
  }
@@ -147,7 +180,7 @@ class Manager extends events_1.EventEmitter {
147
180
  const _query = typeof query === "string" ? { query } : query;
148
181
  const _source = _query.source ?? this.options.defaultSearchPlatform;
149
182
  let search = /^https?:\/\//.test(_query.query) ? _query.query : `${_source}:${_query.query}`;
150
- this.emit(ManagerEventTypes.Debug, `[MANAGER] Performing ${_source} search for: ${_query.query}`);
183
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Performing ${_source} search for: ${_query.query}`);
151
184
  try {
152
185
  const res = (await node.rest.get(`/v4/loadtracks?identifier=${encodeURIComponent(search)}`));
153
186
  if (!res)
@@ -155,13 +188,19 @@ class Manager extends events_1.EventEmitter {
155
188
  let tracks = [];
156
189
  let playlist = null;
157
190
  switch (res.loadType) {
158
- case Utils_1.LoadTypes.Search:
191
+ case Enums_1.LoadTypes.Search:
159
192
  tracks = res.data.map((track) => Utils_1.TrackUtils.build(track, requester));
160
193
  break;
161
- case Utils_1.LoadTypes.Track:
194
+ case Enums_1.LoadTypes.Short:
195
+ case Enums_1.LoadTypes.Track:
162
196
  tracks = [Utils_1.TrackUtils.build(res.data, requester)];
163
197
  break;
164
- case Utils_1.LoadTypes.Playlist: {
198
+ case Enums_1.LoadTypes.Album:
199
+ case Enums_1.LoadTypes.Artist:
200
+ case Enums_1.LoadTypes.Station:
201
+ case Enums_1.LoadTypes.Podcast:
202
+ case Enums_1.LoadTypes.Show:
203
+ case Enums_1.LoadTypes.Playlist: {
165
204
  const playlistData = res.data;
166
205
  tracks = playlistData.tracks.map((track) => Utils_1.TrackUtils.build(track, requester));
167
206
  playlist = {
@@ -190,14 +229,50 @@ class Manager extends events_1.EventEmitter {
190
229
  tracks = tracks.map(processTrack);
191
230
  }
192
231
  }
193
- const result = { loadType: res.loadType, tracks, playlist };
194
- this.emit(ManagerEventTypes.Debug, `[MANAGER] Result ${_source} search for: ${_query.query}: ${JSON.stringify(result)}`);
232
+ let result;
233
+ switch (res.loadType) {
234
+ case Enums_1.LoadTypes.Album:
235
+ case Enums_1.LoadTypes.Artist:
236
+ case Enums_1.LoadTypes.Station:
237
+ case Enums_1.LoadTypes.Podcast:
238
+ case Enums_1.LoadTypes.Show:
239
+ case Enums_1.LoadTypes.Playlist:
240
+ result = { loadType: res.loadType, tracks, playlist };
241
+ break;
242
+ case Enums_1.LoadTypes.Search:
243
+ result = { loadType: res.loadType, tracks };
244
+ break;
245
+ case Enums_1.LoadTypes.Short:
246
+ case Enums_1.LoadTypes.Track:
247
+ result = { loadType: res.loadType, tracks: [tracks[0]] };
248
+ break;
249
+ default:
250
+ return { loadType: res.loadType };
251
+ }
252
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Result ${_source} search for: ${_query.query}: ${JSON.stringify(result)}`);
195
253
  return result;
196
254
  }
197
255
  catch (err) {
198
256
  throw new Error(`An error occurred while searching: ${err}`);
199
257
  }
200
258
  }
259
+ /**
260
+ * Returns a player or undefined if it does not exist.
261
+ * @param guildId The guild ID of the player to retrieve.
262
+ * @returns The player if it exists, undefined otherwise.
263
+ */
264
+ getPlayer(guildId) {
265
+ return this.players.get(guildId);
266
+ }
267
+ /**
268
+ * @deprecated - Will be removed with v2.10.0 use {@link getPlayer} instead
269
+ * Returns a player or undefined if it does not exist.
270
+ * @param guildId The guild ID of the player to retrieve.
271
+ * @returns The player if it exists, undefined otherwise.
272
+ */
273
+ async get(guildId) {
274
+ return this.players.get(guildId);
275
+ }
201
276
  /**
202
277
  * Creates a player or returns one if it already exists.
203
278
  * @param options The options to create the player with.
@@ -208,29 +283,20 @@ class Manager extends events_1.EventEmitter {
208
283
  return this.players.get(options.guildId);
209
284
  }
210
285
  // Create a new player with the given options
211
- this.emit(ManagerEventTypes.Debug, `[MANAGER] Creating new player with options: ${JSON.stringify(options)}`);
286
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Creating new player with options: ${JSON.stringify(options)}`);
212
287
  return new (Utils_1.Structure.get("Player"))(options);
213
288
  }
214
- /**
215
- * Returns a player or undefined if it does not exist.
216
- * @param guildId The guild ID of the player to retrieve.
217
- * @returns The player if it exists, undefined otherwise.
218
- */
219
- get(guildId) {
220
- return this.players.get(guildId);
221
- }
222
289
  /**
223
290
  * Destroys a player.
224
291
  * @param guildId The guild ID of the player to destroy.
225
292
  * @returns A promise that resolves when the player has been destroyed.
226
293
  */
227
294
  async destroy(guildId) {
228
- // Emit debug message for player destruction
229
- this.emit(ManagerEventTypes.Debug, `[MANAGER] Destroying player: ${guildId}`);
230
- // Remove the player from the manager's collection
231
- this.players.delete(guildId);
232
- // Clean up any inactive players
233
- await this.cleanupInactivePlayers();
295
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Destroying player: ${guildId}`);
296
+ const player = this.getPlayer(guildId);
297
+ if (!player)
298
+ return;
299
+ await player.destroy();
234
300
  }
235
301
  /**
236
302
  * Creates a new node or returns an existing one if it already exists.
@@ -238,15 +304,19 @@ class Manager extends events_1.EventEmitter {
238
304
  * @returns The created node.
239
305
  */
240
306
  createNode(options) {
307
+ const key = options.identifier || options.host;
241
308
  // Check if the node already exists in the manager's collection
242
- if (this.nodes.has(options.identifier || options.host)) {
309
+ if (this.nodes.has(key)) {
243
310
  // Return the existing node if it does
244
- return this.nodes.get(options.identifier || options.host);
311
+ return this.nodes.get(key);
245
312
  }
313
+ const node = new Node_1.Node(this, options);
314
+ // Set the node in the manager's collection
315
+ this.nodes.set(key, node);
246
316
  // Emit a debug event for node creation
247
- this.emit(ManagerEventTypes.Debug, `[MANAGER] Creating new node with options: ${JSON.stringify(options)}`);
248
- // Create a new node with the given options
249
- return new (Utils_1.Structure.get("Node"))(options);
317
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Creating new node with options: ${JSON.stringify(options)}`);
318
+ // Return the created node
319
+ return node;
250
320
  }
251
321
  /**
252
322
  * Destroys a node if it exists. Emits a debug event if the node is found and destroyed.
@@ -256,11 +326,13 @@ class Manager extends events_1.EventEmitter {
256
326
  */
257
327
  async destroyNode(identifier) {
258
328
  const node = this.nodes.get(identifier);
259
- if (!node)
329
+ if (!node) {
330
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Tried to destroy non-existent node: ${identifier}`);
260
331
  return;
261
- this.emit(ManagerEventTypes.Debug, `[MANAGER] Destroying node: ${identifier}`);
262
- await node.destroy();
332
+ }
333
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Destroying node: ${identifier}`);
263
334
  this.nodes.delete(identifier);
335
+ await node.destroy();
264
336
  }
265
337
  /**
266
338
  * Attaches an event listener to the manager.
@@ -283,21 +355,15 @@ class Manager extends events_1.EventEmitter {
283
355
  const update = "d" in data ? data.d : data;
284
356
  if (!this.isValidUpdate(update))
285
357
  return;
286
- const player = this.players.get(update.guild_id);
358
+ const player = this.getPlayer(update.guild_id);
287
359
  if (!player)
288
360
  return;
289
- this.emit(ManagerEventTypes.Debug, `[MANAGER] Updating voice state: ${JSON.stringify(update)}`);
361
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Updating voice state: ${JSON.stringify(update)}`);
290
362
  if ("token" in update) {
291
363
  return await this.handleVoiceServerUpdate(player, update);
292
364
  }
293
365
  if (update.user_id !== this.options.clientId)
294
366
  return;
295
- if (!player.voiceState.sessionId && player.voiceState.event) {
296
- if (player.state !== Utils_1.StateTypes.Disconnected) {
297
- await player.destroy();
298
- }
299
- return;
300
- }
301
367
  return await this.handleVoiceStateUpdate(player, update);
302
368
  }
303
369
  /**
@@ -308,7 +374,7 @@ class Manager extends events_1.EventEmitter {
308
374
  * @throws Will throw an error if no nodes are available or if the API request fails.
309
375
  */
310
376
  decodeTracks(tracks) {
311
- this.emit(ManagerEventTypes.Debug, `[MANAGER] Decoding tracks: ${JSON.stringify(tracks)}`);
377
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Decoding tracks: ${JSON.stringify(tracks)}`);
312
378
  return new Promise(async (resolve, reject) => {
313
379
  const node = this.nodes.first();
314
380
  if (!node)
@@ -332,200 +398,464 @@ class Manager extends events_1.EventEmitter {
332
398
  return res[0];
333
399
  }
334
400
  /**
335
- * Saves player states to the JSON file.
401
+ * Saves player states.
336
402
  * @param {string} guildId - The guild ID of the player to save
337
403
  */
338
404
  async savePlayerState(guildId) {
339
- try {
340
- const playerStateFilePath = await this.getPlayerFilePath(guildId);
341
- const player = this.players.get(guildId);
342
- if (!player || player.state === Utils_1.StateTypes.Disconnected || !player.voiceChannelId) {
343
- console.warn(`Skipping save for inactive player: ${guildId}`);
405
+ switch (this.options.stateStorage.type) {
406
+ case Enums_1.StateStorageType.Memory:
407
+ case Enums_1.StateStorageType.JSON:
408
+ {
409
+ try {
410
+ const playerStateFilePath = await this.getPlayerFilePath(guildId);
411
+ const player = this.getPlayer(guildId);
412
+ if (!player || player.state === Enums_1.StateTypes.Disconnected || !player.voiceChannelId) {
413
+ console.warn(`[MANAGER] Skipping save for inactive player: ${guildId}`);
414
+ return;
415
+ }
416
+ const serializedPlayer = await this.serializePlayer(player);
417
+ await promises_1.default.writeFile(playerStateFilePath, JSON.stringify(serializedPlayer, null, 2), "utf-8");
418
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Player state saved: ${guildId}`);
419
+ }
420
+ catch (error) {
421
+ console.error(`[MANAGER] Error saving player state for guild ${guildId}:`, error);
422
+ }
423
+ }
424
+ break;
425
+ case Enums_1.StateStorageType.Redis:
426
+ {
427
+ try {
428
+ const player = this.getPlayer(guildId);
429
+ if (!player || player.state === Enums_1.StateTypes.Disconnected || !player.voiceChannelId) {
430
+ console.warn(`[MANAGER] Skipping save for inactive player: ${guildId}`);
431
+ return;
432
+ }
433
+ const serializedPlayer = await this.serializePlayer(player);
434
+ const redisKey = `${this.options.stateStorage.redisConfig.prefix?.endsWith(":")
435
+ ? this.options.stateStorage.redisConfig.prefix
436
+ : this.options.stateStorage.redisConfig.prefix ?? "magmastream:"}playerstore:${guildId}`;
437
+ await this.redis.set(redisKey, JSON.stringify(serializedPlayer));
438
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Player state saved to Redis: ${guildId}`);
439
+ }
440
+ catch (error) {
441
+ console.error(`[MANAGER] Error saving player state to Redis for guild ${guildId}:`, error);
442
+ }
443
+ }
444
+ break;
445
+ default:
344
446
  return;
345
- }
346
- const serializedPlayer = this.serializePlayer(player);
347
- await promises_1.default.writeFile(playerStateFilePath, JSON.stringify(serializedPlayer, null, 2), "utf-8");
348
- this.emit(ManagerEventTypes.Debug, `[MANAGER] Player state saved: ${guildId}`);
349
- }
350
- catch (error) {
351
- console.error(`Error saving player state for guild ${guildId}:`, error);
352
447
  }
353
448
  }
449
+ /**
450
+ * Sleeps for a specified amount of time.
451
+ * @param ms The amount of time to sleep in milliseconds.
452
+ * @returns A promise that resolves after the specified amount of time.
453
+ */
454
+ async sleep(ms) {
455
+ return new Promise((resolve) => setTimeout(resolve, ms));
456
+ }
354
457
  /**
355
458
  * Loads player states from the JSON file.
356
459
  * @param nodeId The ID of the node to load player states from.
357
460
  * @returns A promise that resolves when the player states have been loaded.
358
461
  */
359
462
  async loadPlayerStates(nodeId) {
360
- this.emit(ManagerEventTypes.Debug, "[MANAGER] Loading saved players.");
463
+ this.emit(Enums_1.ManagerEventTypes.Debug, "[MANAGER] Loading saved players.");
361
464
  const node = this.nodes.get(nodeId);
362
465
  if (!node)
363
466
  throw new Error(`Could not find node: ${nodeId}`);
364
467
  const info = (await node.rest.getAllPlayers());
365
- const playerStatesDir = path_1.default.join(process.cwd(), "magmastream", "dist", "sessionData", "players");
366
- try {
367
- // Check if the directory exists, and create it if it doesn't
368
- await promises_1.default.access(playerStatesDir).catch(async () => {
369
- await promises_1.default.mkdir(playerStatesDir, { recursive: true });
370
- this.emit(ManagerEventTypes.Debug, `[MANAGER] Created directory: ${playerStatesDir}`);
371
- });
372
- // Read the contents of the directory
373
- const playerFiles = await promises_1.default.readdir(playerStatesDir);
374
- // Process each file in the directory
375
- for (const file of playerFiles) {
376
- const filePath = path_1.default.join(playerStatesDir, file);
377
- try {
378
- // Check if the file exists (though readdir should only return valid files)
379
- await promises_1.default.access(filePath);
380
- // Read the file asynchronously
381
- const data = await promises_1.default.readFile(filePath, "utf-8");
382
- const state = JSON.parse(data);
383
- if (state && typeof state === "object" && state.guildId && state.node.options.identifier === nodeId) {
384
- const lavaPlayer = info.find((player) => player.guildId === state.guildId);
385
- if (!lavaPlayer) {
386
- await this.destroy(state.guildId);
387
- }
388
- const playerOptions = {
389
- guildId: state.options.guildId,
390
- textChannelId: state.options.textChannelId,
391
- voiceChannelId: state.options.voiceChannelId,
392
- selfDeafen: state.options.selfDeafen,
393
- volume: lavaPlayer.volume || state.options.volume,
394
- };
395
- this.emit(ManagerEventTypes.Debug, `[MANAGER] Recreating player: ${state.guildId} from saved file: ${JSON.stringify(state.options)}`);
396
- const player = this.create(playerOptions);
397
- await player.node.rest.updatePlayer({
398
- guildId: state.options.guildId,
399
- data: { voice: { token: state.voiceState.event.token, endpoint: state.voiceState.event.endpoint, sessionId: state.voiceState.sessionId } },
468
+ switch (this.options.stateStorage.type) {
469
+ case Enums_1.StateStorageType.JSON:
470
+ {
471
+ const playerStatesDir = path_1.default.join(process.cwd(), "magmastream", "dist", "sessionData", "playerStore");
472
+ try {
473
+ // Check if the directory exists, and create it if it doesn't
474
+ await promises_1.default.access(playerStatesDir).catch(async () => {
475
+ await promises_1.default.mkdir(playerStatesDir, { recursive: true });
476
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Created directory: ${playerStatesDir}`);
400
477
  });
401
- player.connect();
402
- const tracks = [];
403
- const currentTrack = state.queue.current;
404
- const queueTracks = state.queue.tracks;
405
- if (lavaPlayer) {
406
- if (lavaPlayer.track) {
407
- tracks.push(...queueTracks);
408
- if (currentTrack && currentTrack.uri === lavaPlayer.track.info.uri) {
409
- player.queue.current = Utils_1.TrackUtils.build(lavaPlayer.track, currentTrack.requester);
410
- }
411
- }
412
- else {
413
- if (!currentTrack) {
414
- const payload = {
415
- reason: Utils_1.TrackEndReasonTypes.Finished,
478
+ // Read the contents of the directory
479
+ const playerFiles = await promises_1.default.readdir(playerStatesDir);
480
+ // Process each file in the directory
481
+ for (const file of playerFiles) {
482
+ const filePath = path_1.default.join(playerStatesDir, file);
483
+ try {
484
+ // Check if the file exists (though readdir should only return valid files)
485
+ await promises_1.default.access(filePath); // Check if the file exists
486
+ const data = await promises_1.default.readFile(filePath, "utf-8");
487
+ const state = JSON.parse(data);
488
+ if (state.clusterId !== this.options.clusterId)
489
+ continue;
490
+ if (state && typeof state === "object" && state.guildId && state.node.options.identifier === nodeId) {
491
+ const lavaPlayer = info.find((player) => player.guildId === state.guildId);
492
+ if (!lavaPlayer) {
493
+ await this.destroy(state.guildId);
494
+ }
495
+ const playerOptions = {
496
+ guildId: state.options.guildId,
497
+ textChannelId: state.options.textChannelId,
498
+ voiceChannelId: state.options.voiceChannelId,
499
+ selfDeafen: state.options.selfDeafen,
500
+ volume: lavaPlayer.volume || state.options.volume,
501
+ node: nodeId,
416
502
  };
417
- await node.queueEnd(player, currentTrack, payload);
418
- }
419
- else {
420
- tracks.push(currentTrack, ...queueTracks);
503
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Recreating player: ${state.guildId} from saved file: ${JSON.stringify(state.options)}`);
504
+ const player = this.create(playerOptions);
505
+ await player.node.rest.updatePlayer({
506
+ guildId: state.options.guildId,
507
+ data: { voice: { token: state.voiceState.event.token, endpoint: state.voiceState.event.endpoint, sessionId: state.voiceState.sessionId } },
508
+ });
509
+ player.connect();
510
+ const tracks = [];
511
+ const currentTrack = state.queue.current;
512
+ const queueTracks = state.queue.tracks;
513
+ if (state.isAutoplay) {
514
+ Object.setPrototypeOf(state.data.clientUser, { constructor: { name: "User" } });
515
+ player.setAutoplay(true, state.data.clientUser, state.autoplayTries);
516
+ }
517
+ if (lavaPlayer?.track) {
518
+ // If lavaPlayer has a track, push all queue tracks
519
+ tracks.push(...queueTracks);
520
+ // Set current track if matches lavaPlayer's track URI
521
+ if (currentTrack && currentTrack.uri === lavaPlayer.track.info.uri) {
522
+ await player.queue.setCurrent(Utils_1.TrackUtils.build(lavaPlayer.track, currentTrack.requester));
523
+ }
524
+ // Add tracks to queue
525
+ if (tracks.length > 0) {
526
+ await player.queue.clear();
527
+ await player.queue.add(tracks);
528
+ }
529
+ }
530
+ else {
531
+ // LavaPlayer missing track or lavaPlayer is falsy
532
+ if (currentTrack) {
533
+ if (queueTracks.length > 0) {
534
+ tracks.push(...queueTracks);
535
+ await player.queue.clear();
536
+ await player.queue.add(tracks);
537
+ }
538
+ await node.trackEnd(player, currentTrack, {
539
+ reason: Enums_1.TrackEndReasonTypes.Finished,
540
+ type: "TrackEndEvent",
541
+ });
542
+ }
543
+ else {
544
+ // No current track, check previous queue for last track
545
+ const previousQueue = await player.queue.getPrevious();
546
+ const lastTrack = previousQueue?.at(-1);
547
+ if (lastTrack) {
548
+ if (queueTracks.length === 0) {
549
+ // If no tracks in queue, end last track
550
+ await node.trackEnd(player, lastTrack, {
551
+ reason: Enums_1.TrackEndReasonTypes.Finished,
552
+ type: "TrackEndEvent",
553
+ });
554
+ }
555
+ else {
556
+ // If there are queued tracks, add them
557
+ tracks.push(...queueTracks);
558
+ if (tracks.length > 0) {
559
+ await player.queue.clear();
560
+ await player.queue.add(tracks);
561
+ }
562
+ }
563
+ }
564
+ else {
565
+ if (queueTracks.length > 0) {
566
+ tracks.push(...queueTracks);
567
+ if (tracks.length > 0) {
568
+ await player.queue.clear();
569
+ await player.queue.add(tracks);
570
+ }
571
+ await node.trackEnd(player, lastTrack, {
572
+ reason: Enums_1.TrackEndReasonTypes.Finished,
573
+ type: "TrackEndEvent",
574
+ });
575
+ }
576
+ }
577
+ }
578
+ }
579
+ if (state.queue.previous.length > 0) {
580
+ await player.queue.addPrevious(state.queue.previous);
581
+ }
582
+ else {
583
+ await player.queue.clearPrevious();
584
+ }
585
+ if (state.paused) {
586
+ await player.pause(true);
587
+ }
588
+ else {
589
+ player.paused = false;
590
+ }
591
+ if (state.trackRepeat)
592
+ player.setTrackRepeat(true);
593
+ if (state.queueRepeat)
594
+ player.setQueueRepeat(true);
595
+ if (state.dynamicRepeat) {
596
+ player.setDynamicRepeat(state.dynamicRepeat, state.dynamicLoopInterval._idleTimeout);
597
+ }
598
+ if (state.data) {
599
+ for (const [name, value] of Object.entries(state.data)) {
600
+ player.set(name, value);
601
+ }
602
+ }
603
+ const filterActions = {
604
+ bassboost: () => player.filters.bassBoost(state.filters.bassBoostlevel),
605
+ distort: (enabled) => player.filters.distort(enabled),
606
+ setDistortion: () => player.filters.setDistortion(state.filters.distortion),
607
+ eightD: (enabled) => player.filters.eightD(enabled),
608
+ setKaraoke: () => player.filters.setKaraoke(state.filters.karaoke),
609
+ nightcore: (enabled) => player.filters.nightcore(enabled),
610
+ slowmo: (enabled) => player.filters.slowmo(enabled),
611
+ soft: (enabled) => player.filters.soft(enabled),
612
+ trebleBass: (enabled) => player.filters.trebleBass(enabled),
613
+ setTimescale: () => player.filters.setTimescale(state.filters.timescale),
614
+ tv: (enabled) => player.filters.tv(enabled),
615
+ vibrato: () => player.filters.setVibrato(state.filters.vibrato),
616
+ vaporwave: (enabled) => player.filters.vaporwave(enabled),
617
+ pop: (enabled) => player.filters.pop(enabled),
618
+ party: (enabled) => player.filters.party(enabled),
619
+ earrape: (enabled) => player.filters.earrape(enabled),
620
+ electronic: (enabled) => player.filters.electronic(enabled),
621
+ radio: (enabled) => player.filters.radio(enabled),
622
+ setRotation: () => player.filters.setRotation(state.filters.rotation),
623
+ tremolo: (enabled) => player.filters.tremolo(enabled),
624
+ china: (enabled) => player.filters.china(enabled),
625
+ chipmunk: (enabled) => player.filters.chipmunk(enabled),
626
+ darthvader: (enabled) => player.filters.darthvader(enabled),
627
+ daycore: (enabled) => player.filters.daycore(enabled),
628
+ doubletime: (enabled) => player.filters.doubletime(enabled),
629
+ demon: (enabled) => player.filters.demon(enabled),
630
+ };
631
+ // Iterate through filterStatus and apply the enabled filters
632
+ for (const [filter, isEnabled] of Object.entries(state.filters.filterStatus)) {
633
+ if (isEnabled && filterActions[filter]) {
634
+ filterActions[filter](true);
635
+ }
636
+ }
637
+ this.emit(Enums_1.ManagerEventTypes.PlayerRestored, player, node);
638
+ await this.sleep(1000);
421
639
  }
422
640
  }
641
+ catch (error) {
642
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error processing file ${filePath}: ${error}`);
643
+ continue; // Skip to the next file if there's an error
644
+ }
423
645
  }
424
- else {
425
- if (!currentTrack) {
426
- const payload = {
427
- reason: Utils_1.TrackEndReasonTypes.Finished,
428
- };
429
- await node.queueEnd(player, currentTrack, payload);
646
+ // Delete all files inside playerStatesDir where nodeId matches
647
+ for (const file of playerFiles) {
648
+ const filePath = path_1.default.join(playerStatesDir, file);
649
+ try {
650
+ await promises_1.default.access(filePath); // Check if the file exists
651
+ const data = await promises_1.default.readFile(filePath, "utf-8");
652
+ const state = JSON.parse(data);
653
+ if (state && typeof state === "object" && state.node.options.identifier === nodeId) {
654
+ await promises_1.default.unlink(filePath); // Delete the file asynchronously
655
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Deleted player state file: ${filePath}`);
656
+ }
430
657
  }
431
- else {
432
- tracks.push(currentTrack, ...queueTracks);
658
+ catch (error) {
659
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error deleting file ${filePath}: ${error}`);
660
+ continue; // Skip to the next file if there's an error
433
661
  }
434
662
  }
435
- if (tracks.length > 0) {
436
- player.queue.add(tracks);
437
- }
438
- if (state.queue.previous.length > 0) {
439
- player.queue.previous = state.queue.previous;
440
- }
441
- else {
442
- player.queue.previous = [];
443
- }
444
- if (state.paused) {
445
- await player.pause(true);
446
- }
447
- else {
448
- player.paused = false;
449
- }
450
- if (state.trackRepeat)
451
- player.setTrackRepeat(true);
452
- if (state.queueRepeat)
453
- player.setQueueRepeat(true);
454
- if (state.dynamicRepeat) {
455
- player.setDynamicRepeat(state.dynamicRepeat, state.dynamicLoopInterval._idleTimeout);
456
- }
457
- if (state.isAutoplay) {
458
- Object.setPrototypeOf(state.data.clientUser, { constructor: { name: "User" } });
459
- player.setAutoplay(true, state.data.clientUser, state.autoplayTries);
460
- }
461
- if (state.data) {
462
- for (const [name, value] of Object.entries(state.data)) {
463
- player.set(name, value);
663
+ }
664
+ catch (error) {
665
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error loading player states: ${error}`);
666
+ }
667
+ }
668
+ break;
669
+ case Enums_1.StateStorageType.Redis:
670
+ {
671
+ try {
672
+ // Get all keys matching our pattern
673
+ const redisKeyPattern = `${this.options.stateStorage.redisConfig.prefix?.endsWith(":")
674
+ ? this.options.stateStorage.redisConfig.prefix
675
+ : this.options.stateStorage.redisConfig.prefix ?? "magmastream:"}playerstore:*`;
676
+ const keys = await this.redis.keys(redisKeyPattern);
677
+ for (const key of keys) {
678
+ try {
679
+ const data = await this.redis.get(key);
680
+ if (!data)
681
+ continue;
682
+ const state = JSON.parse(data);
683
+ if (!state || typeof state !== "object" || state.clusterId !== this.options.clusterId)
684
+ continue;
685
+ const guildId = key.split(":").pop();
686
+ if (!guildId)
687
+ continue;
688
+ if (state.node?.options?.identifier === nodeId) {
689
+ const lavaPlayer = info.find((player) => player.guildId === guildId);
690
+ if (!lavaPlayer) {
691
+ await this.destroy(guildId);
692
+ }
693
+ const playerOptions = {
694
+ guildId: state.options.guildId,
695
+ textChannelId: state.options.textChannelId,
696
+ voiceChannelId: state.options.voiceChannelId,
697
+ selfDeafen: state.options.selfDeafen,
698
+ volume: lavaPlayer?.volume || state.options.volume,
699
+ node: nodeId,
700
+ };
701
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Recreating player: ${guildId} from Redis`);
702
+ const player = this.create(playerOptions);
703
+ await player.node.rest.updatePlayer({
704
+ guildId: state.options.guildId,
705
+ data: { voice: { token: state.voiceState.event.token, endpoint: state.voiceState.event.endpoint, sessionId: state.voiceState.sessionId } },
706
+ });
707
+ player.connect();
708
+ // Rest of the player state restoration code (tracks, filters, etc.)
709
+ const tracks = [];
710
+ const currentTrack = state.queue.current;
711
+ const queueTracks = state.queue.tracks;
712
+ if (state.isAutoplay) {
713
+ Object.setPrototypeOf(state.data.clientUser, { constructor: { name: "User" } });
714
+ player.setAutoplay(true, state.data.clientUser, state.autoplayTries);
715
+ }
716
+ if (lavaPlayer?.track) {
717
+ // If lavaPlayer has a track, push all queue tracks
718
+ tracks.push(...queueTracks);
719
+ // Set current track if matches lavaPlayer's track URI
720
+ if (currentTrack && currentTrack.uri === lavaPlayer.track.info.uri) {
721
+ await player.queue.setCurrent(Utils_1.TrackUtils.build(lavaPlayer.track, currentTrack.requester));
722
+ }
723
+ // Add tracks to queue
724
+ if (tracks.length > 0) {
725
+ await player.queue.clear();
726
+ await player.queue.add(tracks);
727
+ }
728
+ }
729
+ else {
730
+ // LavaPlayer missing track or lavaPlayer is falsy
731
+ if (currentTrack) {
732
+ if (queueTracks.length > 0) {
733
+ tracks.push(...queueTracks);
734
+ await player.queue.clear();
735
+ await player.queue.add(tracks);
736
+ }
737
+ await node.trackEnd(player, currentTrack, {
738
+ reason: Enums_1.TrackEndReasonTypes.Finished,
739
+ type: "TrackEndEvent",
740
+ });
741
+ }
742
+ else {
743
+ // No current track, check previous queue for last track
744
+ const previousQueue = await player.queue.getPrevious();
745
+ const lastTrack = previousQueue?.at(-1);
746
+ if (lastTrack) {
747
+ if (queueTracks.length === 0) {
748
+ // If no tracks in queue, end last track
749
+ await node.trackEnd(player, lastTrack, {
750
+ reason: Enums_1.TrackEndReasonTypes.Finished,
751
+ type: "TrackEndEvent",
752
+ });
753
+ }
754
+ else {
755
+ // If there are queued tracks, add them
756
+ tracks.push(...queueTracks);
757
+ if (tracks.length > 0) {
758
+ await player.queue.clear();
759
+ await player.queue.add(tracks);
760
+ }
761
+ }
762
+ }
763
+ else {
764
+ if (queueTracks.length > 0) {
765
+ tracks.push(...queueTracks);
766
+ if (tracks.length > 0) {
767
+ await player.queue.clear();
768
+ await player.queue.add(tracks);
769
+ }
770
+ await node.trackEnd(player, lastTrack, {
771
+ reason: Enums_1.TrackEndReasonTypes.Finished,
772
+ type: "TrackEndEvent",
773
+ });
774
+ }
775
+ }
776
+ }
777
+ }
778
+ if (state.queue.previous.length > 0) {
779
+ await player.queue.addPrevious(state.queue.previous);
780
+ }
781
+ else {
782
+ await player.queue.clearPrevious();
783
+ }
784
+ if (state.paused) {
785
+ await player.pause(true);
786
+ }
787
+ else {
788
+ player.paused = false;
789
+ }
790
+ if (state.trackRepeat)
791
+ player.setTrackRepeat(true);
792
+ if (state.queueRepeat)
793
+ player.setQueueRepeat(true);
794
+ if (state.dynamicRepeat) {
795
+ player.setDynamicRepeat(state.dynamicRepeat, state.dynamicLoopInterval._idleTimeout);
796
+ }
797
+ if (state.data) {
798
+ for (const [name, value] of Object.entries(state.data)) {
799
+ player.set(name, value);
800
+ }
801
+ }
802
+ const filterActions = {
803
+ bassboost: () => player.filters.bassBoost(state.filters.bassBoostlevel),
804
+ distort: (enabled) => player.filters.distort(enabled),
805
+ setDistortion: () => player.filters.setDistortion(state.filters.distortion),
806
+ eightD: (enabled) => player.filters.eightD(enabled),
807
+ setKaraoke: () => player.filters.setKaraoke(state.filters.karaoke),
808
+ nightcore: (enabled) => player.filters.nightcore(enabled),
809
+ slowmo: (enabled) => player.filters.slowmo(enabled),
810
+ soft: (enabled) => player.filters.soft(enabled),
811
+ trebleBass: (enabled) => player.filters.trebleBass(enabled),
812
+ setTimescale: () => player.filters.setTimescale(state.filters.timescale),
813
+ tv: (enabled) => player.filters.tv(enabled),
814
+ vibrato: () => player.filters.setVibrato(state.filters.vibrato),
815
+ vaporwave: (enabled) => player.filters.vaporwave(enabled),
816
+ pop: (enabled) => player.filters.pop(enabled),
817
+ party: (enabled) => player.filters.party(enabled),
818
+ earrape: (enabled) => player.filters.earrape(enabled),
819
+ electronic: (enabled) => player.filters.electronic(enabled),
820
+ radio: (enabled) => player.filters.radio(enabled),
821
+ setRotation: () => player.filters.setRotation(state.filters.rotation),
822
+ tremolo: (enabled) => player.filters.tremolo(enabled),
823
+ china: (enabled) => player.filters.china(enabled),
824
+ chipmunk: (enabled) => player.filters.chipmunk(enabled),
825
+ darthvader: (enabled) => player.filters.darthvader(enabled),
826
+ daycore: (enabled) => player.filters.daycore(enabled),
827
+ doubletime: (enabled) => player.filters.doubletime(enabled),
828
+ demon: (enabled) => player.filters.demon(enabled),
829
+ };
830
+ // Iterate through filterStatus and apply the enabled filters
831
+ for (const [filter, isEnabled] of Object.entries(state.filters.filterStatus)) {
832
+ if (isEnabled && filterActions[filter]) {
833
+ filterActions[filter](true);
834
+ }
835
+ }
836
+ // After processing, delete the Redis key
837
+ await this.redis.del(key);
838
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Deleted player state from Redis: ${key}`);
839
+ this.emit(Enums_1.ManagerEventTypes.PlayerRestored, player, node);
840
+ await this.sleep(1000);
841
+ }
464
842
  }
465
- }
466
- const filterActions = {
467
- bassboost: () => player.filters.bassBoost(state.filters.bassBoostlevel),
468
- distort: (enabled) => player.filters.distort(enabled),
469
- setDistortion: () => player.filters.setDistortion(state.filters.distortion),
470
- eightD: (enabled) => player.filters.eightD(enabled),
471
- setKaraoke: () => player.filters.setKaraoke(state.filters.karaoke),
472
- nightcore: (enabled) => player.filters.nightcore(enabled),
473
- slowmo: (enabled) => player.filters.slowmo(enabled),
474
- soft: (enabled) => player.filters.soft(enabled),
475
- trebleBass: (enabled) => player.filters.trebleBass(enabled),
476
- setTimescale: () => player.filters.setTimescale(state.filters.timescale),
477
- tv: (enabled) => player.filters.tv(enabled),
478
- vibrato: () => player.filters.setVibrato(state.filters.vibrato),
479
- vaporwave: (enabled) => player.filters.vaporwave(enabled),
480
- pop: (enabled) => player.filters.pop(enabled),
481
- party: (enabled) => player.filters.party(enabled),
482
- earrape: (enabled) => player.filters.earrape(enabled),
483
- electronic: (enabled) => player.filters.electronic(enabled),
484
- radio: (enabled) => player.filters.radio(enabled),
485
- setRotation: () => player.filters.setRotation(state.filters.rotation),
486
- tremolo: (enabled) => player.filters.tremolo(enabled),
487
- china: (enabled) => player.filters.china(enabled),
488
- chipmunk: (enabled) => player.filters.chipmunk(enabled),
489
- darthvader: (enabled) => player.filters.darthvader(enabled),
490
- daycore: (enabled) => player.filters.daycore(enabled),
491
- doubletime: (enabled) => player.filters.doubletime(enabled),
492
- demon: (enabled) => player.filters.demon(enabled),
493
- };
494
- // Iterate through filterStatus and apply the enabled filters
495
- for (const [filter, isEnabled] of Object.entries(state.filters.filterStatus)) {
496
- if (isEnabled && filterActions[filter]) {
497
- filterActions[filter](true);
843
+ catch (error) {
844
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error processing Redis key ${key}: ${error}`);
845
+ continue;
498
846
  }
499
847
  }
500
848
  }
501
- }
502
- catch (error) {
503
- this.emit(ManagerEventTypes.Debug, `[MANAGER] Error processing file ${filePath}: ${error}`);
504
- continue; // Skip to the next file if there's an error
505
- }
506
- }
507
- // Delete all files inside playerStatesDir where nodeId matches
508
- for (const file of playerFiles) {
509
- const filePath = path_1.default.join(playerStatesDir, file);
510
- try {
511
- await promises_1.default.access(filePath); // Check if the file exists
512
- const data = await promises_1.default.readFile(filePath, "utf-8");
513
- const state = JSON.parse(data);
514
- if (state && typeof state === "object" && state.node.options.identifier === nodeId) {
515
- await promises_1.default.unlink(filePath); // Delete the file asynchronously
516
- this.emit(ManagerEventTypes.Debug, `[MANAGER] Deleted player state file: ${filePath}`);
849
+ catch (error) {
850
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error loading player states from Redis: ${error}`);
517
851
  }
518
852
  }
519
- catch (error) {
520
- this.emit(ManagerEventTypes.Debug, `[MANAGER] Error deleting file ${filePath}: ${error}`);
521
- continue; // Skip to the next file if there's an error
522
- }
523
- }
524
- }
525
- catch (error) {
526
- this.emit(ManagerEventTypes.Debug, `[MANAGER] Error loading player states: ${error}`);
853
+ break;
854
+ default:
855
+ break;
527
856
  }
528
- this.emit(ManagerEventTypes.Debug, "[MANAGER] Finished loading saved players.");
857
+ this.emit(Enums_1.ManagerEventTypes.Debug, "[MANAGER] Finished loading saved players.");
858
+ this.emit(Enums_1.ManagerEventTypes.RestoreComplete, node);
529
859
  }
530
860
  /**
531
861
  * Returns the node to use based on the configured `useNode` and `enablePriorityMode` options.
@@ -537,7 +867,7 @@ class Manager extends events_1.EventEmitter {
537
867
  get useableNode() {
538
868
  return this.options.enablePriorityMode
539
869
  ? this.priorityNode
540
- : this.options.useNode === UseNodeOptions.LeastLoad
870
+ : this.options.useNode === Enums_1.UseNodeOptions.LeastLoad
541
871
  ? this.leastLoadNode.first()
542
872
  : this.leastPlayersNode.first();
543
873
  }
@@ -549,25 +879,28 @@ class Manager extends events_1.EventEmitter {
549
879
  * After saving and cleaning up, it exits the process.
550
880
  */
551
881
  async handleShutdown() {
882
+ this.unloadPlugins();
552
883
  console.warn("\x1b[31m%s\x1b[0m", "MAGMASTREAM WARNING: Shutting down! Please wait, saving active players...");
553
884
  try {
885
+ await this.clearAllStoredPlayers();
554
886
  const savePromises = Array.from(this.players.keys()).map(async (guildId) => {
555
887
  try {
556
888
  await this.savePlayerState(guildId);
557
889
  }
558
890
  catch (error) {
559
- console.error(`Error saving player state for guild ${guildId}:`, error);
891
+ console.error(`[MANAGER] Error saving player state for guild ${guildId}:`, error);
560
892
  }
561
893
  });
894
+ if (this.options.stateStorage.deleteInactivePlayers)
895
+ await this.cleanupInactivePlayers();
562
896
  await Promise.allSettled(savePromises);
563
- await this.cleanupInactivePlayers();
564
897
  setTimeout(() => {
565
898
  console.warn("\x1b[32m%s\x1b[0m", "MAGMASTREAM INFO: Shutting down complete, exiting...");
566
899
  process.exit(0);
567
900
  }, 500);
568
901
  }
569
902
  catch (error) {
570
- console.error("Unexpected error during shutdown:", error);
903
+ console.error(`[MANAGER] Unexpected error during shutdown:`, error);
571
904
  process.exit(1);
572
905
  }
573
906
  }
@@ -685,6 +1018,7 @@ class Manager extends events_1.EventEmitter {
685
1018
  guildId: player.guildId,
686
1019
  data: { voice: { token, endpoint, sessionId } },
687
1020
  });
1021
+ this.emit(Enums_1.ManagerEventTypes.Debug, `Updated voice server for player ${player.guildId} with token ${token} and endpoint ${endpoint} and sessionId ${sessionId}`);
688
1022
  return;
689
1023
  }
690
1024
  /**
@@ -695,18 +1029,19 @@ class Manager extends events_1.EventEmitter {
695
1029
  * @emits {playerDisconnect} - Emits a player disconnect event if the channel ID is null.
696
1030
  */
697
1031
  async handleVoiceStateUpdate(player, update) {
1032
+ this.emit(Enums_1.ManagerEventTypes.Debug, `Updated voice state for player ${player.guildId} with channel id ${update.channel_id} and session id ${update.session_id}`);
698
1033
  if (update.channel_id) {
699
1034
  if (player.voiceChannelId !== update.channel_id) {
700
- this.emit(ManagerEventTypes.PlayerMove, player, player.voiceChannelId, update.channel_id);
1035
+ this.emit(Enums_1.ManagerEventTypes.PlayerMove, player, player.voiceChannelId, update.channel_id);
701
1036
  }
702
1037
  player.voiceState.sessionId = update.session_id;
703
1038
  player.voiceChannelId = update.channel_id;
704
1039
  return;
705
1040
  }
706
- this.emit(ManagerEventTypes.PlayerDisconnect, player, player.voiceChannelId);
1041
+ this.emit(Enums_1.ManagerEventTypes.PlayerDisconnect, player, player.voiceChannelId);
707
1042
  player.voiceChannelId = null;
708
1043
  player.voiceState = Object.assign({});
709
- await player.destroy();
1044
+ await player.pause(true);
710
1045
  return;
711
1046
  }
712
1047
  /**
@@ -715,13 +1050,13 @@ class Manager extends events_1.EventEmitter {
715
1050
  * @returns {string} The path to the player's JSON file
716
1051
  */
717
1052
  async getPlayerFilePath(guildId) {
718
- const configDir = path_1.default.join(process.cwd(), "magmastream", "dist", "sessionData", "players");
1053
+ const configDir = path_1.default.join(process.cwd(), "magmastream", "dist", "sessionData", "playerStore");
719
1054
  try {
720
1055
  await promises_1.default.mkdir(configDir, { recursive: true });
721
1056
  return path_1.default.join(configDir, `${guildId}.json`);
722
1057
  }
723
1058
  catch (err) {
724
- console.error("Error ensuring player data directory exists:", err);
1059
+ console.error(`[MANAGER] Error ensuring player data directory exists: ${err}`);
725
1060
  throw new Error(`Failed to resolve player file path for guild ${guildId}`);
726
1061
  }
727
1062
  }
@@ -730,8 +1065,12 @@ class Manager extends events_1.EventEmitter {
730
1065
  * @param player The Player instance to serialize
731
1066
  * @returns The serialized Player instance
732
1067
  */
733
- serializePlayer(player) {
1068
+ async serializePlayer(player) {
734
1069
  const seen = new WeakSet();
1070
+ // Fetch async queue data once before serializing
1071
+ const current = await player.queue.getCurrent();
1072
+ const tracks = Array.isArray(await player.queue.getTracks()) ? await player.queue.getTracks() : [];
1073
+ const previous = Array.isArray(await player.queue.getPrevious()) ? await player.queue.getPrevious() : [];
735
1074
  /**
736
1075
  * Recursively serializes an object, avoiding circular references.
737
1076
  * @param obj The object to serialize
@@ -750,6 +1089,8 @@ class Manager extends events_1.EventEmitter {
750
1089
  return null;
751
1090
  }
752
1091
  if (key === "filters") {
1092
+ if (!value || typeof value !== "object")
1093
+ return null;
753
1094
  return {
754
1095
  distortion: value.distortion ?? null,
755
1096
  equalizer: value.equalizer ?? [],
@@ -760,54 +1101,239 @@ class Manager extends events_1.EventEmitter {
760
1101
  reverb: value.reverb ?? null,
761
1102
  volume: value.volume ?? 1.0,
762
1103
  bassBoostlevel: value.bassBoostlevel ?? null,
763
- filterStatus: { ...value.filtersStatus },
1104
+ filterStatus: value.filtersStatus ? { ...value.filtersStatus } : {},
764
1105
  };
765
1106
  }
766
1107
  if (key === "queue") {
767
1108
  return {
768
- current: value.current || null,
769
- tracks: [...value],
770
- previous: [...value.previous],
1109
+ current,
1110
+ tracks,
1111
+ previous,
771
1112
  };
772
1113
  }
773
1114
  if (key === "data") {
774
1115
  return {
775
- clientUser: value.Internal_BotUser ?? null,
1116
+ clientUser: value?.Internal_BotUser ?? null,
776
1117
  };
777
1118
  }
778
1119
  return serialize(value);
779
1120
  }));
780
1121
  }
781
1122
  /**
782
- * Checks for players that are no longer active and deletes their saved state files.
1123
+ * Cleans up inactive players by removing their state files from the file system.
783
1124
  * This is done to prevent stale state files from accumulating on the file system.
784
1125
  */
785
1126
  async cleanupInactivePlayers() {
786
- const playerStatesDir = path_1.default.join(process.cwd(), "magmastream", "dist", "sessionData", "players");
787
- try {
788
- // Check if the directory exists, and create it if it doesn't
789
- await promises_1.default.access(playerStatesDir).catch(async () => {
790
- await promises_1.default.mkdir(playerStatesDir, { recursive: true });
791
- this.emit(ManagerEventTypes.Debug, `[MANAGER] Created directory: ${playerStatesDir}`);
792
- });
793
- // Get the list of player state files
794
- const playerFiles = await promises_1.default.readdir(playerStatesDir);
795
- // Get the set of active guild IDs from the manager's player collection
796
- const activeGuildIds = new Set(this.players.keys());
797
- // Iterate over the player state files
798
- for (const file of playerFiles) {
799
- // Get the guild ID from the file name
800
- const guildId = path_1.default.basename(file, ".json");
801
- // If the guild ID is not in the set of active guild IDs, delete the file
802
- if (!activeGuildIds.has(guildId)) {
803
- const filePath = path_1.default.join(playerStatesDir, file);
804
- await promises_1.default.unlink(filePath); // Delete the file asynchronously
805
- this.emit(ManagerEventTypes.Debug, `[MANAGER] Deleting inactive player: ${guildId}`);
1127
+ switch (this.options.stateStorage.type) {
1128
+ case Enums_1.StateStorageType.JSON:
1129
+ {
1130
+ const playerStoreDir = path_1.default.join(process.cwd(), "magmastream", "dist", "sessionData", "playerStore");
1131
+ const playerDataDir = this.options.stateStorage?.jsonConfig?.path ?? path_1.default.join(process.cwd(), "magmastream", "dist", "sessionData", "players");
1132
+ try {
1133
+ await promises_1.default.mkdir(playerStoreDir, { recursive: true });
1134
+ await promises_1.default.mkdir(playerDataDir, { recursive: true });
1135
+ const activeGuildIds = new Set(this.players.keys());
1136
+ // Clean up playerStore/*.json
1137
+ const playerStateFiles = await promises_1.default.readdir(playerStoreDir);
1138
+ for (const file of playerStateFiles) {
1139
+ const guildId = path_1.default.basename(file, ".json");
1140
+ if (!activeGuildIds.has(guildId)) {
1141
+ const filePath = path_1.default.join(playerStoreDir, file);
1142
+ await promises_1.default.unlink(filePath);
1143
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Deleted inactive player state: ${guildId}`);
1144
+ }
1145
+ }
1146
+ // Clean up players/<guildId>/ folders
1147
+ const guildDirs = await promises_1.default.readdir(playerDataDir, { withFileTypes: true });
1148
+ for (const dirent of guildDirs) {
1149
+ if (!dirent.isDirectory())
1150
+ continue;
1151
+ const guildId = dirent.name;
1152
+ if (!activeGuildIds.has(guildId)) {
1153
+ const guildPath = path_1.default.join(playerDataDir, guildId);
1154
+ await promises_1.default.rm(guildPath, { recursive: true, force: true });
1155
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Deleted inactive player data folder: ${guildId}`);
1156
+ }
1157
+ }
1158
+ }
1159
+ catch (error) {
1160
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error cleaning up inactive JSON players: ${error}`);
1161
+ }
1162
+ return;
1163
+ }
1164
+ break;
1165
+ case Enums_1.StateStorageType.Redis:
1166
+ {
1167
+ const prefix = this.options.stateStorage.redisConfig.prefix?.endsWith(":")
1168
+ ? this.options.stateStorage.redisConfig.prefix
1169
+ : this.options.stateStorage.redisConfig.prefix ?? "magmastream:";
1170
+ const pattern = `${prefix}queue:*:current`;
1171
+ const stream = this.redis.scanStream({
1172
+ match: pattern,
1173
+ count: 100,
1174
+ });
1175
+ for await (const keys of stream) {
1176
+ for (const key of keys) {
1177
+ // Extract guildId from queue key
1178
+ const match = key.match(new RegExp(`^${prefix}queue:(.+):current$`));
1179
+ if (!match)
1180
+ continue;
1181
+ const guildId = match[1];
1182
+ // If player is not active in memory, clean up all keys
1183
+ if (!this.players.has(guildId)) {
1184
+ await this.redis.del(`${prefix}playerstore:${guildId}`, `${prefix}queue:${guildId}:current`, `${prefix}queue:${guildId}:tracks`, `${prefix}queue:${guildId}:previous`);
1185
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Cleaned inactive Redis player data: ${guildId}`);
1186
+ }
1187
+ }
1188
+ }
1189
+ return;
1190
+ }
1191
+ break;
1192
+ default:
1193
+ break;
1194
+ }
1195
+ }
1196
+ /**
1197
+ * Cleans up an inactive player by removing its state data.
1198
+ * This is done to prevent stale state data from accumulating.
1199
+ * @param guildId The guild ID of the player to clean up.
1200
+ */
1201
+ async cleanupInactivePlayer(guildId) {
1202
+ switch (this.options.stateStorage.type) {
1203
+ case Enums_1.StateStorageType.JSON:
1204
+ {
1205
+ const playersStoreDir = path_1.default.join(process.cwd(), "magmastream", "dist", "sessionData", "playerStore");
1206
+ const playersDataDir = this.options.stateStorage?.jsonConfig?.path ?? path_1.default.join(process.cwd(), "magmastream", "dist", "sessionData", "players");
1207
+ try {
1208
+ if (!this.players.has(guildId)) {
1209
+ await promises_1.default.unlink(path_1.default.join(playersStoreDir, `${guildId}.json`));
1210
+ await promises_1.default.rm(path_1.default.join(playersDataDir, guildId), { recursive: true, force: true });
1211
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Deleting inactive player files: ${guildId}`);
1212
+ }
1213
+ }
1214
+ catch (error) {
1215
+ if (error.code !== "ENOENT") {
1216
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error deleting player files for ${guildId}: ${error}`);
1217
+ }
1218
+ }
1219
+ }
1220
+ break;
1221
+ case Enums_1.StateStorageType.Redis:
1222
+ {
1223
+ const player = this.getPlayer(guildId);
1224
+ if (!player) {
1225
+ const prefix = this.options.stateStorage.redisConfig.prefix?.endsWith(":")
1226
+ ? this.options.stateStorage.redisConfig.prefix
1227
+ : `${this.options.stateStorage.redisConfig.prefix ?? "magmastream"}:`;
1228
+ const keysToDelete = [
1229
+ `${prefix}playerstore:${guildId}`,
1230
+ `${prefix}queue:${guildId}:tracks`,
1231
+ `${prefix}queue:${guildId}:current`,
1232
+ `${prefix}queue:${guildId}:previous`,
1233
+ ];
1234
+ await this.redis.del(...keysToDelete);
1235
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Deleted Redis player and queue data for: ${guildId}`);
1236
+ }
806
1237
  }
1238
+ break;
1239
+ default:
1240
+ break;
1241
+ }
1242
+ }
1243
+ /**
1244
+ * Loads the enabled plugins.
1245
+ */
1246
+ loadPlugins() {
1247
+ if (!Array.isArray(this.options.enabledPlugins))
1248
+ return;
1249
+ for (const [index, plugin] of this.options.enabledPlugins.entries()) {
1250
+ if (!(plugin instanceof __1.Plugin)) {
1251
+ throw new RangeError(`Plugin at index ${index} does not extend Plugin.`);
1252
+ }
1253
+ try {
1254
+ plugin.load(this);
1255
+ this.loadedPlugins.add(plugin);
1256
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[PLUGIN] Loaded plugin: ${plugin.name}`);
1257
+ }
1258
+ catch (err) {
1259
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[PLUGIN] Failed to load plugin "${plugin.name}": ${err}`);
807
1260
  }
808
1261
  }
809
- catch (error) {
810
- this.emit(ManagerEventTypes.Debug, `[MANAGER] Error cleaning up inactive players: ${error}`);
1262
+ }
1263
+ /**
1264
+ * Unloads the enabled plugins.
1265
+ */
1266
+ unloadPlugins() {
1267
+ for (const plugin of this.loadedPlugins) {
1268
+ try {
1269
+ plugin.unload(this);
1270
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[PLUGIN] Unloaded plugin: ${plugin.name}`);
1271
+ }
1272
+ catch (err) {
1273
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[PLUGIN] Failed to unload plugin "${plugin.name}": ${err}`);
1274
+ }
1275
+ }
1276
+ this.loadedPlugins.clear();
1277
+ }
1278
+ /**
1279
+ * Clears all player states from the file system.
1280
+ * This is done to prevent stale state files from accumulating on the file system.
1281
+ */
1282
+ async clearAllStoredPlayers() {
1283
+ switch (this.options.stateStorage.type) {
1284
+ case Enums_1.StateStorageType.Memory:
1285
+ case Enums_1.StateStorageType.JSON: {
1286
+ const configDir = path_1.default.join(process.cwd(), "magmastream", "dist", "sessionData", "playerStore");
1287
+ try {
1288
+ // Check if the directory exists, and create it if it doesn't
1289
+ await promises_1.default.access(configDir).catch(async () => {
1290
+ await promises_1.default.mkdir(configDir, { recursive: true });
1291
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Created directory: ${configDir}`);
1292
+ });
1293
+ const files = await promises_1.default.readdir(configDir);
1294
+ await Promise.all(files.map((file) => promises_1.default.unlink(path_1.default.join(configDir, file)).catch((err) => console.warn(`[MANAGER] Failed to delete file ${file}:`, err))));
1295
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Cleared all player state files in ${configDir}`);
1296
+ }
1297
+ catch (err) {
1298
+ console.error("[MANAGER] Error clearing player state files:", err);
1299
+ }
1300
+ break;
1301
+ }
1302
+ case Enums_1.StateStorageType.Redis: {
1303
+ const prefix = this.options.stateStorage.redisConfig.prefix?.endsWith(":")
1304
+ ? this.options.stateStorage.redisConfig.prefix
1305
+ : this.options.stateStorage.redisConfig.prefix ?? "magmastream:";
1306
+ const patterns = [`${prefix}playerstore:*`, `${prefix}queue:*`];
1307
+ try {
1308
+ for (const pattern of patterns) {
1309
+ const stream = this.redis.scanStream({
1310
+ match: pattern,
1311
+ count: 100,
1312
+ });
1313
+ let totalDeleted = 0;
1314
+ stream.on("data", async (keys) => {
1315
+ if (keys.length) {
1316
+ const pipeline = this.redis.pipeline();
1317
+ keys.forEach((key) => pipeline.unlink(key));
1318
+ await pipeline.exec();
1319
+ totalDeleted += keys.length;
1320
+ }
1321
+ });
1322
+ stream.on("end", () => {
1323
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Cleared ${totalDeleted} Redis keys (pattern: ${pattern})`);
1324
+ });
1325
+ stream.on("error", (err) => {
1326
+ console.error(`[MANAGER] Error during Redis SCAN stream (${pattern}):`, err);
1327
+ });
1328
+ }
1329
+ }
1330
+ catch (err) {
1331
+ console.error("[MANAGER] Failed to clear Redis keys:", err);
1332
+ }
1333
+ break;
1334
+ }
1335
+ default:
1336
+ console.warn("[MANAGER] No valid stateStorage.type set, skipping state clearing.");
811
1337
  }
812
1338
  }
813
1339
  /**
@@ -818,7 +1344,7 @@ class Manager extends events_1.EventEmitter {
818
1344
  */
819
1345
  get leastLoadNode() {
820
1346
  return this.nodes
821
- .filter((node) => node.connected)
1347
+ .filter((node) => node.connected && !node.options.isBackup)
822
1348
  .sort((a, b) => {
823
1349
  const aload = a.stats.cpu ? (a.stats.cpu.lavalinkLoad / a.stats.cpu.cores) * 100 : 0;
824
1350
  const bload = b.stats.cpu ? (b.stats.cpu.lavalinkLoad / b.stats.cpu.cores) * 100 : 0;
@@ -833,9 +1359,7 @@ class Manager extends events_1.EventEmitter {
833
1359
  * @returns {Collection<string, Node>} A collection of nodes sorted by player count.
834
1360
  */
835
1361
  get leastPlayersNode() {
836
- return this.nodes
837
- .filter((node) => node.connected) // Filter out nodes that are not connected
838
- .sort((a, b) => a.stats.players - b.stats.players); // Sort by the number of players
1362
+ return this.nodes.filter((node) => node.connected && !node.options.isBackup).sort((a, b) => a.stats.players - b.stats.players);
839
1363
  }
840
1364
  /**
841
1365
  * Returns a node based on priority.
@@ -868,104 +1392,17 @@ class Manager extends events_1.EventEmitter {
868
1392
  }
869
1393
  }
870
1394
  // If no node has a cumulative weight greater than or equal to the random number, return the node with the lowest load
871
- return this.options.useNode === UseNodeOptions.LeastLoad ? this.leastLoadNode.first() : this.leastPlayersNode.first();
1395
+ return this.options.useNode === Enums_1.UseNodeOptions.LeastLoad ? this.leastLoadNode.first() : this.leastPlayersNode.first();
1396
+ }
1397
+ send(packet) {
1398
+ if (!this._send) {
1399
+ console.warn("[Manager.send] _send is not defined! Packet will not be sent.");
1400
+ return;
1401
+ }
1402
+ return this._send(packet);
1403
+ }
1404
+ sendPacket(packet) {
1405
+ return this.send(packet);
872
1406
  }
873
1407
  }
874
1408
  exports.Manager = Manager;
875
- var TrackPartial;
876
- (function (TrackPartial) {
877
- /** The base64 encoded string of the track */
878
- TrackPartial["Track"] = "track";
879
- /** The title of the track */
880
- TrackPartial["Title"] = "title";
881
- /** The track identifier */
882
- TrackPartial["Identifier"] = "identifier";
883
- /** The author of the track */
884
- TrackPartial["Author"] = "author";
885
- /** The length of the track in milliseconds */
886
- TrackPartial["Duration"] = "duration";
887
- /** The ISRC of the track */
888
- TrackPartial["Isrc"] = "isrc";
889
- /** Whether the track is seekable */
890
- TrackPartial["IsSeekable"] = "isSeekable";
891
- /** Whether the track is a stream */
892
- TrackPartial["IsStream"] = "isStream";
893
- /** The URI of the track */
894
- TrackPartial["Uri"] = "uri";
895
- /** The artwork URL of the track */
896
- TrackPartial["ArtworkUrl"] = "artworkUrl";
897
- /** The source name of the track */
898
- TrackPartial["SourceName"] = "sourceName";
899
- /** The thumbnail of the track */
900
- TrackPartial["ThumbNail"] = "thumbnail";
901
- /** The requester of the track */
902
- TrackPartial["Requester"] = "requester";
903
- /** The plugin info of the track */
904
- TrackPartial["PluginInfo"] = "pluginInfo";
905
- /** The custom data of the track */
906
- TrackPartial["CustomData"] = "customData";
907
- })(TrackPartial || (exports.TrackPartial = TrackPartial = {}));
908
- var UseNodeOptions;
909
- (function (UseNodeOptions) {
910
- UseNodeOptions["LeastLoad"] = "leastLoad";
911
- UseNodeOptions["LeastPlayers"] = "leastPlayers";
912
- })(UseNodeOptions || (exports.UseNodeOptions = UseNodeOptions = {}));
913
- var SearchPlatform;
914
- (function (SearchPlatform) {
915
- SearchPlatform["AppleMusic"] = "amsearch";
916
- SearchPlatform["Bandcamp"] = "bcsearch";
917
- SearchPlatform["Deezer"] = "dzsearch";
918
- SearchPlatform["Jiosaavn"] = "jssearch";
919
- SearchPlatform["SoundCloud"] = "scsearch";
920
- SearchPlatform["Spotify"] = "spsearch";
921
- SearchPlatform["Tidal"] = "tdsearch";
922
- SearchPlatform["VKMusic"] = "vksearch";
923
- SearchPlatform["YouTube"] = "ytsearch";
924
- SearchPlatform["YouTubeMusic"] = "ytmsearch";
925
- })(SearchPlatform || (exports.SearchPlatform = SearchPlatform = {}));
926
- var AutoPlayPlatform;
927
- (function (AutoPlayPlatform) {
928
- AutoPlayPlatform["Spotify"] = "spotify";
929
- AutoPlayPlatform["Deezer"] = "deezer";
930
- AutoPlayPlatform["SoundCloud"] = "soundcloud";
931
- AutoPlayPlatform["YouTube"] = "youtube";
932
- })(AutoPlayPlatform || (exports.AutoPlayPlatform = AutoPlayPlatform = {}));
933
- var PlayerStateEventTypes;
934
- (function (PlayerStateEventTypes) {
935
- PlayerStateEventTypes["AutoPlayChange"] = "playerAutoplay";
936
- PlayerStateEventTypes["ConnectionChange"] = "playerConnection";
937
- PlayerStateEventTypes["RepeatChange"] = "playerRepeat";
938
- PlayerStateEventTypes["PauseChange"] = "playerPause";
939
- PlayerStateEventTypes["QueueChange"] = "queueChange";
940
- PlayerStateEventTypes["TrackChange"] = "trackChange";
941
- PlayerStateEventTypes["VolumeChange"] = "volumeChange";
942
- PlayerStateEventTypes["ChannelChange"] = "channelChange";
943
- PlayerStateEventTypes["PlayerCreate"] = "playerCreate";
944
- PlayerStateEventTypes["PlayerDestroy"] = "playerDestroy";
945
- })(PlayerStateEventTypes || (exports.PlayerStateEventTypes = PlayerStateEventTypes = {}));
946
- var ManagerEventTypes;
947
- (function (ManagerEventTypes) {
948
- ManagerEventTypes["Debug"] = "debug";
949
- ManagerEventTypes["NodeCreate"] = "nodeCreate";
950
- ManagerEventTypes["NodeDestroy"] = "nodeDestroy";
951
- ManagerEventTypes["NodeConnect"] = "nodeConnect";
952
- ManagerEventTypes["NodeReconnect"] = "nodeReconnect";
953
- ManagerEventTypes["NodeDisconnect"] = "nodeDisconnect";
954
- ManagerEventTypes["NodeError"] = "nodeError";
955
- ManagerEventTypes["NodeRaw"] = "nodeRaw";
956
- ManagerEventTypes["PlayerCreate"] = "playerCreate";
957
- ManagerEventTypes["PlayerDestroy"] = "playerDestroy";
958
- ManagerEventTypes["PlayerStateUpdate"] = "playerStateUpdate";
959
- ManagerEventTypes["PlayerMove"] = "playerMove";
960
- ManagerEventTypes["PlayerDisconnect"] = "playerDisconnect";
961
- ManagerEventTypes["QueueEnd"] = "queueEnd";
962
- ManagerEventTypes["SocketClosed"] = "socketClosed";
963
- ManagerEventTypes["TrackStart"] = "trackStart";
964
- ManagerEventTypes["TrackEnd"] = "trackEnd";
965
- ManagerEventTypes["TrackStuck"] = "trackStuck";
966
- ManagerEventTypes["TrackError"] = "trackError";
967
- ManagerEventTypes["SegmentsLoaded"] = "segmentsLoaded";
968
- ManagerEventTypes["SegmentSkipped"] = "segmentSkipped";
969
- ManagerEventTypes["ChapterStarted"] = "chapterStarted";
970
- ManagerEventTypes["ChaptersLoaded"] = "chaptersLoaded";
971
- })(ManagerEventTypes || (exports.ManagerEventTypes = ManagerEventTypes = {}));