yukimu 1.3.0 → 2.0.0
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/BitrateOptimizer.d.ts +52 -0
- package/dist/BitrateOptimizer.d.ts.map +1 -0
- package/dist/BitrateOptimizer.js +115 -0
- package/dist/BitrateOptimizer.js.map +1 -0
- package/dist/ConnectionPool.d.ts +52 -3
- package/dist/ConnectionPool.d.ts.map +1 -1
- package/dist/ConnectionPool.js +124 -32
- package/dist/ConnectionPool.js.map +1 -1
- package/dist/Constants.d.ts +27 -2
- package/dist/Constants.d.ts.map +1 -1
- package/dist/Constants.js +133 -11
- package/dist/Constants.js.map +1 -1
- package/dist/Logger.d.ts +22 -0
- package/dist/Logger.d.ts.map +1 -0
- package/dist/Logger.js +74 -0
- package/dist/Logger.js.map +1 -0
- package/dist/Node.d.ts +17 -4
- package/dist/Node.d.ts.map +1 -1
- package/dist/Node.js +226 -88
- package/dist/Node.js.map +1 -1
- package/dist/Player.d.ts +74 -7
- package/dist/Player.d.ts.map +1 -1
- package/dist/Player.js +317 -92
- package/dist/Player.js.map +1 -1
- package/dist/Plugin.d.ts +19 -1
- package/dist/Plugin.d.ts.map +1 -1
- package/dist/Plugin.js +8 -0
- package/dist/Plugin.js.map +1 -1
- package/dist/Queue.d.ts +69 -2
- package/dist/Queue.d.ts.map +1 -1
- package/dist/Queue.js +138 -17
- package/dist/Queue.js.map +1 -1
- package/dist/Resolver.d.ts +33 -2
- package/dist/Resolver.d.ts.map +1 -1
- package/dist/Resolver.js +225 -33
- package/dist/Resolver.js.map +1 -1
- package/dist/Rest.d.ts +27 -4
- package/dist/Rest.d.ts.map +1 -1
- package/dist/Rest.js +157 -25
- package/dist/Rest.js.map +1 -1
- package/dist/TrackCache.d.ts +30 -5
- package/dist/TrackCache.d.ts.map +1 -1
- package/dist/TrackCache.js +119 -15
- package/dist/TrackCache.js.map +1 -1
- package/dist/WsQueue.d.ts +31 -1
- package/dist/WsQueue.d.ts.map +1 -1
- package/dist/WsQueue.js +70 -12
- package/dist/WsQueue.js.map +1 -1
- package/dist/Yukimu.d.ts +38 -5
- package/dist/Yukimu.d.ts.map +1 -1
- package/dist/Yukimu.js +150 -57
- package/dist/Yukimu.js.map +1 -1
- package/dist/connector/Connector.d.ts +26 -0
- package/dist/connector/Connector.d.ts.map +1 -1
- package/dist/connector/Connector.js +28 -0
- package/dist/connector/Connector.js.map +1 -1
- package/dist/connector/DiscordJS.d.ts +20 -1
- package/dist/connector/DiscordJS.d.ts.map +1 -1
- package/dist/connector/DiscordJS.js +44 -2
- package/dist/connector/DiscordJS.js.map +1 -1
- package/dist/connector/Eris.d.ts +12 -1
- package/dist/connector/Eris.d.ts.map +1 -1
- package/dist/connector/Eris.js +40 -6
- package/dist/connector/Eris.js.map +1 -1
- package/dist/connector/Oceanic.d.ts +12 -1
- package/dist/connector/Oceanic.d.ts.map +1 -1
- package/dist/connector/Oceanic.js +39 -4
- package/dist/connector/Oceanic.js.map +1 -1
- package/dist/errors/YukimuError.d.ts +40 -4
- package/dist/errors/YukimuError.d.ts.map +1 -1
- package/dist/errors/YukimuError.js +79 -8
- package/dist/errors/YukimuError.js.map +1 -1
- package/dist/index.d.ts +13 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -16
- package/dist/index.js.map +1 -1
- package/dist/plugins/AutoResume.d.ts +18 -1
- package/dist/plugins/AutoResume.d.ts.map +1 -1
- package/dist/plugins/AutoResume.js +109 -21
- package/dist/plugins/AutoResume.js.map +1 -1
- package/dist/plugins/AutoplayPlugin.d.ts +35 -0
- package/dist/plugins/AutoplayPlugin.d.ts.map +1 -0
- package/dist/plugins/AutoplayPlugin.js +111 -0
- package/dist/plugins/AutoplayPlugin.js.map +1 -0
- package/dist/plugins/InactivityPlugin.d.ts +30 -0
- package/dist/plugins/InactivityPlugin.d.ts.map +1 -0
- package/dist/plugins/InactivityPlugin.js +86 -0
- package/dist/plugins/InactivityPlugin.js.map +1 -0
- package/dist/plugins/PlayerMoved.d.ts +23 -1
- package/dist/plugins/PlayerMoved.d.ts.map +1 -1
- package/dist/plugins/PlayerMoved.js +57 -12
- package/dist/plugins/PlayerMoved.js.map +1 -1
- package/dist/types.d.ts +198 -71
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/package.json +49 -5
- package/.cache/replit/env/latest +0 -88
- package/.cache/replit/env/latest.json +0 -1
- package/.cache/replit/modules/nodejs-20.res +0 -1
- package/.cache/replit/modules/python-3.11.res +0 -1
- package/.cache/replit/modules/replit.res +0 -1
- package/.cache/replit/modules.stamp +0 -0
- package/.cache/replit/nix/dotreplitenv.json +0 -1
- package/.cache/replit/toolchain.json +0 -1
- package/.local/state/workflow-logs/7zVU0iVo-fBL1ccMCmELy/configure_your_app.packager.installForAll.0 +0 -9
- package/.local/state/workflow-logs/7zVU0iVo-fBL1ccMCmELy/configure_your_app.shell.exec.1 +0 -1
- package/.local/state/workflow-logs/KRgHXizaECjWI5nWtS7Dj/configure_your_app.packager.installForAll.0 +0 -1
- package/.local/state/workflow-logs/KRgHXizaECjWI5nWtS7Dj/configure_your_app.shell.exec.1 +0 -1
- package/.local/state/workflow-logs/U0AinJQVHonnwGjj0RXLn/configure_your_app.packager.installForAll.0 +0 -2
- package/.local/state/workflow-logs/jVavLOnv1MqxUvxhMmqER/configure_your_app.packager.installForAll.0 +0 -1
- package/.local/state/workflow-logs/jVavLOnv1MqxUvxhMmqER/configure_your_app.shell.exec.1 +0 -1
- package/.replit +0 -7
- package/.upm/store.json +0 -1
- package/src/ConnectionPool.ts +0 -114
- package/src/Constants.ts +0 -45
- package/src/Node.ts +0 -302
- package/src/Player.ts +0 -332
- package/src/Plugin.ts +0 -7
- package/src/Queue.ts +0 -66
- package/src/Resolver.ts +0 -90
- package/src/Rest.ts +0 -127
- package/src/TrackCache.ts +0 -46
- package/src/WsQueue.ts +0 -58
- package/src/Yukimu.ts +0 -213
- package/src/connector/Connector.ts +0 -13
- package/src/connector/DiscordJS.ts +0 -26
- package/src/connector/Eris.ts +0 -24
- package/src/connector/Oceanic.ts +0 -22
- package/src/errors/YukimuError.ts +0 -31
- package/src/index.ts +0 -24
- package/src/plugins/AutoResume.ts +0 -37
- package/src/plugins/PlayerMoved.ts +0 -26
- package/src/types.ts +0 -145
- package/tsconfig.json +0 -22
package/dist/Player.js
CHANGED
|
@@ -1,24 +1,47 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/** Yukimu v2.0.0 — Player with State Safety & Bitrate Optimization */
|
|
2
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
4
|
exports.Player = void 0;
|
|
4
5
|
const Queue_1 = require("./Queue");
|
|
6
|
+
const BitrateOptimizer_1 = require("./BitrateOptimizer");
|
|
5
7
|
const Constants_1 = require("./Constants");
|
|
6
8
|
const YukimuError_1 = require("./errors/YukimuError");
|
|
9
|
+
/**
|
|
10
|
+
* Represents an audio player for a specific Discord guild.
|
|
11
|
+
*
|
|
12
|
+
* Critical fixes from v1:
|
|
13
|
+
* 1. play() — only sets playing=true AFTER the REST call succeeds
|
|
14
|
+
* 2. pause() — paused tracks are still "playing" from Lavalink's perspective
|
|
15
|
+
* 3. moveToNode() — properly re-sends voice state to the new node
|
|
16
|
+
* 4. sendVoicePayload() — deferred until connector is ready (safe for startup)
|
|
17
|
+
* 5. Proper cleanup in destroy() — all state, timers, and references
|
|
18
|
+
* 6. Input validation on filter values
|
|
19
|
+
* 7. Boost-tier-aware audio optimization
|
|
20
|
+
*/
|
|
7
21
|
class Player {
|
|
22
|
+
manager;
|
|
23
|
+
node;
|
|
24
|
+
queue;
|
|
25
|
+
guildId;
|
|
26
|
+
voiceChannelId;
|
|
27
|
+
textChannelId;
|
|
28
|
+
state = Constants_1.PlayerState.DISCONNECTED;
|
|
29
|
+
playing = false;
|
|
30
|
+
paused = false;
|
|
31
|
+
position = 0;
|
|
32
|
+
ping = -1;
|
|
33
|
+
lastUpdated = Date.now();
|
|
34
|
+
volume;
|
|
35
|
+
loop = "none";
|
|
36
|
+
autoplay = false;
|
|
37
|
+
sessionId = null;
|
|
38
|
+
voiceToken = null;
|
|
39
|
+
voiceEndpoint = null;
|
|
40
|
+
selfDeaf;
|
|
41
|
+
selfMute;
|
|
42
|
+
filters = {};
|
|
43
|
+
data = new Map();
|
|
8
44
|
constructor(manager, node, options) {
|
|
9
|
-
this.state = Constants_1.PlayerState.DISCONNECTED;
|
|
10
|
-
this.playing = false;
|
|
11
|
-
this.paused = false;
|
|
12
|
-
this.position = 0;
|
|
13
|
-
this.ping = -1;
|
|
14
|
-
this.lastUpdated = Date.now();
|
|
15
|
-
this.loop = "none";
|
|
16
|
-
this.autoplay = false;
|
|
17
|
-
this.sessionId = null;
|
|
18
|
-
this.voiceToken = null;
|
|
19
|
-
this.voiceEndpoint = null;
|
|
20
|
-
this.filters = {};
|
|
21
|
-
this.data = new Map();
|
|
22
45
|
this.manager = manager;
|
|
23
46
|
this.node = node;
|
|
24
47
|
this.queue = new Queue_1.Queue();
|
|
@@ -28,21 +51,46 @@ class Player {
|
|
|
28
51
|
this.volume = options.volume ?? 100;
|
|
29
52
|
this.selfDeaf = options.selfDeaf ?? true;
|
|
30
53
|
this.selfMute = options.selfMute ?? false;
|
|
54
|
+
// Send the voice state update to Discord (join the channel)
|
|
55
|
+
// This is safe — connector.sendPayload handles missing guilds gracefully
|
|
31
56
|
this.sendVoicePayload(options.voiceChannelId, this.selfDeaf, this.selfMute);
|
|
32
57
|
this.state = Constants_1.PlayerState.CONNECTING;
|
|
33
58
|
}
|
|
34
|
-
|
|
59
|
+
// ─── Getters ────────────────────────────────────────────────────────────
|
|
60
|
+
get connected() {
|
|
61
|
+
return this.state === Constants_1.PlayerState.CONNECTED;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Estimated real-time position accounting for time since last update.
|
|
65
|
+
* Returns the actual position (capped at track length).
|
|
66
|
+
*/
|
|
35
67
|
get realPosition() {
|
|
36
68
|
if (!this.playing || this.paused)
|
|
37
69
|
return this.position;
|
|
38
|
-
|
|
70
|
+
const trackLength = this.queue.current?.info?.length ?? Infinity;
|
|
71
|
+
return Math.min(this.position + (Date.now() - this.lastUpdated), trackLength);
|
|
39
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Get the current boost tier of the guild this player is in.
|
|
75
|
+
*/
|
|
76
|
+
get boostTier() {
|
|
77
|
+
return this.manager.connector.getGuildBoostTier(this.guildId);
|
|
78
|
+
}
|
|
79
|
+
// ─── Voice ──────────────────────────────────────────────────────────────
|
|
40
80
|
sendVoicePayload(channelId, selfDeaf, selfMute) {
|
|
41
81
|
this.manager.connector.sendPayload(this.guildId, {
|
|
42
82
|
op: 4,
|
|
43
|
-
d: {
|
|
83
|
+
d: {
|
|
84
|
+
guild_id: this.guildId,
|
|
85
|
+
channel_id: channelId,
|
|
86
|
+
self_deaf: selfDeaf,
|
|
87
|
+
self_mute: selfMute,
|
|
88
|
+
},
|
|
44
89
|
});
|
|
45
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Check if we have all voice connection data and send it to Lavalink.
|
|
93
|
+
*/
|
|
46
94
|
checkVoiceReady() {
|
|
47
95
|
if (!this.sessionId || !this.voiceToken || !this.voiceEndpoint)
|
|
48
96
|
return;
|
|
@@ -53,22 +101,38 @@ class Player {
|
|
|
53
101
|
channelId: this.voiceChannelId,
|
|
54
102
|
};
|
|
55
103
|
if (this.node.version === 4) {
|
|
56
|
-
this.node.rest.updatePlayer(this.guildId, { voice: voiceState }).catch(
|
|
104
|
+
this.node.rest.updatePlayer(this.guildId, { voice: voiceState }).catch((err) => {
|
|
105
|
+
this.manager.logger.error(`Failed to send voice update for guild ${this.guildId}: ${err}`);
|
|
106
|
+
});
|
|
57
107
|
}
|
|
58
108
|
else {
|
|
59
109
|
this.node.send({
|
|
60
110
|
op: "voiceUpdate",
|
|
61
111
|
guildId: this.guildId,
|
|
62
112
|
sessionId: this.sessionId,
|
|
63
|
-
event: {
|
|
113
|
+
event: {
|
|
114
|
+
token: this.voiceToken,
|
|
115
|
+
guild_id: this.guildId,
|
|
116
|
+
endpoint: this.voiceEndpoint,
|
|
117
|
+
},
|
|
118
|
+
}).catch((err) => {
|
|
119
|
+
this.manager.logger.error(`Failed to send voice update for guild ${this.guildId}: ${err}`);
|
|
64
120
|
});
|
|
65
121
|
}
|
|
66
122
|
this.state = Constants_1.PlayerState.CONNECTED;
|
|
67
123
|
}
|
|
124
|
+
// ─── Playback ───────────────────────────────────────────────────────────
|
|
125
|
+
/**
|
|
126
|
+
* Play a track. If no track is provided, plays the current queue track.
|
|
127
|
+
*
|
|
128
|
+
* FIX: Only sets `playing = true` after the REST call succeeds.
|
|
129
|
+
* In v1, state was set before the REST call, causing ghost playing state on failure.
|
|
130
|
+
*/
|
|
68
131
|
async play(track, options) {
|
|
69
132
|
const toPlay = track ?? this.queue.current;
|
|
70
|
-
if (!toPlay)
|
|
71
|
-
throw new YukimuError_1.PlayerError("No track to play");
|
|
133
|
+
if (!toPlay) {
|
|
134
|
+
throw new YukimuError_1.PlayerError("No track to play", YukimuError_1.ErrorCode.PLAYER_NO_TRACK);
|
|
135
|
+
}
|
|
72
136
|
this.queue.current = toPlay;
|
|
73
137
|
if (this.node.version === 4) {
|
|
74
138
|
await this.node.rest.updatePlayer(this.guildId, {
|
|
@@ -79,7 +143,7 @@ class Player {
|
|
|
79
143
|
}, options?.noReplace ?? false);
|
|
80
144
|
}
|
|
81
145
|
else {
|
|
82
|
-
this.node.send({
|
|
146
|
+
await this.node.send({
|
|
83
147
|
op: "play",
|
|
84
148
|
guildId: this.guildId,
|
|
85
149
|
track: toPlay.encoded,
|
|
@@ -88,27 +152,36 @@ class Player {
|
|
|
88
152
|
...(options?.endTime !== undefined && { endTime: String(options.endTime) }),
|
|
89
153
|
});
|
|
90
154
|
}
|
|
155
|
+
// Only update state AFTER the call succeeds
|
|
91
156
|
this.playing = true;
|
|
92
157
|
this.paused = false;
|
|
93
158
|
this.lastUpdated = Date.now();
|
|
94
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* Pause or unpause playback.
|
|
162
|
+
*
|
|
163
|
+
* FIX: `playing` now reflects "has an active track" independently of pause state.
|
|
164
|
+
* A paused player is still "playing" (it has an active track, just paused).
|
|
165
|
+
*/
|
|
95
166
|
async pause(state = true) {
|
|
96
167
|
if (this.node.version === 4) {
|
|
97
168
|
await this.node.rest.updatePlayer(this.guildId, { paused: state });
|
|
98
169
|
}
|
|
99
170
|
else {
|
|
100
|
-
this.node.send({ op: "pause", guildId: this.guildId, pause: state });
|
|
171
|
+
await this.node.send({ op: "pause", guildId: this.guildId, pause: state });
|
|
101
172
|
}
|
|
102
173
|
this.paused = state;
|
|
103
|
-
|
|
174
|
+
// `playing` stays true — the track is still loaded, just paused
|
|
175
|
+
}
|
|
176
|
+
async resume() {
|
|
177
|
+
return this.pause(false);
|
|
104
178
|
}
|
|
105
|
-
async resume() { return this.pause(false); }
|
|
106
179
|
async stop() {
|
|
107
180
|
if (this.node.version === 4) {
|
|
108
181
|
await this.node.rest.updatePlayer(this.guildId, { track: { encoded: null } });
|
|
109
182
|
}
|
|
110
183
|
else {
|
|
111
|
-
this.node.send({ op: "stop", guildId: this.guildId });
|
|
184
|
+
await this.node.send({ op: "stop", guildId: this.guildId });
|
|
112
185
|
}
|
|
113
186
|
this.playing = false;
|
|
114
187
|
this.paused = false;
|
|
@@ -126,41 +199,77 @@ class Player {
|
|
|
126
199
|
}
|
|
127
200
|
return next;
|
|
128
201
|
}
|
|
202
|
+
/**
|
|
203
|
+
* Play the previous track (from history).
|
|
204
|
+
* @returns The previous track, or null if no history exists
|
|
205
|
+
*/
|
|
206
|
+
async previous() {
|
|
207
|
+
const prev = this.queue.previous[0];
|
|
208
|
+
if (!prev)
|
|
209
|
+
return null;
|
|
210
|
+
// Move current back to front of queue
|
|
211
|
+
if (this.queue.current) {
|
|
212
|
+
this.queue.add(this.queue.current, 0);
|
|
213
|
+
}
|
|
214
|
+
// Remove from previous history
|
|
215
|
+
this.queue.previous.shift();
|
|
216
|
+
await this.play(prev);
|
|
217
|
+
return prev;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Restart the currently playing track from the beginning.
|
|
221
|
+
*/
|
|
222
|
+
async replay() {
|
|
223
|
+
if (!this.queue.current) {
|
|
224
|
+
throw new YukimuError_1.PlayerError("No track to replay", YukimuError_1.ErrorCode.PLAYER_NO_TRACK);
|
|
225
|
+
}
|
|
226
|
+
await this.play(this.queue.current, { startTime: 0 });
|
|
227
|
+
}
|
|
129
228
|
async seek(position) {
|
|
130
|
-
if (!this.queue.current?.info?.isSeekable)
|
|
131
|
-
throw new YukimuError_1.PlayerError("Current track is not seekable");
|
|
229
|
+
if (!this.queue.current?.info?.isSeekable) {
|
|
230
|
+
throw new YukimuError_1.PlayerError("Current track is not seekable", YukimuError_1.ErrorCode.PLAYER_NOT_SEEKABLE);
|
|
231
|
+
}
|
|
232
|
+
if (position < 0)
|
|
233
|
+
position = 0;
|
|
132
234
|
if (this.node.version === 4) {
|
|
133
235
|
await this.node.rest.updatePlayer(this.guildId, { position });
|
|
134
236
|
}
|
|
135
237
|
else {
|
|
136
|
-
this.node.send({ op: "seek", guildId: this.guildId, position });
|
|
238
|
+
await this.node.send({ op: "seek", guildId: this.guildId, position });
|
|
137
239
|
}
|
|
138
240
|
this.position = position;
|
|
139
241
|
this.lastUpdated = Date.now();
|
|
140
242
|
}
|
|
243
|
+
// ─── Volume ─────────────────────────────────────────────────────────────
|
|
141
244
|
async setVolume(volume) {
|
|
142
|
-
if (volume
|
|
143
|
-
throw new YukimuError_1.PlayerError("Volume must be
|
|
245
|
+
if (typeof volume !== "number" || isNaN(volume)) {
|
|
246
|
+
throw new YukimuError_1.PlayerError("Volume must be a number", YukimuError_1.ErrorCode.PLAYER_INVALID_VOLUME);
|
|
247
|
+
}
|
|
248
|
+
volume = Math.floor(Math.max(0, Math.min(1000, volume)));
|
|
144
249
|
if (this.node.version === 4) {
|
|
145
250
|
await this.node.rest.updatePlayer(this.guildId, { volume });
|
|
146
251
|
}
|
|
147
252
|
else {
|
|
148
|
-
this.node.send({ op: "volume", guildId: this.guildId, volume });
|
|
253
|
+
await this.node.send({ op: "volume", guildId: this.guildId, volume });
|
|
149
254
|
}
|
|
150
255
|
this.volume = volume;
|
|
151
256
|
}
|
|
257
|
+
// ─── Filters ────────────────────────────────────────────────────────────
|
|
152
258
|
async setFilters(filters) {
|
|
153
259
|
this.filters = { ...this.filters, ...filters };
|
|
154
260
|
if (this.node.version === 4) {
|
|
155
261
|
await this.node.rest.updatePlayer(this.guildId, { filters: this.filters });
|
|
156
262
|
}
|
|
157
263
|
else {
|
|
158
|
-
|
|
159
|
-
|
|
264
|
+
// v3: equalizer is sent separately
|
|
265
|
+
if (filters.equalizer) {
|
|
266
|
+
await this.node.send({ op: "equalizer", guildId: this.guildId, bands: filters.equalizer });
|
|
267
|
+
}
|
|
160
268
|
const rest = { ...filters };
|
|
161
269
|
delete rest.equalizer;
|
|
162
|
-
if (Object.keys(rest).length > 0)
|
|
163
|
-
this.node.send({ op: "filters", guildId: this.guildId, ...rest });
|
|
270
|
+
if (Object.keys(rest).length > 0) {
|
|
271
|
+
await this.node.send({ op: "filters", guildId: this.guildId, ...rest });
|
|
272
|
+
}
|
|
164
273
|
}
|
|
165
274
|
}
|
|
166
275
|
async clearFilters() {
|
|
@@ -169,128 +278,221 @@ class Player {
|
|
|
169
278
|
await this.node.rest.updatePlayer(this.guildId, { filters: {} });
|
|
170
279
|
}
|
|
171
280
|
else {
|
|
172
|
-
this.node.send({ op: "filters", guildId: this.guildId });
|
|
281
|
+
await this.node.send({ op: "filters", guildId: this.guildId });
|
|
173
282
|
}
|
|
174
283
|
}
|
|
284
|
+
// ─── Filter Presets ─────────────────────────────────────────────────────
|
|
175
285
|
async setBassBoost(level) {
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
};
|
|
183
|
-
|
|
286
|
+
const tier = this.boostTier;
|
|
287
|
+
if (level === "off") {
|
|
288
|
+
await this.setFilters({ equalizer: BitrateOptimizer_1.BitrateOptimizer.getFlatEQ() });
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
// Scale bass based on boost tier to prevent clipping
|
|
292
|
+
const scales = { low: 0.3, medium: 0.5, high: 0.75, extreme: 1.0 };
|
|
293
|
+
const scale = scales[level] ?? 0.5;
|
|
294
|
+
const tierScale = tier >= 2 ? 1.0 : tier === 1 ? 0.8 : 0.6;
|
|
295
|
+
const effectiveScale = scale * tierScale;
|
|
296
|
+
const eq = [
|
|
297
|
+
{ band: 0, gain: 0.6 * effectiveScale },
|
|
298
|
+
{ band: 1, gain: 0.5 * effectiveScale },
|
|
299
|
+
{ band: 2, gain: 0.4 * effectiveScale },
|
|
300
|
+
{ band: 3, gain: 0.25 * effectiveScale },
|
|
301
|
+
{ band: 4, gain: 0.1 * effectiveScale },
|
|
302
|
+
{ band: 5, gain: -0.05 * effectiveScale },
|
|
303
|
+
...Array.from({ length: 9 }, (_, i) => ({ band: i + 6, gain: 0 })),
|
|
304
|
+
];
|
|
305
|
+
await this.setFilters({ equalizer: eq });
|
|
184
306
|
}
|
|
185
307
|
async setNightcore(enabled) {
|
|
186
|
-
await this.setFilters({
|
|
308
|
+
await this.setFilters({
|
|
309
|
+
timescale: enabled ? { speed: 1.2, pitch: 1.2, rate: 1.0 } : {},
|
|
310
|
+
});
|
|
187
311
|
}
|
|
188
312
|
async setVaporwave(enabled) {
|
|
189
|
-
await this.setFilters({
|
|
313
|
+
await this.setFilters({
|
|
314
|
+
timescale: enabled ? { speed: 0.8, pitch: 0.8, rate: 1.0 } : {},
|
|
315
|
+
});
|
|
190
316
|
}
|
|
191
317
|
async set8D(enabled) {
|
|
192
|
-
await this.setFilters({
|
|
318
|
+
await this.setFilters({
|
|
319
|
+
rotation: enabled ? { rotationHz: 0.2 } : {},
|
|
320
|
+
});
|
|
193
321
|
}
|
|
194
322
|
async setKaraoke(enabled) {
|
|
195
|
-
await this.setFilters({
|
|
323
|
+
await this.setFilters({
|
|
324
|
+
karaoke: enabled
|
|
325
|
+
? { level: 1.0, monoLevel: 1.0, filterBand: 220.0, filterWidth: 100.0 }
|
|
326
|
+
: {},
|
|
327
|
+
});
|
|
196
328
|
}
|
|
197
329
|
async setTremolo(enabled, frequency = 2.0, depth = 0.5) {
|
|
330
|
+
if (enabled) {
|
|
331
|
+
if (frequency <= 0 || frequency > 14) {
|
|
332
|
+
throw new YukimuError_1.PlayerError("Tremolo frequency must be between 0 and 14", YukimuError_1.ErrorCode.PLAYER_INVALID_FILTER);
|
|
333
|
+
}
|
|
334
|
+
if (depth <= 0 || depth > 1) {
|
|
335
|
+
throw new YukimuError_1.PlayerError("Tremolo depth must be between 0 and 1", YukimuError_1.ErrorCode.PLAYER_INVALID_FILTER);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
198
338
|
await this.setFilters({ tremolo: enabled ? { frequency, depth } : {} });
|
|
199
339
|
}
|
|
200
340
|
async setVibrato(enabled, frequency = 2.0, depth = 0.5) {
|
|
341
|
+
if (enabled) {
|
|
342
|
+
if (frequency <= 0 || frequency > 14) {
|
|
343
|
+
throw new YukimuError_1.PlayerError("Vibrato frequency must be between 0 and 14", YukimuError_1.ErrorCode.PLAYER_INVALID_FILTER);
|
|
344
|
+
}
|
|
345
|
+
if (depth <= 0 || depth > 1) {
|
|
346
|
+
throw new YukimuError_1.PlayerError("Vibrato depth must be between 0 and 1", YukimuError_1.ErrorCode.PLAYER_INVALID_FILTER);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
201
349
|
await this.setFilters({ vibrato: enabled ? { frequency, depth } : {} });
|
|
202
350
|
}
|
|
203
351
|
async setLowPass(enabled, smoothing = 20.0) {
|
|
352
|
+
if (enabled && smoothing <= 0) {
|
|
353
|
+
throw new YukimuError_1.PlayerError("LowPass smoothing must be greater than 0", YukimuError_1.ErrorCode.PLAYER_INVALID_FILTER);
|
|
354
|
+
}
|
|
204
355
|
await this.setFilters({ lowPass: enabled ? { smoothing } : {} });
|
|
205
356
|
}
|
|
357
|
+
/** Set playback speed (1.0 = normal, 2.0 = double speed) */
|
|
358
|
+
async setSpeed(speed) {
|
|
359
|
+
if (speed <= 0 || speed > 10) {
|
|
360
|
+
throw new YukimuError_1.PlayerError("Speed must be between 0 and 10", YukimuError_1.ErrorCode.PLAYER_INVALID_FILTER);
|
|
361
|
+
}
|
|
362
|
+
const existing = (this.filters.timescale ?? {});
|
|
363
|
+
await this.setFilters({
|
|
364
|
+
timescale: { ...existing, speed },
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
/** Set playback pitch (1.0 = normal) */
|
|
368
|
+
async setPitch(pitch) {
|
|
369
|
+
if (pitch <= 0 || pitch > 10) {
|
|
370
|
+
throw new YukimuError_1.PlayerError("Pitch must be between 0 and 10", YukimuError_1.ErrorCode.PLAYER_INVALID_FILTER);
|
|
371
|
+
}
|
|
372
|
+
const existing = (this.filters.timescale ?? {});
|
|
373
|
+
await this.setFilters({
|
|
374
|
+
timescale: { ...existing, pitch },
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
// ─── Queue Shortcuts ───────────────────────────────────────────────────
|
|
378
|
+
/**
|
|
379
|
+
* Add track(s) to the queue. If nothing is currently playing, plays immediately.
|
|
380
|
+
*/
|
|
206
381
|
async add(tracks, requester) {
|
|
207
|
-
const arr = Array.isArray(tracks) ? tracks : [tracks];
|
|
208
|
-
if (requester)
|
|
209
|
-
|
|
382
|
+
const arr = Array.isArray(tracks) ? [...tracks] : [tracks];
|
|
383
|
+
if (requester) {
|
|
384
|
+
for (const t of arr) {
|
|
385
|
+
t["requester"] = requester;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
210
388
|
if (!this.queue.current && arr.length > 0) {
|
|
211
389
|
const first = arr.shift();
|
|
212
|
-
|
|
390
|
+
if (arr.length > 0)
|
|
391
|
+
this.queue.add(arr);
|
|
213
392
|
await this.play(first);
|
|
214
393
|
}
|
|
215
394
|
else {
|
|
216
395
|
this.queue.add(arr);
|
|
217
396
|
}
|
|
218
397
|
}
|
|
219
|
-
setLoop(mode) {
|
|
220
|
-
|
|
398
|
+
setLoop(mode) {
|
|
399
|
+
this.loop = mode;
|
|
400
|
+
}
|
|
401
|
+
setAutoplay(enabled) {
|
|
402
|
+
this.autoplay = enabled;
|
|
403
|
+
}
|
|
404
|
+
// ─── Node Movement ─────────────────────────────────────────────────────
|
|
405
|
+
/**
|
|
406
|
+
* Move this player to a different Lavalink node.
|
|
407
|
+
*
|
|
408
|
+
* FIX: Now properly re-sends the voice state to the new node.
|
|
409
|
+
* In v1, the new node didn't know about the voice connection.
|
|
410
|
+
*/
|
|
221
411
|
async moveToNode(node) {
|
|
222
412
|
if (this.node === node)
|
|
223
413
|
return;
|
|
224
414
|
const oldNode = this.node;
|
|
225
415
|
this.node = node;
|
|
416
|
+
// Clean up on old node
|
|
226
417
|
await oldNode.rest.destroyPlayer(this.guildId).catch(() => { });
|
|
418
|
+
// Re-send voice state to new node
|
|
419
|
+
this.checkVoiceReady();
|
|
420
|
+
// Resume playback on new node
|
|
227
421
|
if (this.queue.current) {
|
|
228
422
|
await this.play(this.queue.current, { startTime: this.realPosition });
|
|
229
423
|
}
|
|
230
|
-
|
|
424
|
+
// Re-apply filters and volume
|
|
425
|
+
if (Object.keys(this.filters).length > 0) {
|
|
231
426
|
await this.setFilters(this.filters).catch(() => { });
|
|
232
|
-
|
|
427
|
+
}
|
|
428
|
+
if (this.volume !== 100) {
|
|
233
429
|
await this.setVolume(this.volume).catch(() => { });
|
|
430
|
+
}
|
|
234
431
|
}
|
|
432
|
+
/** Move the bot to a different voice channel */
|
|
235
433
|
async move(channelId) {
|
|
236
434
|
this.voiceChannelId = channelId;
|
|
237
435
|
this.sendVoicePayload(channelId, this.selfDeaf, this.selfMute);
|
|
238
436
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
this.
|
|
246
|
-
|
|
247
|
-
this.
|
|
437
|
+
// ─── Audio Quality ──────────────────────────────────────────────────────
|
|
438
|
+
/**
|
|
439
|
+
* Apply optimal audio settings based on the guild's boost tier.
|
|
440
|
+
* Automatically adjusts EQ and filters for best clarity at the available bitrate.
|
|
441
|
+
*/
|
|
442
|
+
async applyOptimalQuality() {
|
|
443
|
+
const tier = this.boostTier;
|
|
444
|
+
const filters = BitrateOptimizer_1.BitrateOptimizer.getOptimalFilters(tier);
|
|
445
|
+
await this.setFilters(filters);
|
|
446
|
+
this.manager.logger.debug(`Applied optimal quality for guild ${this.guildId} (boost tier ${tier}, max ${BitrateOptimizer_1.BitrateOptimizer.getMaxBitrate(tier)}kbps)`);
|
|
248
447
|
}
|
|
249
|
-
// ─── Audio Quality Presets ────────────────────────────────────────
|
|
250
448
|
/** Maximum audio quality — crystal clear, enhanced bass & treble */
|
|
251
449
|
async setHighQuality() {
|
|
450
|
+
const tier = this.boostTier;
|
|
451
|
+
const scale = tier >= 2 ? 1.0 : tier === 1 ? 0.8 : 0.6;
|
|
252
452
|
await this.setFilters({
|
|
253
453
|
equalizer: [
|
|
254
|
-
{ band: 0, gain: 0.25 },
|
|
255
|
-
{ band: 1, gain: 0.20 },
|
|
256
|
-
{ band: 2, gain: 0.15 },
|
|
257
|
-
{ band: 3, gain: 0.05 },
|
|
258
|
-
{ band: 4, gain: 0.0 },
|
|
259
|
-
{ band: 5, gain: 0.0 },
|
|
260
|
-
{ band: 6, gain: 0.0 },
|
|
261
|
-
{ band: 7, gain: 0.05 },
|
|
262
|
-
{ band: 8, gain: 0.10 },
|
|
263
|
-
{ band: 9, gain: 0.15 },
|
|
264
|
-
{ band: 10, gain: 0.15 },
|
|
265
|
-
{ band: 11, gain: 0.10 },
|
|
266
|
-
{ band: 12, gain: 0.05 },
|
|
267
|
-
{ band: 13, gain: 0.0 },
|
|
268
|
-
{ band: 14, gain: 0.0 },
|
|
454
|
+
{ band: 0, gain: 0.25 * scale },
|
|
455
|
+
{ band: 1, gain: 0.20 * scale },
|
|
456
|
+
{ band: 2, gain: 0.15 * scale },
|
|
457
|
+
{ band: 3, gain: 0.05 * scale },
|
|
458
|
+
{ band: 4, gain: 0.0 },
|
|
459
|
+
{ band: 5, gain: 0.0 },
|
|
460
|
+
{ band: 6, gain: 0.0 },
|
|
461
|
+
{ band: 7, gain: 0.05 * scale },
|
|
462
|
+
{ band: 8, gain: 0.10 * scale },
|
|
463
|
+
{ band: 9, gain: 0.15 * scale },
|
|
464
|
+
{ band: 10, gain: 0.15 * scale },
|
|
465
|
+
{ band: 11, gain: 0.10 * scale },
|
|
466
|
+
{ band: 12, gain: 0.05 * scale },
|
|
467
|
+
{ band: 13, gain: 0.0 },
|
|
468
|
+
{ band: 14, gain: 0.0 },
|
|
269
469
|
],
|
|
270
|
-
lowPass: { smoothing: 5.0 }
|
|
470
|
+
lowPass: tier >= 2 ? { smoothing: 5.0 } : {},
|
|
271
471
|
});
|
|
272
472
|
}
|
|
273
473
|
/** Studio quality — flat, neutral, most accurate */
|
|
274
474
|
async setStudioQuality() {
|
|
275
475
|
await this.clearFilters();
|
|
276
476
|
await this.setFilters({
|
|
277
|
-
equalizer:
|
|
477
|
+
equalizer: BitrateOptimizer_1.BitrateOptimizer.getFlatEQ(),
|
|
278
478
|
});
|
|
279
479
|
}
|
|
280
|
-
/** Concert hall effect */
|
|
480
|
+
/** Concert hall effect — scaled for boost tier */
|
|
281
481
|
async setConcertHall() {
|
|
482
|
+
const tier = this.boostTier;
|
|
483
|
+
const scale = tier >= 2 ? 1.0 : 0.6;
|
|
282
484
|
await this.setFilters({
|
|
283
485
|
equalizer: [
|
|
284
|
-
{ band: 0, gain: 0.1 },
|
|
285
|
-
{ band: 1, gain: 0.1 },
|
|
286
|
-
{ band: 2, gain: 0.05 },
|
|
486
|
+
{ band: 0, gain: 0.1 * scale },
|
|
487
|
+
{ band: 1, gain: 0.1 * scale },
|
|
488
|
+
{ band: 2, gain: 0.05 * scale },
|
|
287
489
|
{ band: 3, gain: 0.0 },
|
|
288
|
-
{ band: 4, gain: -0.05 },
|
|
490
|
+
{ band: 4, gain: -0.05 * scale },
|
|
289
491
|
{ band: 5, gain: 0.0 },
|
|
290
|
-
{ band: 6, gain: 0.05 },
|
|
291
|
-
{ band: 7, gain: 0.1 },
|
|
292
|
-
{ band: 8, gain: 0.1 },
|
|
293
|
-
{ band: 9, gain: 0.05 },
|
|
492
|
+
{ band: 6, gain: 0.05 * scale },
|
|
493
|
+
{ band: 7, gain: 0.1 * scale },
|
|
494
|
+
{ band: 8, gain: 0.1 * scale },
|
|
495
|
+
{ band: 9, gain: 0.05 * scale },
|
|
294
496
|
{ band: 10, gain: 0.0 },
|
|
295
497
|
{ band: 11, gain: 0.0 },
|
|
296
498
|
{ band: 12, gain: 0.0 },
|
|
@@ -300,6 +502,29 @@ class Player {
|
|
|
300
502
|
rotation: { rotationHz: 0.05 },
|
|
301
503
|
});
|
|
302
504
|
}
|
|
505
|
+
// ─── Lifecycle ──────────────────────────────────────────────────────────
|
|
506
|
+
/**
|
|
507
|
+
* Destroy this player and clean up all resources.
|
|
508
|
+
*/
|
|
509
|
+
async destroy() {
|
|
510
|
+
this.state = Constants_1.PlayerState.DISCONNECTING;
|
|
511
|
+
// Leave voice channel
|
|
512
|
+
this.sendVoicePayload(null, false, false);
|
|
513
|
+
// Destroy on Lavalink
|
|
514
|
+
await this.node.rest.destroyPlayer(this.guildId).catch(() => { });
|
|
515
|
+
// Clean up all state
|
|
516
|
+
this.playing = false;
|
|
517
|
+
this.paused = false;
|
|
518
|
+
this.position = 0;
|
|
519
|
+
this.queue.clear();
|
|
520
|
+
this.data.clear();
|
|
521
|
+
this.filters = {};
|
|
522
|
+
this.sessionId = null;
|
|
523
|
+
this.voiceToken = null;
|
|
524
|
+
this.voiceEndpoint = null;
|
|
525
|
+
this.voiceChannelId = null;
|
|
526
|
+
this.state = Constants_1.PlayerState.DISCONNECTED;
|
|
527
|
+
}
|
|
303
528
|
}
|
|
304
529
|
exports.Player = Player;
|
|
305
530
|
//# sourceMappingURL=Player.js.map
|