Skip to content

some fixes#174

Open
rly-dev wants to merge 9 commits intoTomato6966:mainfrom
rly-dev:main
Open

some fixes#174
rly-dev wants to merge 9 commits intoTomato6966:mainfrom
rly-dev:main

Conversation

@rly-dev
Copy link

@rly-dev rly-dev commented Feb 14, 2026

In Queue.ts there are two fixes

  1. the sync() uses wrong array length for previous
  • in Queue.utils.sync() when overriding the previous array with data from the queue store the splice call used is this.tracks.length as the delete count instead of this.previous.length
  1. missing null-safety on optional queueOptions in constructor
  • the constructor parameter queueOptions is typed as queueOptions?: ManagerQueueOptions (optional) but line 153 accessed it as queueOptions.queueChangesWatcher without optional chaining

In Node.ts there are two fixes

  1. race condition on error() handler
  • when a WebSocket error occurred with closeOnError enabled the handler called this.reconnect() first then called this.socket?.close() the close() call fires a close event on the socket which triggers the close handler and that handler also calls reconnect()
  1. off-bye-one in error rate-limiting ( two location )
  • both trackStuck() and trackError() filter old timestamps push the current timestamp then check if (oldTimestamps.length > maxAmount) but oldTimestamps was captured before the current error was added to the stored array so it doesn't include the current error

In Filters.ts two fixes

  1. resetFilters() karaoke resets twice but vaporwave doesnt reset
  • in resetFilters()two consecutive lines both set this.filters.karaoke = false the second line was clearly meant to be this.filters.vaporwave = false
  1. setSpeed / setPitch / setRate overwrite each others values
  • each of these three methods replaced the entire this.data.timescale object by spreading DEFAULT_FILTER_DATAS.timescale (which has speed: 1, pitch: 1, rate: 1) and then overriding only their own property for example setSpeed(2) produces { speed: 2, pitch: 1, rate: 1 }

In Player.ts one fix

  1. changeNode() enables SponsorBlock on node change default
  • in changeNode() when the new node supports SponsorBlock, the code checked if sponsorBlockCategories was a non-empty array if it was it re-applied those categories (correct) but the else branch called setSponsorBlock() with no arguments which applies default categories

Commit History and Details
dc406be Fix Queue sync & constructor
56799fe Fix Node reconnect & ratelimit
7d2cdd6 Fix Filters reset & timescale
19ee6aa Fix Player SponsorBlock default

… constructor

- sync(): Used this.tracks.length instead of this.previous.length when
  splicing the previous array, causing incomplete overrides that corrupt
  queue state on sync.
- constructor: queueOptions parameter is optional but was accessed
  without optional chaining, causing TypeError when omitted.
…r rate-limiting

- error(): reconnect() was called before socket.close(), causing
  duplicate reconnection attempts. Now reconnect only runs when
  closeOnError is false; otherwise the close event handles it.
- trackStuck()/trackError(): Used > instead of >= in rate-limiting,
  allowing one extra error beyond the configured maxAmount before
  destroying the player.
…setSpeed/setPitch/setRate

- resetFilters(): Duplicate karaoke reset instead of resetting
  vaporwave, leaving the vaporwave flag active after a full reset.
- setSpeed/setPitch/setRate: Each method replaced the entire timescale
  object from defaults, discarding prior customizations from the other
  methods. Now preserves existing timescale state.
- changeNode(): The else branch unconditionally called setSponsorBlock()
  with default categories when moving to a new node, even if the user
  never enabled SponsorBlock. Now only re-applies if categories were
  previously set.
Copy link
Owner

@Tomato6966 Tomato6966 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a left a few comments. first up: Thanks for the bug fixes, please implement my requested changes.

this.filters.tremolo = false;
this.filters.vibrato = false;
this.filters.karaoke = false;
this.filters.karaoke = false;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, i missed that.


this.filters.nightcore = false;
this.filters.vaporwave = false;
this.data.timescale = { ...DEFAULT_FILTER_DATAS.timescale, speed };
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good thing. could you add that it first spreads the deafult data, then the pre-applied data, and then the speed? or just map the 3 vaslues...


this.filters.nightcore = false;
this.filters.vaporwave = false;
this.data.timescale = { ...DEFAULT_FILTER_DATAS.timescale, pitch };
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good thing. could you add that it first spreads the deafult data, then the pre-applied data, and then the speed? or just map the 3 vaslues...


this.filters.nightcore = false;
this.filters.vaporwave = false;
this.data.timescale = { ...DEFAULT_FILTER_DATAS.timescale, rate };
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good thing. could you add that it first spreads the deafult data, then the pre-applied data, and then the speed? or just map the 3 vaslues...

if (this.heartBeatInterval) clearInterval(this.heartBeatInterval);
if (this.pingTimeout) clearTimeout(this.pingTimeout);
this.socket?.close(500, "Node-Error - Force Reconnect");
} else {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of else, do a return in the if () above.

);
player.set("internal_erroredTracksTimestamps", [...oldTimestamps, Date.now()]);
if (oldTimestamps.length > this._LManager.options.playerOptions.maxErrorsPerTime?.maxAmount) {
if (oldTimestamps.length >= this._LManager.options.playerOptions.maxErrorsPerTime?.maxAmount) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you.
since we are comparing old data and not new data >= is correct.

functionLayer: "Player > changeNode()",
});
});
} else {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this block removed?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the else branch called setSponsorBlock() with no arguments which applies default sponsor-block categories this means when a player changes nodes even if the user never enabled SponsorBlock they'd suddenly get default filtering enabled silently skipping track segments without opting in by removing the else SponsorBlock is only re-applied on the new node if the user had previously set specific categories via setSponsorBlock(categories)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rly-dev the else block was there so that the ChapterStarted and ChapterLoaded event will work from the SponsorBlock plugin on the newNode (I didn't investigate why this was the case that's why I left that there).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the context reverted that change in f525b91 — the else block is restored so SponsorBlock events register properly on the new node

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i were correct about the surface-level behavior (it does set defaults) but wrong about the intent (it's a necessary side effect for plugin event registration not an accidental opt-in)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also i have added comments on line 953-954 in 30a5118

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can implement an option in the parent function.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"enforceSponsorBlockRequestForEventEnablement"

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added enforceSponsorBlockRequestForEventEnablement as a configurable option in ManagerPlayerOptions (commit b404f71)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you approve this then i would gladly update in the docs LavalinkManager

)
this.previous.splice(
0,
override ? this.tracks.length : 0,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dang, how did i miss that

@rly-dev
Copy link
Author

rly-dev commented Feb 14, 2026

sure thing i would do it in few mins

- Filters.ts: spread defaults first, then existing timescale, then new
  value in setSpeed/setPitch/setRate for guaranteed key coverage.
- Node.ts: use early return in closeOnError block instead of if/else.
@rly-dev
Copy link
Author

rly-dev commented Feb 14, 2026

refactor updated the code and committed (4e3d867)

The else block is intentional - setSponsorBlock() with defaults is
needed so the SponsorBlock plugin registers ChapterStarted/ChapterLoaded
event listeners on the new node.
Clarifies that setSponsorBlock() with defaults is needed on node change
so the SponsorBlock plugin registers ChapterStarted/ChapterLoaded event
listeners on the new node.
Adds a configurable option to control whether setSponsorBlock() with
defaults is called on node change for SponsorBlock event registration.
Defaults to true to preserve existing behavior. Set to false to disable
if you don't use SponsorBlock events.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants