Skip to content

Commit 9fb2fc7

Browse files
authored
Merge pull request #87 from PandaIN95/main
Fixed: player.changeNode()
2 parents 2d53c44 + b2c6c8a commit 9fb2fc7

File tree

2 files changed

+91
-45
lines changed

2 files changed

+91
-45
lines changed

src/structures/Node.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,6 +1229,7 @@ export class LavalinkNode {
12291229

12301230
/** @private util function for handling trackEnd event */
12311231
private async trackEnd(player: Player, track: Track, payload: TrackEndEvent): Promise<void> {
1232+
if (player.get('internal_nodeChanging') === true) return; // Check if nodeChange is in Progress than stop the trackEnd Event from being triggered.
12321233
const trackToUse = track || this.getTrackOfPayload(payload);
12331234
// If a track was forcibly played
12341235
if (payload.reason === "replaced") {
@@ -1452,6 +1453,7 @@ export class LavalinkNode {
14521453

14531454
/** private util function for handling the queue end event */
14541455
private async queueEnd(player: Player, track: Track, payload: TrackEndEvent | TrackStuckEvent | TrackExceptionEvent): Promise<void> {
1456+
if (player.get('internal_nodeChanging') === true) return; // Check if nodeChange is in Progress than stop the queueEnd Event from being triggered.
14551457
// add previous track to the queue!
14561458
player.queue.current = null;
14571459
player.playing = false;

src/structures/Player.ts

Lines changed: 89 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,12 @@ export class Player {
7979
serverMute: boolean,
8080
suppress: boolean,
8181
} = {
82-
selfDeaf: false,
83-
selfMute: false,
84-
serverDeaf: false,
85-
serverMute: false,
86-
suppress: false,
87-
}
82+
selfDeaf: false,
83+
selfMute: false,
84+
serverDeaf: false,
85+
serverMute: false,
86+
suppress: false,
87+
}
8888

8989
/** Custom data for the player */
9090
private readonly data: Record<string, unknown> = {};
@@ -109,7 +109,7 @@ export class Player {
109109
: this.options.node;
110110

111111
if (!this.node || typeof this.node.request !== "function") {
112-
if(typeof this.options.node === "string" && this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
112+
if (typeof this.options.node === "string" && this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
113113
this.LavalinkManager.emit("debug", DebugEvents.PlayerCreateNodeNotFound, {
114114
state: "warn",
115115
message: `Player was created with provided node Id: ${this.options.node}, but no node with that Id was found.`,
@@ -179,7 +179,7 @@ export class Player {
179179
*/
180180
async play(options: Partial<PlayOptions> = {}) {
181181
if (this.get("internal_queueempty")) {
182-
if(this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
182+
if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
183183
this.LavalinkManager.emit("debug", DebugEvents.PlayerPlayQueueEmptyTimeoutClear, {
184184
state: "log",
185185
message: `Player was called to play something, while there was a queueEmpty Timeout set, clearing the timeout.`,
@@ -198,7 +198,7 @@ export class Player {
198198
// resolve the unresolved track
199199
await (options.clientTrack as UnresolvedTrack).resolve(this);
200200
} catch (error) {
201-
if(this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
201+
if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
202202
this.LavalinkManager.emit("debug", DebugEvents.PlayerPlayUnresolvedTrackFailed, {
203203
state: "error",
204204
error: error,
@@ -245,16 +245,16 @@ export class Player {
245245
identifier: options.track.identifier,
246246
}).filter(v => typeof v[1] !== "undefined")) as LavalinkPlayOptions["track"];
247247

248-
if(typeof options.track.userData === "object") track.userData = {
248+
if (typeof options.track.userData === "object") track.userData = {
249249
...(options.track.userData || {})
250250
};
251251

252-
if(typeof options?.track?.requester === "object") track.userData = {
252+
if (typeof options?.track?.requester === "object") track.userData = {
253253
...(track.userData || {}),
254254
requester: this.LavalinkManager.utils.getTransformedRequester(options?.track?.requester || {}) as anyObject
255255
};
256256

257-
if(this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
257+
if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
258258
this.LavalinkManager.emit("debug", DebugEvents.PlayerPlayWithTrackReplace, {
259259
state: "log",
260260
message: `Player was called to play something, with a specific track provided. Replacing the current Track and resolving the track on trackStart Event.`,
@@ -280,7 +280,7 @@ export class Player {
280280
if (!this.queue.current && this.queue.tracks.length) await queueTrackEnd(this);
281281

282282
if (this.queue.current && this.LavalinkManager.utils.isUnresolvedTrack(this.queue.current)) {
283-
if(this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
283+
if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
284284
this.LavalinkManager.emit("debug", DebugEvents.PlayerPlayUnresolvedTrack, {
285285
state: "log",
286286
message: `Player Play was called, current Queue Song is unresolved, resolving the track.`,
@@ -294,7 +294,7 @@ export class Player {
294294

295295
if (typeof options.track?.userData === "object" && this.queue.current) this.queue.current.userData = { ...(this.queue.current?.userData || {}), ...(options.track?.userData || {}) };
296296
} catch (error) {
297-
if(this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
297+
if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
298298
this.LavalinkManager.emit("debug", DebugEvents.PlayerPlayUnresolvedTrackFailed, {
299299
state: "error",
300300
error: error,
@@ -375,7 +375,7 @@ export class Player {
375375

376376
const now = performance.now();
377377
if (this.LavalinkManager.options.playerOptions.applyVolumeAsFilter) {
378-
if(this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
378+
if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
379379
this.LavalinkManager.emit("debug", DebugEvents.PlayerVolumeAsFilter, {
380380
state: "log",
381381
message: `Player Volume was set as a Filter, because LavalinkManager option "playerOptions.applyVolumeAsFilter" is true`,
@@ -427,7 +427,7 @@ export class Player {
427427
const Query = this.LavalinkManager.utils.transformQuery(query);
428428

429429
if (["bcsearch", "bandcamp"].includes(Query.source) && !this.node.info.sourceManagers.includes("bandcamp")) {
430-
if(this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
430+
if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
431431
this.LavalinkManager.emit("debug", DebugEvents.BandcampSearchLokalEngine, {
432432
state: "log",
433433
message: `Player.search was called with a Bandcamp Query, but no bandcamp search was enabled on lavalink, searching with the custom Search Engine.`,
@@ -512,11 +512,10 @@ export class Player {
512512
await this.queue.splice(0, skipTo - 1);
513513
}
514514

515-
if (!this.playing) return (this.play(), this);
516-
515+
if (!this.playing && !this.queue.current) return (this.play(), this);
517516
const now = performance.now();
518517
this.set("internal_skipped", true);
519-
await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { track: { encoded: null } } });
518+
await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { track: { encoded: null }, paused: false } });
520519

521520
this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
522521

@@ -628,7 +627,7 @@ export class Player {
628627

629628
if (this.get("internal_destroystatus") === true) {
630629

631-
if(this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
630+
if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
632631
this.LavalinkManager.emit("debug", DebugEvents.PlayerDestroyingSomewhereElse, {
633632
state: "warn",
634633
message: `Player is already destroying somewhere else..`,
@@ -715,12 +714,31 @@ export class Player {
715714
/**
716715
* Move the player on a different Audio-Node
717716
* @param newNode New Node / New Node Id
717+
* @param checkSources If it should check if the sources are supported by the new node
718718
*/
719-
public async changeNode(newNode: LavalinkNode | string) {
719+
public async changeNode(newNode: LavalinkNode | string, checkSources: boolean = true) {
720720
const updateNode = typeof newNode === "string" ? this.LavalinkManager.nodeManager.nodes.get(newNode) : newNode;
721721
if (!updateNode) throw new Error("Could not find the new Node");
722+
if (!updateNode.connected) throw new Error("The provided Node is not active or disconnected");
723+
if (this.node.id === updateNode.id) throw new Error("Player is already on the provided Node");
724+
if (this.get("internal_nodeChanging") === true) throw new Error("Player is already changing the node please wait");
725+
if (checkSources) {
726+
const isDefaultSource = (): boolean => { // check if defaultSearchPlatform is enabled on newNode
727+
try {
728+
this.LavalinkManager.utils.validateSourceString(updateNode, this.LavalinkManager.options.playerOptions.defaultSearchPlatform);
729+
return true;
730+
} catch { return false }
731+
};
732+
if (!isDefaultSource()) throw new RangeError(`defaultSearchPlatform "${this.LavalinkManager.options.playerOptions.defaultSearchPlatform}" is not supported by the newNode`);
733+
if (this.queue.current || this.queue.tracks.length) { // Check if all queued track sources are supported by the new node
734+
const trackSources = new Set([this.queue.current, ...this.queue.tracks].map(track => track.info.sourceName));
735+
const missingSources = [...trackSources].filter(
736+
source => !updateNode.info.sourceManagers.includes(source));
737+
if (missingSources.length)
738+
throw new RangeError(`Sources missing for Node ${updateNode.id}: ${missingSources.join(', ')}`)}
739+
}
722740

723-
if(this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
741+
if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
724742
this.LavalinkManager.emit("debug", DebugEvents.PlayerChangeNode, {
725743
state: "log",
726744
message: `Player.changeNode() was executed, trying to change from "${this.node.id}" to "${updateNode.id}"`,
@@ -729,32 +747,58 @@ export class Player {
729747
}
730748

731749
const data = this.toJSON();
732-
733750
const currentTrack = this.queue.current;
734-
735-
await this.node.destroyPlayer(this.guildId);
736-
751+
const voiceData = this.voice;
752+
if (!voiceData.endpoint ||
753+
!voiceData.sessionId ||
754+
!voiceData.token)
755+
throw new Error("Voice Data is missing, can't change the node");
756+
this.set("internal_nodeChanging", true); // This will stop execution of trackEnd or queueEnd event while changing the node
757+
if (this.node.connected) await this.node.destroyPlayer(this.guildId); // destroy the player on the currentNode if it's connected
737758
this.node = updateNode;
738-
739759
const now = performance.now();
740-
741-
await this.connect();
742-
743-
await this.node.updatePlayer({
744-
guildId: this.guildId,
745-
noReplace: false,
746-
playerOptions: {
747-
position: data.position,
748-
volume: Math.round(Math.max(Math.min(data.volume, 1000), 0)),
749-
paused: data.paused,
750-
filters: { ...data.filters, equalizer: data.equalizer },
751-
track: currentTrack ?? undefined
752-
},
753-
});
754-
755-
this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
756-
757-
return this.node.id;
760+
try {
761+
await this.connect();
762+
const endpoint = `/sessions/${this.node.sessionId}/players/${this.guildId}`; //Send the VoiceData to the newly connected node.
763+
await this.node.request(endpoint, r => {
764+
r.method = "PATCH";
765+
r.headers["Content-Type"] = "application/json";
766+
r.body = JSON.stringify({
767+
voice: {
768+
token: voiceData.token,
769+
endpoint: voiceData.endpoint,
770+
sessionId: voiceData.sessionId
771+
}
772+
});
773+
});
774+
if (currentTrack) { // If there is a current track, send it to the new node.
775+
await this.node.updatePlayer({
776+
guildId: this.guildId,
777+
noReplace: false,
778+
playerOptions: {
779+
track: currentTrack ?? null,
780+
position: currentTrack ? data.position : 0,
781+
volume: data.lavalinkVolume,
782+
paused: data.paused,
783+
//filters: { ...data.filters, equalizer: data.equalizer }, Sending filters on nodeChange causes issues (player gets dicsonnected)
784+
}
785+
});
786+
}
787+
this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
788+
return this.node.id;
789+
} catch (error) {
790+
if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
791+
this.LavalinkManager.emit("debug", DebugEvents.PlayerChangeNode, {
792+
state: "error",
793+
error: error,
794+
message: `Player.changeNode() execution failed`,
795+
functionLayer: "Player > changeNode()",
796+
});
797+
}
798+
throw new Error(`Failed to change the node: ${error}`);
799+
} finally {
800+
this.set("internal_nodeChanging", undefined);
801+
}
758802
}
759803

760804
/** Converts the Player including Queue to a Json state */

0 commit comments

Comments
 (0)