@@ -79,12 +79,12 @@ export class Player {
79
79
serverMute : boolean ,
80
80
suppress : boolean ,
81
81
} = {
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
+ }
88
88
89
89
/** Custom data for the player */
90
90
private readonly data : Record < string , unknown > = { } ;
@@ -109,7 +109,7 @@ export class Player {
109
109
: this . options . node ;
110
110
111
111
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 ) {
113
113
this . LavalinkManager . emit ( "debug" , DebugEvents . PlayerCreateNodeNotFound , {
114
114
state : "warn" ,
115
115
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 {
179
179
*/
180
180
async play ( options : Partial < PlayOptions > = { } ) {
181
181
if ( this . get ( "internal_queueempty" ) ) {
182
- if ( this . LavalinkManager . options ?. advancedOptions ?. enableDebugEvents ) {
182
+ if ( this . LavalinkManager . options ?. advancedOptions ?. enableDebugEvents ) {
183
183
this . LavalinkManager . emit ( "debug" , DebugEvents . PlayerPlayQueueEmptyTimeoutClear , {
184
184
state : "log" ,
185
185
message : `Player was called to play something, while there was a queueEmpty Timeout set, clearing the timeout.` ,
@@ -198,7 +198,7 @@ export class Player {
198
198
// resolve the unresolved track
199
199
await ( options . clientTrack as UnresolvedTrack ) . resolve ( this ) ;
200
200
} catch ( error ) {
201
- if ( this . LavalinkManager . options ?. advancedOptions ?. enableDebugEvents ) {
201
+ if ( this . LavalinkManager . options ?. advancedOptions ?. enableDebugEvents ) {
202
202
this . LavalinkManager . emit ( "debug" , DebugEvents . PlayerPlayUnresolvedTrackFailed , {
203
203
state : "error" ,
204
204
error : error ,
@@ -245,16 +245,16 @@ export class Player {
245
245
identifier : options . track . identifier ,
246
246
} ) . filter ( v => typeof v [ 1 ] !== "undefined" ) ) as LavalinkPlayOptions [ "track" ] ;
247
247
248
- if ( typeof options . track . userData === "object" ) track . userData = {
248
+ if ( typeof options . track . userData === "object" ) track . userData = {
249
249
...( options . track . userData || { } )
250
250
} ;
251
251
252
- if ( typeof options ?. track ?. requester === "object" ) track . userData = {
252
+ if ( typeof options ?. track ?. requester === "object" ) track . userData = {
253
253
...( track . userData || { } ) ,
254
254
requester : this . LavalinkManager . utils . getTransformedRequester ( options ?. track ?. requester || { } ) as anyObject
255
255
} ;
256
256
257
- if ( this . LavalinkManager . options ?. advancedOptions ?. enableDebugEvents ) {
257
+ if ( this . LavalinkManager . options ?. advancedOptions ?. enableDebugEvents ) {
258
258
this . LavalinkManager . emit ( "debug" , DebugEvents . PlayerPlayWithTrackReplace , {
259
259
state : "log" ,
260
260
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 {
280
280
if ( ! this . queue . current && this . queue . tracks . length ) await queueTrackEnd ( this ) ;
281
281
282
282
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 ) {
284
284
this . LavalinkManager . emit ( "debug" , DebugEvents . PlayerPlayUnresolvedTrack , {
285
285
state : "log" ,
286
286
message : `Player Play was called, current Queue Song is unresolved, resolving the track.` ,
@@ -294,7 +294,7 @@ export class Player {
294
294
295
295
if ( typeof options . track ?. userData === "object" && this . queue . current ) this . queue . current . userData = { ...( this . queue . current ?. userData || { } ) , ...( options . track ?. userData || { } ) } ;
296
296
} catch ( error ) {
297
- if ( this . LavalinkManager . options ?. advancedOptions ?. enableDebugEvents ) {
297
+ if ( this . LavalinkManager . options ?. advancedOptions ?. enableDebugEvents ) {
298
298
this . LavalinkManager . emit ( "debug" , DebugEvents . PlayerPlayUnresolvedTrackFailed , {
299
299
state : "error" ,
300
300
error : error ,
@@ -375,7 +375,7 @@ export class Player {
375
375
376
376
const now = performance . now ( ) ;
377
377
if ( this . LavalinkManager . options . playerOptions . applyVolumeAsFilter ) {
378
- if ( this . LavalinkManager . options ?. advancedOptions ?. enableDebugEvents ) {
378
+ if ( this . LavalinkManager . options ?. advancedOptions ?. enableDebugEvents ) {
379
379
this . LavalinkManager . emit ( "debug" , DebugEvents . PlayerVolumeAsFilter , {
380
380
state : "log" ,
381
381
message : `Player Volume was set as a Filter, because LavalinkManager option "playerOptions.applyVolumeAsFilter" is true` ,
@@ -427,7 +427,7 @@ export class Player {
427
427
const Query = this . LavalinkManager . utils . transformQuery ( query ) ;
428
428
429
429
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 ) {
431
431
this . LavalinkManager . emit ( "debug" , DebugEvents . BandcampSearchLokalEngine , {
432
432
state : "log" ,
433
433
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 {
512
512
await this . queue . splice ( 0 , skipTo - 1 ) ;
513
513
}
514
514
515
- if ( ! this . playing ) return ( this . play ( ) , this ) ;
516
-
515
+ if ( ! this . playing && ! this . queue . current ) return ( this . play ( ) , this ) ;
517
516
const now = performance . now ( ) ;
518
517
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 } } ) ;
520
519
521
520
this . ping . lavalink = Math . round ( ( performance . now ( ) - now ) / 10 ) / 100 ;
522
521
@@ -628,7 +627,7 @@ export class Player {
628
627
629
628
if ( this . get ( "internal_destroystatus" ) === true ) {
630
629
631
- if ( this . LavalinkManager . options ?. advancedOptions ?. enableDebugEvents ) {
630
+ if ( this . LavalinkManager . options ?. advancedOptions ?. enableDebugEvents ) {
632
631
this . LavalinkManager . emit ( "debug" , DebugEvents . PlayerDestroyingSomewhereElse , {
633
632
state : "warn" ,
634
633
message : `Player is already destroying somewhere else..` ,
@@ -715,12 +714,31 @@ export class Player {
715
714
/**
716
715
* Move the player on a different Audio-Node
717
716
* @param newNode New Node / New Node Id
717
+ * @param checkSources If it should check if the sources are supported by the new node
718
718
*/
719
- public async changeNode ( newNode : LavalinkNode | string ) {
719
+ public async changeNode ( newNode : LavalinkNode | string , checkSources : boolean = true ) {
720
720
const updateNode = typeof newNode === "string" ? this . LavalinkManager . nodeManager . nodes . get ( newNode ) : newNode ;
721
721
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
+ }
722
740
723
- if ( this . LavalinkManager . options ?. advancedOptions ?. enableDebugEvents ) {
741
+ if ( this . LavalinkManager . options ?. advancedOptions ?. enableDebugEvents ) {
724
742
this . LavalinkManager . emit ( "debug" , DebugEvents . PlayerChangeNode , {
725
743
state : "log" ,
726
744
message : `Player.changeNode() was executed, trying to change from "${ this . node . id } " to "${ updateNode . id } "` ,
@@ -729,32 +747,58 @@ export class Player {
729
747
}
730
748
731
749
const data = this . toJSON ( ) ;
732
-
733
750
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
737
758
this . node = updateNode ;
738
-
739
759
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
+ }
758
802
}
759
803
760
804
/** Converts the Player including Queue to a Json state */
0 commit comments