lavalink-client 2.4.4 → 2.4.5

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.
@@ -1159,6 +1159,8 @@ class LavalinkNode {
1159
1159
  }
1160
1160
  /** @private util function for handling trackEnd event */
1161
1161
  async trackEnd(player, track, payload) {
1162
+ if (player.get('internal_nodeChanging') === true)
1163
+ return; // Check if nodeChange is in Progress than stop the trackEnd Event from being triggered.
1162
1164
  const trackToUse = track || this.getTrackOfPayload(payload);
1163
1165
  // If a track was forcibly played
1164
1166
  if (payload.reason === "replaced") {
@@ -1374,6 +1376,8 @@ class LavalinkNode {
1374
1376
  }
1375
1377
  /** private util function for handling the queue end event */
1376
1378
  async queueEnd(player, track, payload) {
1379
+ if (player.get('internal_nodeChanging') === true)
1380
+ return; // Check if nodeChange is in Progress than stop the queueEnd Event from being triggered.
1377
1381
  // add previous track to the queue!
1378
1382
  player.queue.current = null;
1379
1383
  player.playing = false;
@@ -217,8 +217,9 @@ export declare class Player {
217
217
  /**
218
218
  * Move the player on a different Audio-Node
219
219
  * @param newNode New Node / New Node Id
220
+ * @param checkSources If it should check if the sources are supported by the new node
220
221
  */
221
- changeNode(newNode: LavalinkNode | string): Promise<string>;
222
+ changeNode(newNode: LavalinkNode | string, checkSources?: boolean): Promise<string>;
222
223
  /** Converts the Player including Queue to a Json state */
223
224
  toJSON(): PlayerJson;
224
225
  }
@@ -459,11 +459,11 @@ class Player {
459
459
  throw new RangeError("Can't skip more than the queue size");
460
460
  await this.queue.splice(0, skipTo - 1);
461
461
  }
462
- if (!this.playing)
462
+ if (!this.playing && !this.queue.current)
463
463
  return (this.play(), this);
464
464
  const now = performance.now();
465
465
  this.set("internal_skipped", true);
466
- await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { track: { encoded: null } } });
466
+ await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { track: { encoded: null }, paused: false } });
467
467
  this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
468
468
  return this;
469
469
  }
