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