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.
- package/dist/structures/Enums.d.ts +145 -1
- package/dist/structures/Enums.js +148 -1
- package/dist/structures/Filters.d.ts +120 -1
- package/dist/structures/Filters.js +348 -11
- package/dist/structures/Manager.d.ts +9 -0
- package/dist/structures/Manager.js +169 -30
- package/dist/structures/Node.d.ts +89 -2
- package/dist/structures/Node.js +504 -53
- package/dist/structures/Player.d.ts +85 -2
- package/dist/structures/Player.js +300 -12
- package/dist/structures/Rest.d.ts +61 -2
- package/dist/structures/Rest.js +210 -1
- package/dist/structures/Types.d.ts +645 -90
- package/dist/structures/Utils.d.ts +1 -1
- package/dist/structures/Utils.js +60 -18
- package/dist/utils/managerCheck.js +3 -2
- package/package.json +3 -2
|
@@ -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 {
|
|
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.
|
|
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
|
|
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 {
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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.
|