magmastream 2.10.5 → 2.10.6-alpha.2
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.
|
@@ -117,12 +117,6 @@ export declare class Manager extends EventEmitter {
|
|
|
117
117
|
* @param {string} guildId - The guild ID of the player to save
|
|
118
118
|
*/
|
|
119
119
|
savePlayerState(guildId: string): Promise<void>;
|
|
120
|
-
/**
|
|
121
|
-
* Sleeps for a specified amount of time.
|
|
122
|
-
* @param ms The amount of time to sleep in milliseconds.
|
|
123
|
-
* @returns A promise that resolves after the specified amount of time.
|
|
124
|
-
*/
|
|
125
|
-
private sleep;
|
|
126
120
|
private restorePlayerFromState;
|
|
127
121
|
private restoreQueue;
|
|
128
122
|
private restorePreviousQueue;
|
|
@@ -529,19 +529,10 @@ class Manager extends events_1.EventEmitter {
|
|
|
529
529
|
await player.queue.clearPrevious();
|
|
530
530
|
await player.queue.setCurrent(null);
|
|
531
531
|
}
|
|
532
|
-
/**
|
|
533
|
-
* Sleeps for a specified amount of time.
|
|
534
|
-
* @param ms The amount of time to sleep in milliseconds.
|
|
535
|
-
* @returns A promise that resolves after the specified amount of time.
|
|
536
|
-
*/
|
|
537
|
-
async sleep(ms) {
|
|
538
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
539
|
-
}
|
|
540
532
|
async restorePlayerFromState(node, nodeId, guildId, state, cleanup) {
|
|
541
533
|
if (!state.guildId || state.node?.options?.identifier !== nodeId)
|
|
542
534
|
return;
|
|
543
|
-
|
|
544
|
-
if (!hasGuild)
|
|
535
|
+
if (state.guildId !== guildId)
|
|
545
536
|
return;
|
|
546
537
|
const lavaPlayer = (await node.rest.get(`/v4/sessions/${state.node.sessionId}/players/${state.guildId}`));
|
|
547
538
|
if (!lavaPlayer)
|
|
@@ -584,7 +575,6 @@ class Manager extends events_1.EventEmitter {
|
|
|
584
575
|
await player.pause(true);
|
|
585
576
|
await cleanup();
|
|
586
577
|
this.emit(Enums_1.ManagerEventTypes.PlayerRestored, player, node);
|
|
587
|
-
await this.sleep(1000);
|
|
588
578
|
}
|
|
589
579
|
async restoreQueue(node, player, state, lavaPlayer) {
|
|
590
580
|
const currentTrack = state.queue.current;
|
|
@@ -724,12 +714,14 @@ class Manager extends events_1.EventEmitter {
|
|
|
724
714
|
await promises_1.default.mkdir(playersBaseDir, { recursive: true });
|
|
725
715
|
});
|
|
726
716
|
const guildDirs = await promises_1.default.readdir(playersBaseDir, { withFileTypes: true });
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
const guildId = file.name;
|
|
717
|
+
await Promise.all(guildDirs
|
|
718
|
+
.filter((file) => file.isDirectory())
|
|
719
|
+
.map(async ({ name: guildId }) => {
|
|
731
720
|
const stateFilePath = Utils_1.PlayerUtils.getPlayerStatePath(guildId);
|
|
732
721
|
try {
|
|
722
|
+
// Guilds outside this client's cache belong to another cluster; skip them before reading any file.
|
|
723
|
+
if (!this.resolveGuild(guildId))
|
|
724
|
+
return;
|
|
733
725
|
await promises_1.default.access(stateFilePath);
|
|
734
726
|
const state = JSON.parse(await promises_1.default.readFile(stateFilePath, "utf-8"));
|
|
735
727
|
await this.restorePlayerFromState(node, nodeId, guildId, state, async () => {
|
|
@@ -740,7 +732,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
740
732
|
catch (error) {
|
|
741
733
|
this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error processing guild ${guildId}: ${error}`);
|
|
742
734
|
}
|
|
743
|
-
}
|
|
735
|
+
}));
|
|
744
736
|
}
|
|
745
737
|
catch (error) {
|
|
746
738
|
this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error loading player states: ${error}`);
|
|
@@ -750,17 +742,17 @@ class Manager extends events_1.EventEmitter {
|
|
|
750
742
|
case Enums_1.StateStorageType.Redis: {
|
|
751
743
|
try {
|
|
752
744
|
const keys = await this.redis.keys(`${Utils_1.PlayerUtils.getRedisKey()}playerstore:*`);
|
|
753
|
-
|
|
745
|
+
await Promise.all(keys.map(async (key) => {
|
|
754
746
|
try {
|
|
747
|
+
const guildId = key.split(":").pop();
|
|
748
|
+
if (!guildId || !this.resolveGuild(guildId))
|
|
749
|
+
return;
|
|
755
750
|
const data = await this.redis.get(key);
|
|
756
751
|
if (!data)
|
|
757
|
-
|
|
752
|
+
return;
|
|
758
753
|
const state = JSON.parse(data);
|
|
759
754
|
if (!state || typeof state !== "object")
|
|
760
|
-
|
|
761
|
-
const guildId = key.split(":").pop();
|
|
762
|
-
if (!guildId)
|
|
763
|
-
continue;
|
|
755
|
+
return;
|
|
764
756
|
await this.restorePlayerFromState(node, nodeId, guildId, state, async () => {
|
|
765
757
|
await this.redis.del(key);
|
|
766
758
|
this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Deleted Redis state: ${key}`);
|
|
@@ -769,7 +761,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
769
761
|
catch (error) {
|
|
770
762
|
this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error processing Redis key ${key}: ${error}`);
|
|
771
763
|
}
|
|
772
|
-
}
|
|
764
|
+
}));
|
|
773
765
|
}
|
|
774
766
|
catch (error) {
|
|
775
767
|
this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error loading player states from Redis: ${error}`);
|
package/dist/structures/Node.js
CHANGED
|
@@ -11,6 +11,7 @@ const path_1 = tslib_1.__importDefault(require("path"));
|
|
|
11
11
|
const Enums_1 = require("./Enums");
|
|
12
12
|
const MagmastreamError_1 = require("./MagmastreamError");
|
|
13
13
|
const validSponsorBlocks = Object.values(Enums_1.SponsorBlockSegment).map((segment) => segment.toLowerCase());
|
|
14
|
+
const TRACK_REQUIRED_EVENTS = new Set(["TrackStartEvent", "TrackEndEvent", "TrackStuckEvent", "TrackExceptionEvent"]);
|
|
14
15
|
class Node {
|
|
15
16
|
manager;
|
|
16
17
|
options;
|
|
@@ -484,7 +485,9 @@ class Node {
|
|
|
484
485
|
this.handlePlayerUpdate(payload);
|
|
485
486
|
break;
|
|
486
487
|
case "event":
|
|
487
|
-
this.manager.
|
|
488
|
+
if (this.manager.listenerCount(Enums_1.ManagerEventTypes.Debug)) {
|
|
489
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Node message: ${Utils_1.JSONUtils.safe(payload, 2)}`);
|
|
490
|
+
}
|
|
488
491
|
await this.handleEvent(payload);
|
|
489
492
|
break;
|
|
490
493
|
case "ready":
|
|
@@ -531,6 +534,7 @@ class Node {
|
|
|
531
534
|
if (!player || player.node.options.identifier !== this.options.identifier)
|
|
532
535
|
return;
|
|
533
536
|
player.position = payload.state.position ?? 0;
|
|
537
|
+
player.ping = payload.state.ping ?? -1;
|
|
534
538
|
this.manager.emit(Enums_1.ManagerEventTypes.PlayerUpdate, player, payload);
|
|
535
539
|
}
|
|
536
540
|
/**
|
|
@@ -567,8 +571,7 @@ class Node {
|
|
|
567
571
|
return;
|
|
568
572
|
const track = await this.resolveEventTrack(player, payload);
|
|
569
573
|
const type = payload.type;
|
|
570
|
-
|
|
571
|
-
if (!track && trackRequiredEvents.includes(type)) {
|
|
574
|
+
if (!track && TRACK_REQUIRED_EVENTS.has(type)) {
|
|
572
575
|
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Received ${type} for guild ${payload.guildId}, but neither the payload nor local queue contains a usable track.`);
|
|
573
576
|
return;
|
|
574
577
|
}
|
|
@@ -1227,7 +1230,9 @@ class Node {
|
|
|
1227
1230
|
socketClosed(player, payload) {
|
|
1228
1231
|
// A move can close the old socket after the new connection is ready.
|
|
1229
1232
|
this.manager.emit(Enums_1.ManagerEventTypes.SocketClosed, player, payload);
|
|
1230
|
-
this.manager.
|
|
1233
|
+
if (this.manager.listenerCount(Enums_1.ManagerEventTypes.Debug)) {
|
|
1234
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Websocket closed for player: ${player.guildId} with payload: ${Utils_1.JSONUtils.safe(payload, 2)}`);
|
|
1235
|
+
}
|
|
1231
1236
|
}
|
|
1232
1237
|
/**
|
|
1233
1238
|
* Emitted when the segments for a track are loaded.
|
|
@@ -18,6 +18,8 @@ export declare class Player {
|
|
|
18
18
|
dynamicRepeat: boolean;
|
|
19
19
|
/** The time the player is in the track. */
|
|
20
20
|
position: number;
|
|
21
|
+
/** Last node-reported Discord voice ping in milliseconds; -1 when unavailable. */
|
|
22
|
+
ping: number;
|
|
21
23
|
/** Whether the player is playing. */
|
|
22
24
|
playing: boolean;
|
|
23
25
|
/** Whether the player is paused. */
|
|
@@ -84,6 +86,8 @@ export declare class Player {
|
|
|
84
86
|
* @returns {T} - The data associated with the key, cast to the specified type.
|
|
85
87
|
*/
|
|
86
88
|
get<T>(key: string): T;
|
|
89
|
+
/** Returns the last node-reported Discord voice ping in milliseconds, or -1 when unavailable. */
|
|
90
|
+
getPing(): number;
|
|
87
91
|
/**
|
|
88
92
|
* Same as Manager#search() but a shortcut on the player itself.
|
|
89
93
|
* @param query
|
|
@@ -101,6 +105,7 @@ export declare class Player {
|
|
|
101
105
|
* @returns {this} The player instance.
|
|
102
106
|
*/
|
|
103
107
|
disconnect(): Promise<this>;
|
|
108
|
+
private emitConnectionChange;
|
|
104
109
|
/**
|
|
105
110
|
* Destroys the player and clears the queue.
|
|
106
111
|
* @param {boolean} disconnect - Whether to disconnect the player from the voice channel.
|
|
@@ -26,6 +26,8 @@ class Player {
|
|
|
26
26
|
dynamicRepeat = false;
|
|
27
27
|
/** The time the player is in the track. */
|
|
28
28
|
position = 0;
|
|
29
|
+
/** Last node-reported Discord voice ping in milliseconds; -1 when unavailable. */
|
|
30
|
+
ping = -1;
|
|
29
31
|
/** Whether the player is playing. */
|
|
30
32
|
playing = false;
|
|
31
33
|
/** Whether the player is paused. */
|
|
@@ -162,6 +164,10 @@ class Player {
|
|
|
162
164
|
// Access the data object using the key and cast it to the specified type T.
|
|
163
165
|
return this.data[key];
|
|
164
166
|
}
|
|
167
|
+
/** Returns the last node-reported Discord voice ping in milliseconds, or -1 when unavailable. */
|
|
168
|
+
getPing() {
|
|
169
|
+
return this.ping;
|
|
170
|
+
}
|
|
165
171
|
/**
|
|
166
172
|
* Same as Manager#search() but a shortcut on the player itself.
|
|
167
173
|
* @param query
|
|
@@ -187,7 +193,7 @@ class Player {
|
|
|
187
193
|
// Set the player state to connecting.
|
|
188
194
|
this.state = Enums_1.StateTypes.Connecting;
|
|
189
195
|
// Clone the current player state for comparison.
|
|
190
|
-
const oldPlayer =
|
|
196
|
+
const oldPlayer = { ...this };
|
|
191
197
|
this.manager.sendPacket({
|
|
192
198
|
op: 4,
|
|
193
199
|
d: {
|
|
@@ -200,15 +206,7 @@ class Player {
|
|
|
200
206
|
// Set the player state to connected.
|
|
201
207
|
this.state = Enums_1.StateTypes.Connected;
|
|
202
208
|
// Emit the player state update event.
|
|
203
|
-
this.
|
|
204
|
-
changeType: Enums_1.PlayerStateEventTypes.ConnectionChange,
|
|
205
|
-
details: {
|
|
206
|
-
type: "connection",
|
|
207
|
-
action: "connect",
|
|
208
|
-
previousConnection: oldPlayer?.state === Enums_1.StateTypes.Connected,
|
|
209
|
-
currentConnection: true,
|
|
210
|
-
},
|
|
211
|
-
});
|
|
209
|
+
this.emitConnectionChange(oldPlayer, "connect");
|
|
212
210
|
}
|
|
213
211
|
/**
|
|
214
212
|
* Disconnects the player from the voice channel.
|
|
@@ -218,7 +216,7 @@ class Player {
|
|
|
218
216
|
// Set the player state to disconnecting.
|
|
219
217
|
this.state = Enums_1.StateTypes.Disconnecting;
|
|
220
218
|
// Clone the current player state for comparison.
|
|
221
|
-
const oldPlayer =
|
|
219
|
+
const oldPlayer = { ...this };
|
|
222
220
|
// Pause the player.
|
|
223
221
|
if (this.options.pauseOnDisconnect) {
|
|
224
222
|
await this.pause(true);
|
|
@@ -238,16 +236,19 @@ class Player {
|
|
|
238
236
|
// Set the player state to disconnected.
|
|
239
237
|
this.state = Enums_1.StateTypes.Disconnected;
|
|
240
238
|
// Emit the player state update event.
|
|
239
|
+
this.emitConnectionChange(oldPlayer, "disconnect");
|
|
240
|
+
return this;
|
|
241
|
+
}
|
|
242
|
+
emitConnectionChange(oldPlayer, action) {
|
|
241
243
|
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this, {
|
|
242
244
|
changeType: Enums_1.PlayerStateEventTypes.ConnectionChange,
|
|
243
245
|
details: {
|
|
244
246
|
type: "connection",
|
|
245
|
-
action
|
|
247
|
+
action,
|
|
246
248
|
previousConnection: oldPlayer.state === Enums_1.StateTypes.Connected,
|
|
247
|
-
currentConnection:
|
|
249
|
+
currentConnection: action === "connect",
|
|
248
250
|
},
|
|
249
251
|
});
|
|
250
|
-
return this;
|
|
251
252
|
}
|
|
252
253
|
/**
|
|
253
254
|
* Destroys the player and clears the queue.
|
|
@@ -1340,7 +1341,9 @@ class Player {
|
|
|
1340
1341
|
}
|
|
1341
1342
|
if (!packet?.op)
|
|
1342
1343
|
return;
|
|
1343
|
-
this.manager.
|
|
1344
|
+
if (this.manager.listenerCount(Enums_1.ManagerEventTypes.Debug)) {
|
|
1345
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `VoiceReceiver recieved a payload: ${Utils_1.JSONUtils.safe(textPayload, 2)}`);
|
|
1346
|
+
}
|
|
1344
1347
|
switch (packet.type) {
|
|
1345
1348
|
case "startSpeakingEvent": {
|
|
1346
1349
|
this.manager.emit(Enums_1.ManagerEventTypes.VoiceReceiverStartSpeaking, this, packet.data);
|
|
@@ -1355,7 +1358,9 @@ class Player {
|
|
|
1355
1358
|
break;
|
|
1356
1359
|
}
|
|
1357
1360
|
default: {
|
|
1358
|
-
this.manager.
|
|
1361
|
+
if (this.manager.listenerCount(Enums_1.ManagerEventTypes.Debug)) {
|
|
1362
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `VoiceReceiver recieved an unknown payload: ${Utils_1.JSONUtils.safe(textPayload, 2)}`);
|
|
1363
|
+
}
|
|
1359
1364
|
break;
|
|
1360
1365
|
}
|
|
1361
1366
|
}
|
|
@@ -1408,9 +1413,12 @@ class Player {
|
|
|
1408
1413
|
return;
|
|
1409
1414
|
}
|
|
1410
1415
|
if (frame.op === 3) {
|
|
1411
|
-
|
|
1416
|
+
let stream = this.voiceReceiverStreams.get(key);
|
|
1417
|
+
if (!stream) {
|
|
1418
|
+
stream = { guildId: frame.guildId, userId: frame.userId, type: frame.type, chunks: [] };
|
|
1419
|
+
this.voiceReceiverStreams.set(key, stream);
|
|
1420
|
+
}
|
|
1412
1421
|
stream.chunks.push(frame.data);
|
|
1413
|
-
this.voiceReceiverStreams.set(key, stream);
|
|
1414
1422
|
this.manager.emit(Enums_1.ManagerEventTypes.VoiceReceiverData, this, {
|
|
1415
1423
|
userId: frame.userId,
|
|
1416
1424
|
guildId: frame.guildId,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magmastream",
|
|
3
|
-
"version": "2.10.
|
|
3
|
+
"version": "2.10.6-alpha.2",
|
|
4
4
|
"description": "A user-friendly Lavalink client designed for NodeJS.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"prepare": "npm run build",
|
|
12
12
|
"build": "tsc",
|
|
13
13
|
"types": "rtb --dist dist",
|
|
14
|
-
"test": "npm run build && node --test tests/player-state.test.cjs",
|
|
14
|
+
"test": "npm run build && node --test tests/player-state.test.cjs tests/hot-paths.test.cjs tests/restore-performance.test.cjs",
|
|
15
15
|
"format": "prettier --write .",
|
|
16
16
|
"format:check": "prettier --check .",
|
|
17
17
|
"lint": "eslint \"src/**/*.{ts,js}\"",
|