magmastream 2.10.3-dev.2 → 2.10.4

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,7 +1,7 @@
1
1
  import { Filters } from "./Filters";
2
2
  import { Manager } from "./Manager";
3
3
  import { Node } from "./Node";
4
- import { AnyMessage, IQueue, Lyrics, PlayerOptions, PlayerVoiceState, PlayOptions, SearchQuery, SearchResult, Track } from "./Types";
4
+ import { AnyMessage, IQueue, Lyrics, NodeLinkChapter, NodeLinkFadingConfig, NodeLinkMeaningResult, NodeLinkMix, NodeLinkNextTrackOptions, NodeLinkSponsorBlockSegment, NodeLinkSponsorBlockState, NodeLinkSponsorBlockUpdate, PlayerOptions, PlayerVoiceState, PlayOptions, SearchQuery, SearchResult, Track } from "./Types";
5
5
  import { SponsorBlockSegment, StateTypes } from "./Enums";
6
6
  import { WebSocket } from "ws";
7
7
  export declare class Player {
@@ -58,6 +58,7 @@ export declare class Player {
58
58
  protected voiceReceiverReconnectTimeout?: NodeJS.Timeout;
59
59
  protected voiceReceiverAttempt: number;
60
60
  protected voiceReceiverReconnectTries: number;
61
+ private readonly voiceReceiverStreams;
61
62
  /**
62
63
  * Creates a new player, returns one if it already exists.
63
64
  * @param options The player options.
@@ -148,6 +149,15 @@ export declare class Player {
148
149
  play(track: Track): Promise<Player>;
149
150
  play(options: PlayOptions): Promise<Player>;
150
151
  play(track: Track, options: PlayOptions): Promise<Player>;
152
+ private assertNodeLinkPlayOptionsSupported;
153
+ /** Preloads or clears NodeLink's next track for gapless playback. */
154
+ preloadNextTrack(track: Track | null, options?: NodeLinkNextTrackOptions): Promise<this>;
155
+ /** Updates NodeLink playback fading behavior. */
156
+ setNodeLinkFading(fading: NodeLinkFadingConfig): Promise<this>;
157
+ /** Enables or disables NodeLink loudness normalization. */
158
+ setNodeLinkLoudnessNormalizer(enabled: boolean): Promise<this>;
159
+ private assertNodeLinkFeature;
160
+ private assertNodeLinkFading;
151
161
  /**
152
162
  * Sets the autoplay-state of the player.
153
163
  *
@@ -167,6 +177,70 @@ export declare class Player {
167
177
  * @returns {Promise<Track[]>} - Array of recommended tracks.
168
178
  */
169
179
  getRecommendedTracks(track: Track): Promise<Track[]>;
180
+ /**
181
+ * Loads chapters for the current or provided track using NodeLink.
182
+ * @param {Track} [track] - The track to load chapters for. Defaults to the current queue track.
183
+ * @returns {Promise<NodeLinkChapter[]>} The loaded chapters.
184
+ */
185
+ getChapters(track?: Track): Promise<NodeLinkChapter[]>;
186
+ /**
187
+ * Loads chapters for the current or provided track using NodeLink.
188
+ * @param {Track} [track] - The track to load chapters for. Defaults to the current queue track.
189
+ * @returns {Promise<NodeLinkChapter[]>} The loaded chapters.
190
+ */
191
+ getNodeLinkChapters(track?: Track): Promise<NodeLinkChapter[]>;
192
+ /** Loads NodeLink meaning/background information for the current or provided track. */
193
+ getNodeLinkMeaning(track?: Track, language?: string): Promise<NodeLinkMeaningResult>;
194
+ /**
195
+ * Adds a NodeLink audio mix layer over the current playback.
196
+ * @param {Track} track - The track to mix.
197
+ * @param {number} [volume] - The mix volume in the 0-1 range.
198
+ * @returns {Promise<NodeLinkMix>} The created mix layer.
199
+ */
200
+ addMix(track: Track, volume?: number): Promise<NodeLinkMix>;
201
+ /**
202
+ * Adds a NodeLink audio mix layer over the current playback.
203
+ * @param {Track} track - The track to mix.
204
+ * @param {number} [volume] - The mix volume in the 0-1 range.
205
+ * @returns {Promise<NodeLinkMix>} The created mix layer.
206
+ */
207
+ addNodeLinkMix(track: Track, volume?: number): Promise<NodeLinkMix>;
208
+ /**
209
+ * Gets the active NodeLink audio mix layers.
210
+ * @returns {Promise<NodeLinkMix[]>} The active mix layers.
211
+ */
212
+ getMixes(): Promise<NodeLinkMix[]>;
213
+ /**
214
+ * Gets the active NodeLink audio mix layers.
215
+ * @returns {Promise<NodeLinkMix[]>} The active mix layers.
216
+ */
217
+ getNodeLinkMixes(): Promise<NodeLinkMix[]>;
218
+ /**
219
+ * Updates a NodeLink audio mix layer volume.
220
+ * @param {string} mixId - The mix layer id.
221
+ * @param {number} volume - The mix volume in the 0-1 range.
222
+ * @returns {Promise<void>}
223
+ */
224
+ updateMix(mixId: string, volume: number): Promise<void>;
225
+ /**
226
+ * Updates a NodeLink audio mix layer volume.
227
+ * @param {string} mixId - The mix layer id.
228
+ * @param {number} volume - The mix volume in the 0-1 range.
229
+ * @returns {Promise<void>}
230
+ */
231
+ updateNodeLinkMix(mixId: string, volume: number): Promise<void>;
232
+ /**
233
+ * Removes a NodeLink audio mix layer.
234
+ * @param {string} mixId - The mix layer id.
235
+ * @returns {Promise<void>}
236
+ */
237
+ removeMix(mixId: string): Promise<void>;
238
+ /**
239
+ * Removes a NodeLink audio mix layer.
240
+ * @param {string} mixId - The mix layer id.
241
+ * @returns {Promise<void>}
242
+ */
243
+ removeNodeLinkMix(mixId: string): Promise<void>;
170
244
  /**
171
245
  * Sets the volume of the player.
172
246
  * @param {number} volume - The new volume. Must be between 0 and 500 when using filter mode (100 = 100%).
@@ -189,6 +263,12 @@ export declare class Player {
189
263
  * @returns {Promise<SponsorBlockSegment[]>} The sponsorblock segments.
190
264
  */
191
265
  getSponsorBlock(): Promise<SponsorBlockSegment[]>;
266
+ /** Returns the complete NodeLink SponsorBlock state. */
267
+ getNodeLinkSponsorBlockState(): Promise<NodeLinkSponsorBlockState>;
268
+ /** Updates NodeLink SponsorBlock categories, actions, margin, or enabled state. */
269
+ updateNodeLinkSponsorBlock(update: NodeLinkSponsorBlockUpdate): Promise<NodeLinkSponsorBlockState>;
270
+ /** Replaces NodeLink's SponsorBlock segments with an explicit segment list. */
271
+ setNodeLinkSponsorBlockSegments(segments: NodeLinkSponsorBlockSegment[]): Promise<NodeLinkSponsorBlockState>;
192
272
  /**
193
273
  * Deletes the sponsorblock for the player. This will remove all sponsorblock segments that have been set for the player.
194
274
  * @returns {Promise<void>}
@@ -329,10 +409,13 @@ export declare class Player {
329
409
  private openVoiceReceiver;
330
410
  /**
331
411
  * Handles a voice receiver message.
332
- * @param {string} payload - The payload to handle.
412
+ * @param {RawData} payload - The payload to handle.
333
413
  * @returns {Promise<void>} - A promise that resolves when the voice receiver message is handled.
334
414
  */
335
415
  private onVoiceReceiverMessage;
416
+ private toVoiceReceiverBuffer;
417
+ private parseNodeLinkVoiceFrame;
418
+ private handleNodeLinkVoiceFrame;
336
419
  /**
337
420
  * Handles a voice receiver error.
338
421
  * @param {Error} error - The error to handle.
@@ -61,11 +61,12 @@ class Player {
61
61
  dynamicRepeatIntervalMs = null;
62
62
  static _manager;
63
63
  /** Should only be used when the node is a NodeLink */
64
- voiceReceiverWsClient;
65
- isConnectToVoiceReceiver;
64
+ voiceReceiverWsClient = null;
65
+ isConnectToVoiceReceiver = false;
66
66
  voiceReceiverReconnectTimeout;
67
- voiceReceiverAttempt;
68
- voiceReceiverReconnectTries;
67
+ voiceReceiverAttempt = 1;
68
+ voiceReceiverReconnectTries = 30;
69
+ voiceReceiverStreams = new Map();
69
70
  /**
70
71
  * Creates a new player, returns one if it already exists.
71
72
  * @param options The player options.
@@ -270,6 +271,7 @@ class Player {
270
271
  this.voiceReceiverWsClient.close();
271
272
  this.voiceReceiverWsClient = null;
272
273
  }
274
+ this.voiceReceiverStreams.clear();
273
275
  if (disconnect) {
274
276
  await this.disconnect().catch(() => { });
275
277
  }
@@ -381,23 +383,101 @@ class Player {
381
383
  console.error(error);
382
384
  return this;
383
385
  }
384
- const isPlayOptions = (v) => typeof v === "object" && v !== null && ("startTime" in v || "endTime" in v || "noReplace" in v);
386
+ const isPlayOptions = (v) => typeof v === "object" && v !== null && ("startTime" in v || "endTime" in v || "noReplace" in v || "audioTrackId" in v || "language" in v || "loudnessNormalizer" in v);
385
387
  const finalOptions = playOptions ? playOptions : isPlayOptions(optionsOrTrack) ? optionsOrTrack : {};
388
+ this.assertNodeLinkPlayOptionsSupported(finalOptions);
386
389
  await this.node.rest.updatePlayer({
387
390
  guildId: this.guildId,
388
391
  noReplace: finalOptions.noReplace,
389
392
  data: {
390
393
  track: {
391
394
  encoded: currentTrack.track,
395
+ ...(typeof finalOptions.audioTrackId === "string" && { audioTrackId: finalOptions.audioTrackId }),
396
+ ...(typeof finalOptions.language === "string" && { language: finalOptions.language }),
392
397
  },
393
398
  ...(typeof finalOptions.startTime === "number" && { position: finalOptions.startTime }),
394
399
  ...(typeof finalOptions.endTime === "number" && finalOptions.endTime > 0 && { endTime: finalOptions.endTime }),
400
+ ...(typeof finalOptions.loudnessNormalizer === "boolean" && { loudnessNormalizer: finalOptions.loudnessNormalizer }),
395
401
  },
396
402
  });
397
403
  this.playing = true;
398
404
  this.position = finalOptions.startTime ?? 0;
399
405
  return this;
400
406
  }
407
+ assertNodeLinkPlayOptionsSupported(options) {
408
+ if (this.node.isNodeLink)
409
+ return;
410
+ const nodeLinkOnlyFields = [
411
+ ...(options.audioTrackId !== undefined ? ["audioTrackId"] : []),
412
+ ...(options.language !== undefined ? ["language"] : []),
413
+ ...(options.loudnessNormalizer !== undefined ? ["loudnessNormalizer"] : []),
414
+ ];
415
+ if (!nodeLinkOnlyFields.length)
416
+ return;
417
+ throw new MagmastreamError_1.MagmaStreamError({
418
+ code: Enums_1.MagmaStreamErrorCode.NODE_PROTOCOL_ERROR,
419
+ message: `NodeLink-only play options cannot be used on Lavalink node "${this.node.options.identifier}": ${nodeLinkOnlyFields.join(", ")}.`,
420
+ context: { identifier: this.node.options.identifier, guildId: this.guildId, fields: nodeLinkOnlyFields },
421
+ });
422
+ }
423
+ /** Preloads or clears NodeLink's next track for gapless playback. */
424
+ async preloadNextTrack(track, options = {}) {
425
+ this.assertNodeLinkFeature("preload a next track");
426
+ await this.node.rest.updatePlayer({
427
+ guildId: this.guildId,
428
+ data: {
429
+ nextTrack: track
430
+ ? {
431
+ encoded: track.track,
432
+ ...(options.audioTrackId !== undefined ? { audioTrackId: options.audioTrackId } : {}),
433
+ ...(options.language !== undefined ? { language: options.language } : {}),
434
+ }
435
+ : null,
436
+ },
437
+ });
438
+ return this;
439
+ }
440
+ /** Updates NodeLink playback fading behavior. */
441
+ async setNodeLinkFading(fading) {
442
+ this.assertNodeLinkFeature("set playback fading");
443
+ this.assertNodeLinkFading(fading);
444
+ await this.node.rest.updatePlayer({ guildId: this.guildId, data: { fading } });
445
+ return this;
446
+ }
447
+ /** Enables or disables NodeLink loudness normalization. */
448
+ async setNodeLinkLoudnessNormalizer(enabled) {
449
+ this.assertNodeLinkFeature("set loudness normalization");
450
+ if (typeof enabled !== "boolean") {
451
+ throw new MagmastreamError_1.MagmaStreamError({ code: Enums_1.MagmaStreamErrorCode.NODE_PROTOCOL_ERROR, message: "NodeLink loudness normalization must be a boolean." });
452
+ }
453
+ await this.node.rest.updatePlayer({ guildId: this.guildId, data: { loudnessNormalizer: enabled } });
454
+ return this;
455
+ }
456
+ assertNodeLinkFeature(action) {
457
+ if (this.node.isNodeLink)
458
+ return;
459
+ throw new MagmastreamError_1.MagmaStreamError({
460
+ code: Enums_1.MagmaStreamErrorCode.NODE_PROTOCOL_ERROR,
461
+ message: `NodeLink is required to ${action} on node "${this.node.options.identifier}".`,
462
+ context: { identifier: this.node.options.identifier, guildId: this.guildId },
463
+ });
464
+ }
465
+ assertNodeLinkFading(fading) {
466
+ if (!fading || typeof fading !== "object") {
467
+ throw new MagmastreamError_1.MagmaStreamError({ code: Enums_1.MagmaStreamErrorCode.NODE_PROTOCOL_ERROR, message: "NodeLink fading must be an object." });
468
+ }
469
+ for (const key of ["trackStart", "trackEnd", "trackStop", "seek", "pause", "resume"]) {
470
+ const section = fading[key];
471
+ if (section === undefined)
472
+ continue;
473
+ if (typeof section.duration !== "number" || !Number.isFinite(section.duration) || section.duration < 0) {
474
+ throw new MagmastreamError_1.MagmaStreamError({
475
+ code: Enums_1.MagmaStreamErrorCode.NODE_PROTOCOL_ERROR,
476
+ message: `NodeLink fading section "${key}" requires a non-negative duration.`,
477
+ });
478
+ }
479
+ }
480
+ }
401
481
  /**
402
482
  * Sets the autoplay-state of the player.
403
483
  *
@@ -458,6 +538,107 @@ class Player {
458
538
  const tracks = await Utils_1.AutoPlayUtils.getRecommendedTracks(track);
459
539
  return tracks;
460
540
  }
541
+ /**
542
+ * Loads chapters for the current or provided track using NodeLink.
543
+ * @param {Track} [track] - The track to load chapters for. Defaults to the current queue track.
544
+ * @returns {Promise<NodeLinkChapter[]>} The loaded chapters.
545
+ */
546
+ async getChapters(track) {
547
+ const targetTrack = track ?? (await this.queue.getCurrent());
548
+ if (!targetTrack) {
549
+ throw new MagmastreamError_1.MagmaStreamError({
550
+ code: Enums_1.MagmaStreamErrorCode.PLAYER_QUEUE_EMPTY,
551
+ message: "The queue is empty.",
552
+ });
553
+ }
554
+ return this.node.getChapters(targetTrack);
555
+ }
556
+ /**
557
+ * Loads chapters for the current or provided track using NodeLink.
558
+ * @param {Track} [track] - The track to load chapters for. Defaults to the current queue track.
559
+ * @returns {Promise<NodeLinkChapter[]>} The loaded chapters.
560
+ */
561
+ async getNodeLinkChapters(track) {
562
+ return this.getChapters(track);
563
+ }
564
+ /** Loads NodeLink meaning/background information for the current or provided track. */
565
+ async getNodeLinkMeaning(track, language = "en") {
566
+ const target = track ?? (await this.queue.getCurrent());
567
+ if (!target) {
568
+ throw new MagmastreamError_1.MagmaStreamError({
569
+ code: Enums_1.MagmaStreamErrorCode.PLAYER_QUEUE_EMPTY,
570
+ message: "Cannot load meaning without a current track.",
571
+ context: { guildId: this.guildId },
572
+ });
573
+ }
574
+ return this.node.getNodeLinkMeaning(target, language);
575
+ }
576
+ /**
577
+ * Adds a NodeLink audio mix layer over the current playback.
578
+ * @param {Track} track - The track to mix.
579
+ * @param {number} [volume] - The mix volume in the 0-1 range.
580
+ * @returns {Promise<NodeLinkMix>} The created mix layer.
581
+ */
582
+ async addMix(track, volume) {
583
+ return this.node.addMix(this, track, volume);
584
+ }
585
+ /**
586
+ * Adds a NodeLink audio mix layer over the current playback.
587
+ * @param {Track} track - The track to mix.
588
+ * @param {number} [volume] - The mix volume in the 0-1 range.
589
+ * @returns {Promise<NodeLinkMix>} The created mix layer.
590
+ */
591
+ async addNodeLinkMix(track, volume) {
592
+ return this.addMix(track, volume);
593
+ }
594
+ /**
595
+ * Gets the active NodeLink audio mix layers.
596
+ * @returns {Promise<NodeLinkMix[]>} The active mix layers.
597
+ */
598
+ async getMixes() {
599
+ return this.node.getMixes(this);
600
+ }
601
+ /**
602
+ * Gets the active NodeLink audio mix layers.
603
+ * @returns {Promise<NodeLinkMix[]>} The active mix layers.
604
+ */
605
+ async getNodeLinkMixes() {
606
+ return this.getMixes();
607
+ }
608
+ /**
609
+ * Updates a NodeLink audio mix layer volume.
610
+ * @param {string} mixId - The mix layer id.
611
+ * @param {number} volume - The mix volume in the 0-1 range.
612
+ * @returns {Promise<void>}
613
+ */
614
+ async updateMix(mixId, volume) {
615
+ return this.node.updateMix(this, mixId, volume);
616
+ }
617
+ /**
618
+ * Updates a NodeLink audio mix layer volume.
619
+ * @param {string} mixId - The mix layer id.
620
+ * @param {number} volume - The mix volume in the 0-1 range.
621
+ * @returns {Promise<void>}
622
+ */
623
+ async updateNodeLinkMix(mixId, volume) {
624
+ return this.updateMix(mixId, volume);
625
+ }
626
+ /**
627
+ * Removes a NodeLink audio mix layer.
628
+ * @param {string} mixId - The mix layer id.
629
+ * @returns {Promise<void>}
630
+ */
631
+ async removeMix(mixId) {
632
+ return this.node.removeMix(this, mixId);
633
+ }
634
+ /**
635
+ * Removes a NodeLink audio mix layer.
636
+ * @param {string} mixId - The mix layer id.
637
+ * @returns {Promise<void>}
638
+ */
639
+ async removeNodeLinkMix(mixId) {
640
+ return this.removeMix(mixId);
641
+ }
461
642
  /**
462
643
  * Sets the volume of the player.
463
644
  * @param {number} volume - The new volume. Must be between 0 and 500 when using filter mode (100 = 100%).
@@ -526,6 +707,18 @@ class Player {
526
707
  async getSponsorBlock() {
527
708
  return this.node.getSponsorBlock(this);
528
709
  }
710
+ /** Returns the complete NodeLink SponsorBlock state. */
711
+ async getNodeLinkSponsorBlockState() {
712
+ return this.node.getNodeLinkSponsorBlockState(this);
713
+ }
714
+ /** Updates NodeLink SponsorBlock categories, actions, margin, or enabled state. */
715
+ async updateNodeLinkSponsorBlock(update) {
716
+ return this.node.updateNodeLinkSponsorBlock(this, update);
717
+ }
718
+ /** Replaces NodeLink's SponsorBlock segments with an explicit segment list. */
719
+ async setNodeLinkSponsorBlockSegments(segments) {
720
+ return this.node.setNodeLinkSponsorBlockSegments(this, segments);
721
+ }
529
722
  /**
530
723
  * Deletes the sponsorblock for the player. This will remove all sponsorblock segments that have been set for the player.
531
724
  * @returns {Promise<void>}
@@ -1040,10 +1233,11 @@ class Player {
1040
1233
  "Client-Name": this.manager.options.clientName,
1041
1234
  };
1042
1235
  const { host, useSSL, port } = this.node.options;
1043
- this.voiceReceiverWsClient = new ws_1.WebSocket(`${useSSL ? "wss" : "ws"}://${host}:${port}/connection/data`, { headers });
1236
+ this.voiceReceiverReconnectTries = this.node.options.maxRetryAttempts ?? this.voiceReceiverReconnectTries;
1237
+ this.voiceReceiverWsClient = new ws_1.WebSocket(`${useSSL ? "wss" : "ws"}://${host}:${port}/v4/websocket/voice/${this.guildId}`, { headers });
1044
1238
  this.voiceReceiverWsClient.on("open", () => this.openVoiceReceiver());
1045
1239
  this.voiceReceiverWsClient.on("error", (err) => this.onVoiceReceiverError(err));
1046
- this.voiceReceiverWsClient.on("message", (rawMessage) => this.onVoiceReceiverMessage(rawMessage.toString()));
1240
+ this.voiceReceiverWsClient.on("message", (rawMessage) => this.onVoiceReceiverMessage(rawMessage));
1047
1241
  this.voiceReceiverWsClient.on("close", (code, reason) => this.closeVoiceReceiver(code, reason.toString()));
1048
1242
  }
1049
1243
  /**
@@ -1065,6 +1259,7 @@ class Player {
1065
1259
  this.voiceReceiverWsClient = null;
1066
1260
  }
1067
1261
  this.isConnectToVoiceReceiver = false;
1262
+ this.voiceReceiverStreams.clear();
1068
1263
  }
1069
1264
  /**
1070
1265
  * Closes the voice receiver for the player.
@@ -1108,6 +1303,8 @@ class Player {
1108
1303
  this.voiceReceiverWsClient?.close(1000, "destroy");
1109
1304
  this.voiceReceiverWsClient?.removeAllListeners();
1110
1305
  this.voiceReceiverWsClient = null;
1306
+ this.voiceReceiverStreams.clear();
1307
+ this.isConnectToVoiceReceiver = false;
1111
1308
  this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[PLAYER] Disconnected from voice receiver for player ${this.guildId}`);
1112
1309
  this.manager.emit(Enums_1.ManagerEventTypes.VoiceReceiverDisconnect, this);
1113
1310
  }
@@ -1125,14 +1322,28 @@ class Player {
1125
1322
  }
1126
1323
  /**
1127
1324
  * Handles a voice receiver message.
1128
- * @param {string} payload - The payload to handle.
1325
+ * @param {RawData} payload - The payload to handle.
1129
1326
  * @returns {Promise<void>} - A promise that resolves when the voice receiver message is handled.
1130
1327
  */
1131
1328
  async onVoiceReceiverMessage(payload) {
1132
- const packet = JSON.parse(payload);
1329
+ const buffer = this.toVoiceReceiverBuffer(payload);
1330
+ const parsedFrame = this.parseNodeLinkVoiceFrame(buffer);
1331
+ if (parsedFrame) {
1332
+ this.handleNodeLinkVoiceFrame(parsedFrame);
1333
+ return;
1334
+ }
1335
+ const textPayload = buffer.toString();
1336
+ let packet;
1337
+ try {
1338
+ packet = JSON.parse(textPayload);
1339
+ }
1340
+ catch {
1341
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `VoiceReceiver recieved an unknown binary payload for player ${this.guildId}`);
1342
+ return;
1343
+ }
1133
1344
  if (!packet?.op)
1134
1345
  return;
1135
- this.manager.emit(Enums_1.ManagerEventTypes.Debug, `VoiceReceiver recieved a payload: ${Utils_1.JSONUtils.safe(payload, 2)}`);
1346
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `VoiceReceiver recieved a payload: ${Utils_1.JSONUtils.safe(textPayload, 2)}`);
1136
1347
  switch (packet.type) {
1137
1348
  case "startSpeakingEvent": {
1138
1349
  this.manager.emit(Enums_1.ManagerEventTypes.VoiceReceiverStartSpeaking, this, packet.data);
@@ -1141,17 +1352,94 @@ class Player {
1141
1352
  case "endSpeakingEvent": {
1142
1353
  const data = {
1143
1354
  ...packet.data,
1144
- data: Buffer.from(packet.data.data, "base64"),
1355
+ data: Buffer.isBuffer(packet.data.data) ? packet.data.data : Buffer.from(packet.data.data, "base64"),
1145
1356
  };
1146
1357
  this.manager.emit(Enums_1.ManagerEventTypes.VoiceReceiverEndSpeaking, this, data);
1147
1358
  break;
1148
1359
  }
1149
1360
  default: {
1150
- this.manager.emit(Enums_1.ManagerEventTypes.Debug, `VoiceReceiver recieved an unknown payload: ${Utils_1.JSONUtils.safe(payload, 2)}`);
1361
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `VoiceReceiver recieved an unknown payload: ${Utils_1.JSONUtils.safe(textPayload, 2)}`);
1151
1362
  break;
1152
1363
  }
1153
1364
  }
1154
1365
  }
1366
+ toVoiceReceiverBuffer(payload) {
1367
+ if (Buffer.isBuffer(payload))
1368
+ return payload;
1369
+ if (payload instanceof ArrayBuffer)
1370
+ return Buffer.from(payload);
1371
+ return Buffer.concat(payload);
1372
+ }
1373
+ parseNodeLinkVoiceFrame(payload) {
1374
+ if (payload.length < 12)
1375
+ return null;
1376
+ let offset = 0;
1377
+ const op = payload.readUInt8(offset++);
1378
+ if (op < 1 || op > 3)
1379
+ return null;
1380
+ const format = payload.readUInt8(offset++);
1381
+ const guildLength = payload.readUInt8(offset++);
1382
+ if (offset + guildLength > payload.length)
1383
+ return null;
1384
+ const guildId = payload.toString("utf8", offset, offset + guildLength);
1385
+ offset += guildLength;
1386
+ if (offset >= payload.length)
1387
+ return null;
1388
+ const userLength = payload.readUInt8(offset++);
1389
+ if (offset + userLength + 8 > payload.length)
1390
+ return null;
1391
+ const userId = payload.toString("utf8", offset, offset + userLength);
1392
+ offset += userLength;
1393
+ const ssrc = payload.readUInt32BE(offset);
1394
+ offset += 4;
1395
+ const timestamp = payload.readUInt32BE(offset);
1396
+ offset += 4;
1397
+ const type = format === 2 ? "pcm_s16le" : format === 1 ? "ogg" : "opus";
1398
+ return { op, type, guildId, userId, ssrc, timestamp, data: payload.subarray(offset) };
1399
+ }
1400
+ handleNodeLinkVoiceFrame(frame) {
1401
+ const key = `${frame.guildId}:${frame.ssrc}`;
1402
+ if (frame.op === 1) {
1403
+ this.voiceReceiverStreams.set(key, { guildId: frame.guildId, userId: frame.userId, type: frame.type, chunks: [] });
1404
+ this.manager.emit(Enums_1.ManagerEventTypes.VoiceReceiverStartSpeaking, this, {
1405
+ userId: frame.userId,
1406
+ guildId: frame.guildId,
1407
+ ssrc: frame.ssrc,
1408
+ type: frame.type,
1409
+ timestamp: frame.timestamp,
1410
+ });
1411
+ return;
1412
+ }
1413
+ if (frame.op === 3) {
1414
+ const stream = this.voiceReceiverStreams.get(key) ?? { guildId: frame.guildId, userId: frame.userId, type: frame.type, chunks: [] };
1415
+ stream.chunks.push(frame.data);
1416
+ this.voiceReceiverStreams.set(key, stream);
1417
+ this.manager.emit(Enums_1.ManagerEventTypes.VoiceReceiverData, this, {
1418
+ userId: frame.userId,
1419
+ guildId: frame.guildId,
1420
+ data: frame.data,
1421
+ type: frame.type,
1422
+ ssrc: frame.ssrc,
1423
+ timestamp: frame.timestamp,
1424
+ });
1425
+ return;
1426
+ }
1427
+ if (frame.op === 2) {
1428
+ const stream = this.voiceReceiverStreams.get(key);
1429
+ const chunks = stream?.chunks ?? [];
1430
+ if (frame.data.length > 0)
1431
+ chunks.push(frame.data);
1432
+ this.voiceReceiverStreams.delete(key);
1433
+ this.manager.emit(Enums_1.ManagerEventTypes.VoiceReceiverEndSpeaking, this, {
1434
+ userId: stream?.userId ?? frame.userId,
1435
+ guildId: stream?.guildId ?? frame.guildId,
1436
+ data: Buffer.concat(chunks),
1437
+ type: stream?.type ?? frame.type,
1438
+ ssrc: frame.ssrc,
1439
+ timestamp: frame.timestamp,
1440
+ });
1441
+ }
1442
+ }
1155
1443
  /**
1156
1444
  * Handles a voice receiver error.
1157
1445
  * @param {Error} error - The error to handle.
@@ -1,6 +1,7 @@
1
1
  import { Node } from "./Node";
2
+ import { type Response as UndiciResponse } from "undici";
2
3
  import { Manager } from "./Manager";
3
- import { LavalinkSession, LavaPlayer, RestPlayOptions } from "./Types";
4
+ import { LavalinkInfo, LavalinkSession, LavaPlayer, NodeLinkConnectionInfo, NodeLinkLoadStreamOptions, NodeLinkMeaningResult, NodeLinkTrackEncodeInput, NodeLinkTrackStreamResult, NodeStats, RestPlayOptions, RoutePlannerStatus, TrackData } from "./Types";
4
5
  /** Handles the requests sent to the Lavalink REST API. */
5
6
  export declare class Rest {
6
7
  /** The Node that this Rest instance is connected to. */
@@ -32,6 +33,11 @@ export declare class Rest {
32
33
  * @returns {Promise<LavaPlayer>} Returns the player.
33
34
  */
34
35
  getPlayer(guildId: string): Promise<LavaPlayer>;
36
+ /**
37
+ * Retrieves all players that belong to the current session.
38
+ * @returns {Promise<LavaPlayer[]>} Returns the players.
39
+ */
40
+ getPlayers(): Promise<LavaPlayer[]>;
35
41
  /**
36
42
  * Sends a PATCH request to update player related data.
37
43
  * @param {RestPlayOptions} options The options to update the player with.
@@ -51,6 +57,59 @@ export declare class Rest {
51
57
  * @returns {Promise<LavalinkSession>} The updated session.
52
58
  */
53
59
  updateSession(resuming: boolean, timeout: number): Promise<LavalinkSession>;
60
+ /**
61
+ * Decodes a single Lavalink track.
62
+ * @param {string} encodedTrack The encoded track string.
63
+ * @returns {Promise<TrackData>} The decoded track.
64
+ */
65
+ decodeTrack(encodedTrack: string): Promise<TrackData>;
66
+ /**
67
+ * Decodes multiple Lavalink tracks.
68
+ * @param {string[]} encodedTracks The encoded track strings.
69
+ * @returns {Promise<TrackData[]>} The decoded tracks.
70
+ */
71
+ decodeTracks(encodedTracks: string[]): Promise<TrackData[]>;
72
+ /**
73
+ * Retrieves this node's Lavalink info.
74
+ * @returns {Promise<LavalinkInfo>} The node info.
75
+ */
76
+ getInfo(): Promise<LavalinkInfo>;
77
+ /**
78
+ * Retrieves this node's current stats.
79
+ * @returns {Promise<NodeStats>} The node stats.
80
+ */
81
+ getStats(): Promise<NodeStats>;
82
+ /** Returns NodeLink connection diagnostics. */
83
+ getNodeLinkConnection(): Promise<NodeLinkConnectionInfo>;
84
+ /** Encodes one track info payload using NodeLink. */
85
+ encodeNodeLinkTrack(track: NodeLinkTrackEncodeInput): Promise<string>;
86
+ /** Encodes multiple track info payloads using NodeLink. */
87
+ encodeNodeLinkTracks(tracks: NodeLinkTrackEncodeInput[]): Promise<string[]>;
88
+ /** Loads NodeLink meaning/background information for an encoded track. */
89
+ getNodeLinkMeaning(encodedTrack: string, language?: string): Promise<NodeLinkMeaningResult>;
90
+ /** Resolves NodeLink's direct stream URL and format metadata. */
91
+ getNodeLinkTrackStream(encodedTrack: string, itag?: number): Promise<NodeLinkTrackStreamResult>;
92
+ /** Opens NodeLink's raw PCM stream response. The caller consumes response.body. */
93
+ loadNodeLinkPcmStream(encodedTrack: string, options?: NodeLinkLoadStreamOptions): Promise<UndiciResponse>;
94
+ /**
95
+ * Retrieves the route planner status.
96
+ * @returns {Promise<RoutePlannerStatus>} The route planner status.
97
+ */
98
+ getRoutePlannerStatus(): Promise<RoutePlannerStatus>;
99
+ /**
100
+ * Unmarks a failed route planner address.
101
+ * @param {string} address The address to free.
102
+ * @returns {Promise<void>} Void.
103
+ */
104
+ freeRoutePlannerAddress(address: string): Promise<void>;
105
+ /**
106
+ * Unmarks all failed route planner addresses.
107
+ * @returns {Promise<void>} Void.
108
+ */
109
+ freeAllRoutePlannerAddresses(): Promise<void>;
110
+ private assertUpdatePlayerSupported;
111
+ private assertNodeLink;
112
+ private assertEncodedTrack;
54
113
  /**
55
114
  * Sends a request to the specified endpoint and returns the response data.
56
115
  * @param {string} method The HTTP method to use for the request.
@@ -78,7 +137,7 @@ export declare class Rest {
78
137
  * @param {unknown} body The data to send in the request body.
79
138
  * @returns {Promise<T>} The response data of the POST request.
80
139
  */
81
- post<T>(endpoint: string, body: unknown): Promise<T>;
140
+ post<T>(endpoint: string, body?: unknown): Promise<T>;
82
141
  /**
83
142
  * Sends a PUT request to the specified endpoint and returns the response data.
84
143
  * @param {string} endpoint The endpoint to send the PUT request to.