@@ -639,11 +639,37 @@ class Player {
639
639
  /**
640
640
  * Move the player on a different Audio-Node
641
641
  * @param newNode New Node / New Node Id
642
+ * @param checkSources If it should check if the sources are supported by the new node
642
643
  */
643
- async changeNode(newNode) {
644
+ async changeNode(newNode, checkSources = true) {
644
645
  const updateNode = typeof newNode === "string" ? this.LavalinkManager.nodeManager.nodes.get(newNode) : newNode;
645
646
  if (!updateNode)
646
647
  throw new Error("Could not find the new Node");
648
+ if (!updateNode.connected)
649
+ throw new Error("The provided Node is not active or disconnected");
650
+ if (this.node.id === updateNode.id)
651
+ throw new Error("Player is already on the provided Node");
652
+ if (this.get("internal_nodeChanging") === true)
653
+ throw new Error("Player is already changing the node please wait");
654
+ if (checkSources) {
655
+ const isDefaultSource = () => {
656
+ try {
657
+ this.LavalinkManager.utils.validateSourceString(updateNode, this.LavalinkManager.options.playerOptions.defaultSearchPlatform);
658
+ return true;
659
+ }
660
+ catch {
661
+ return false;
662
+ }
663
+ };
664
+ if (!isDefaultSource())
665
+ throw new RangeError(`defaultSearchPlatform "${this.LavalinkManager.options.playerOptions.defaultSearchPlatform}" is not supported by the newNode`);
666
+ if (this.queue.current || this.queue.tracks.length) { // Check if all queued track sources are supported by the new node
667
+ const trackSources = new Set([this.queue.current, ...this.queue.tracks].map(track => track.info.sourceName));
668
+ const missingSources = [...trackSources].filter(source => !updateNode.info.sourceManagers.includes(source));
669
+ if (missingSources.length)
670
+ throw new RangeError(`Sources missing for Node ${updateNode.id}: ${missingSources.join(', ')}`);
671
+ }
672
+ }
647
673
  if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
648
674
  this.LavalinkManager.emit("debug", Constants_1.DebugEvents.PlayerChangeNode, {
649
675
  state: "log",
@@ -653,23 +679,60 @@ class Player {
653
679
  }
654
680
  const data = this.toJSON();
655
681
  const currentTrack = this.queue.current;
656
- await this.node.destroyPlayer(this.guildId);
682
+ const voiceData = this.voice;
683
+ if (!voiceData.endpoint ||
684
+ !voiceData.sessionId ||
685
+ !voiceData.token)
686
+ throw new Error("Voice Data is missing, can't change the node");
687
+ this.set("internal_nodeChanging", true); // This will stop execution of trackEnd or queueEnd event while changing the node
688
+ if (this.node.connected)
689
+ await this.node.destroyPlayer(this.guildId); // destroy the player on the currentNode if it's connected
657
690
  this.node = updateNode;
658
691
  const now = performance.now();
659
- await this.connect();
660
- await this.node.updatePlayer({
661
- guildId: this.guildId,
662
- noReplace: false,
663
- playerOptions: {
664
- position: data.position,
665
- volume: Math.round(Math.max(Math.min(data.volume, 1000), 0)),
666
- paused: data.paused,
667
- filters: { ...data.filters, equalizer: data.equalizer },
668
- track: currentTrack ?? undefined
669
- },
670
- });
671
- this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
672
- return this.node.id;
692
+ try {
693
+ await this.connect();
694
+ const endpoint = `/sessions/${this.node.sessionId}/players/${this.guildId}`; //Send the VoiceData to the newly connected node.
695
+ await this.node.request(endpoint, r => {
696
+ r.method = "PATCH";
697
+ r.headers["Content-Type"] = "application/json";
698
+ r.body = JSON.stringify({
699
+ voice: {
700
+ token: voiceData.token,
701
+ endpoint: voiceData.endpoint,
702
+ sessionId: voiceData.sessionId
703
+ }
704
+ });
705
+ });
706
+ if (currentTrack) { // If there is a current track, send it to the new node.
707
+ await this.node.updatePlayer({
708
+ guildId: this.guildId,
709
+ noReplace: false,
710
+ playerOptions: {
711
+ track: currentTrack ?? null,
712
+ position: currentTrack ? data.position : 0,
713
+ volume: data.lavalinkVolume,
714
+ paused: data.paused,
715
+ //filters: { ...data.filters, equalizer: data.equalizer }, Sending filters on nodeChange causes issues (player gets dicsonnected)
716
+ }
717
+ });
718
+ }
719
+ this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
720
+ return this.node.id;
721
+ }
722
+ catch (error) {
723
+ if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
724
+ this.LavalinkManager.emit("debug", Constants_1.DebugEvents.PlayerChangeNode, {
725
+ state: "error",
726
+ error: error,
727
+ message: `Player.changeNode() execution failed`,
728
+ functionLayer: "Player > changeNode()",
729
+ });
730
+ }
731
+ throw new Error(`Failed to change the node: ${error}`);
732
+ }
733
+ finally {
734
+ this.set("internal_nodeChanging", undefined);
735
+ }
673
736
  }
674
737
  /** Converts the Player including Queue to a Json state */
675
738
  toJSON() {
@@ -1155,6 +1155,8 @@ export class LavalinkNode {
1155
1155
  }
1156
1156
  /** @private util function for handling trackEnd event */
1157
1157
  async trackEnd(player, track, payload) {
1158
+ if (player.get('internal_nodeChanging') === true)
1159
+ return; // Check if nodeChange is in Progress than stop the trackEnd Event from being triggered.
1158
1160
  const trackToUse = track || this.getTrackOfPayload(payload);
1159
1161
  // If a track was forcibly played
1160
1162
  if (payload.reason === "replaced") {
@@ -1370,6 +1372,8 @@ export class LavalinkNode {
1370
1372
  }
1371
1373
  /** private util function for handling the queue end event */
1372
1374
  async queueEnd(player, track, payload) {
1375
+ if (player.get('internal_nodeChanging') === true)
1376
+ return; // Check if nodeChange is in Progress than stop the queueEnd Event from being triggered.
1373
1377
  // add previous track to the queue!
1374
1378
  player.queue.current = null;
1375
1379
  player.playing = false;
@@ -217,8 +217,9 @@ export declare class Player {
217
217
  /**
218
218
  * Move the player on a different Audio-Node
219
219
  * @param newNode New Node / New Node Id
220
+ * @param checkSources If it should check if the sources are supported by the new node
220
221
  */
221
- changeNode(newNode: LavalinkNode | string): Promise<string>;
222
+ changeNode(newNode: LavalinkNode | string, checkSources?: boolean): Promise<string>;
222
223
  /** Converts the Player including Queue to a Json state */
223
224
  toJSON(): PlayerJson;
224
225
  }
@@ -456,11 +456,11 @@ export class Player {
456
456
  throw new RangeError("Can't skip more than the queue size");
457
457
  await this.queue.splice(0, skipTo - 1);
458
458
  }
459
- if (!this.playing)
459
+ if (!this.playing && !this.queue.current)
460
460
  return (this.play(), this);
461
461
  const now = performance.now();
462
462
  this.set("internal_skipped", true);
463
- await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { track: { encoded: null } } });
463
+ await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { track: { encoded: null }, paused: false } });
464
464
  this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
465
465
  return this;
466
466
  }
@@ -636,11 +636,37 @@ export class Player {
636
636
  /**
637
637
  * Move the player on a different Audio-Node
638
638
  * @param newNode New Node / New Node Id
639
+ * @param checkSources If it should check if the sources are supported by the new node
639
640
  */
640
- async changeNode(newNode) {
641
+ async changeNode(newNode, checkSources = true) {
641
642
  const updateNode = typeof newNode === "string" ? this.LavalinkManager.nodeManager.nodes.get(newNode) : newNode;
642
643
  if (!updateNode)
643
644
  throw new Error("Could not find the new Node");
645
+ if (!updateNode.connected)
646
+ throw new Error("The provided Node is not active or disconnected");
647
+ if (this.node.id === updateNode.id)
648
+ throw new Error("Player is already on the provided Node");
649
+ if (this.get("internal_nodeChanging") === true)
650
+ throw new Error("Player is already changing the node please wait");
651
+ if (checkSources) {
652
+ const isDefaultSource = () => {
653
+ try {
654
+ this.LavalinkManager.utils.validateSourceString(updateNode, this.LavalinkManager.options.playerOptions.defaultSearchPlatform);
655
+ return true;
656
+ }
657
+ catch {
658
+ return false;
659
+ }
660
+ };
661
+ if (!isDefaultSource())
662
+ throw new RangeError(`defaultSearchPlatform "${this.LavalinkManager.options.playerOptions.defaultSearchPlatform}" is not supported by the newNode`);
663
+ if (this.queue.current || this.queue.tracks.length) { // Check if all queued track sources are supported by the new node
664
+ const trackSources = new Set([this.queue.current, ...this.queue.tracks].map(track => track.info.sourceName));
665
+ const missingSources = [...trackSources].filter(source => !updateNode.info.sourceManagers.includes(source));
666
+ if (missingSources.length)
667
+ throw new RangeError(`Sources missing for Node ${updateNode.id}: ${missingSources.join(', ')}`);
668
+ }
669
+ }
644
670
  if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
645
671
  this.LavalinkManager.emit("debug", DebugEvents.PlayerChangeNode, {
646
672
  state: "log",
@@ -650,23 +676,60 @@ export class Player {
650
676
  }
651
677
  const data = this.toJSON();
652
678
  const currentTrack = this.queue.current;
653
- await this.node.destroyPlayer(this.guildId);
679
+ const voiceData = this.voice;
680
+ if (!voiceData.endpoint ||
681
+ !voiceData.sessionId ||
682
+ !voiceData.token)
683
+ throw new Error("Voice Data is missing, can't change the node");
684
+ this.set("internal_nodeChanging", true); // This will stop execution of trackEnd or queueEnd event while changing the node
685
+ if (this.node.connected)
686
+ await this.node.destroyPlayer(this.guildId); // destroy the player on the currentNode if it's connected
654
687
  this.node = updateNode;
655
688
  const now = performance.now();
656
- await this.connect();
657
- await this.node.updatePlayer({
658
- guildId: this.guildId,
659
- noReplace: false,
660
- playerOptions: {
661
- position: data.position,
662
- volume: Math.round(Math.max(Math.min(data.volume, 1000), 0)),
663
- paused: data.paused,
664
- filters: { ...data.filters, equalizer: data.equalizer },
665
- track: currentTrack ?? undefined
666
- },
667
- });
668
- this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
669
- return this.node.id;
689
+ try {
690
+ await this.connect();
691
+ const endpoint = `/sessions/${this.node.sessionId}/players/${this.guildId}`; //Send the VoiceData to the newly connected node.
692
+ await this.node.request(endpoint, r => {
693
+ r.method = "PATCH";
694
+ r.headers["Content-Type"] = "application/json";
695
+ r.body = JSON.stringify({
696
+ voice: {
697
+ token: voiceData.token,
698
+ endpoint: voiceData.endpoint,
699
+ sessionId: voiceData.sessionId
700
+ }
701
+ });
702
+ });
703
+ if (currentTrack) { // If there is a current track, send it to the new node.
704
+ await this.node.updatePlayer({
705
+ guildId: this.guildId,
706
+ noReplace: false,
707
+ playerOptions: {
708
+ track: currentTrack ?? null,
709
+ position: currentTrack ? data.position : 0,
710
+ volume: data.lavalinkVolume,
711
+ paused: data.paused,
712
+ //filters: { ...data.filters, equalizer: data.equalizer }, Sending filters on nodeChange causes issues (player gets dicsonnected)
713
+ }
714
+ });
715
+ }
716
+ this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
717
+ return this.node.id;
718
+ }
719
+ catch (error) {
720
+ if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
721
+ this.LavalinkManager.emit("debug", DebugEvents.PlayerChangeNode, {
722
+ state: "error",
723
+ error: error,
724
+ message: `Player.changeNode() execution failed`,
725
+ functionLayer: "Player > changeNode()",
726
+ });
727
+ }
728
+ throw new Error(`Failed to change the node: ${error}`);
729
+ }
730
+ finally {
731
+ this.set("internal_nodeChanging", undefined);
732
+ }
670
733
  }
671
734
  /** Converts the Player including Queue to a Json state */
672
735
  toJSON() {
@@ -217,8 +217,9 @@ export declare class Player {
217
217
  /**
218
218
  * Move the player on a different Audio-Node
219
219
  * @param newNode New Node / New Node Id
220
+ * @param checkSources If it should check if the sources are supported by the new node
220
221
  */
221
- changeNode(newNode: LavalinkNode | string): Promise<string>;
222
+ changeNode(newNode: LavalinkNode | string, checkSources?: boolean): Promise<string>;
222
223
  /** Converts the Player including Queue to a Json state */
223
224
  toJSON(): PlayerJson;
224
225
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lavalink-client",
3
- "version": "2.4.4",
3
+ "version": "2.4.5",
4
4
  "description": "Easy, flexible and feature-rich lavalink@v4 Client. Both for Beginners and Proficients.",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -58,22 +58,22 @@
58
58
  },
59
59
  "homepage": "https://tomato6966.github.io/lavalink-client/",
60
60
  "devDependencies": {
61
- "@eslint/eslintrc": "^3.1.0",
62
- "@eslint/js": "^9.11.0",
63
- "@types/node": "^22.5.5",
64
- "@types/ws": "^8.5.12",
65
- "@typescript-eslint/eslint-plugin": "^8.6.0",
66
- "@typescript-eslint/parser": "^8.6.0",
67
- "eslint": "^9.11.0",
61
+ "@eslint/eslintrc": "^3.2.0",
62
+ "@eslint/js": "^9.18.0",
63
+ "@types/node": "^22.10.5",
64
+ "@types/ws": "^8.5.13",
65
+ "@typescript-eslint/eslint-plugin": "^8.20.0",
66
+ "@typescript-eslint/parser": "^8.20.0",
67
+ "eslint": "^9.18.0",
68
68
  "tsc-alias": "^1.8.10",
69
- "typescript": "^5.6.2"
69
+ "typescript": "^5.7.3"
70
70
  },
71
71
  "dependencies": {
72
- "tslib": "^2.7.0",
72
+ "tslib": "^2.8.1",
73
73
  "ws": "^8.18.0"
74
74
  },
75
75
  "engines": {
76
76
  "node": ">=18.0.0",
77
- "bun": ">=1.0.0"
77
+ "bun": ">=1.1.27"
78
78
  }
79
79
  }