aqualink 2.9.13 → 2.10.1

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.
@@ -29,58 +29,55 @@ const FAILURE_REASONS = new Set(['LOAD_FAILED', 'CLEANUP'])
29
29
  const RECONNECT_CODES = new Set([4015, 4009, 4006])
30
30
  const FAIL_LOAD_TYPES = new Set(['error', 'empty', 'LOAD_FAILED', 'NO_MATCHES'])
31
31
 
32
- const VOLUME_REGEX = /^([0-9]|[1-9][0-9]|1[0-9][0-9]|200)$/
33
- const POSITION_REGEX = /^\d+$/
32
+ const clamp = v => Math.max(0, Math.min(200, +v || 0))
33
+ const isValidVolume = v => typeof v === 'number' && v >= 0 && v <= 200
34
+ const isValidPosition = p => typeof p === 'number' && !isNaN(p) && p >= 0
35
+ const getRandomIndex = arr => (Math.random() * arr.length) | 0
34
36
 
35
- const _clampVolume = volume => Math.max(0, Math.min(200, volume))
36
- const _isValidVolume = volume => VOLUME_REGEX.test(String(volume))
37
- const _isValidPosition = position => POSITION_REGEX.test(String(position))
38
-
39
- class OptimizedUpdateBatcher {
37
+ class MicrotaskUpdateBatcher {
40
38
  constructor(player) {
41
39
  this.player = player
42
40
  this.updates = null
43
- this.timeoutId = 0
44
- this.hasPending = false
41
+ this.isScheduled = false
45
42
  }
46
43
 
47
44
  batch(data, immediate = false) {
48
- if (!this.updates) this.updates = Object.create(null)
49
-
50
- Object.assign(this.updates, data)
51
- this.hasPending = true
45
+ this.updates ??= Object.create(null)
52
46
 
53
- if (this.timeoutId) {
54
- clearTimeout(this.timeoutId)
55
- this.timeoutId = 0
47
+ const keys = Object.keys(data)
48
+ for (let i = 0; i < keys.length; i++) {
49
+ const key = keys[i]
50
+ this.updates[key] = data[key]
56
51
  }
57
52
 
58
53
  if (immediate || data.track) {
59
54
  return this._flush()
60
55
  }
61
56
 
62
- this.timeoutId = setTimeout(() => this._flush(), 32)
57
+ if (!this.isScheduled) {
58
+ this.isScheduled = true
59
+ queueMicrotask(() => {
60
+ this._flush()
61
+ this.isScheduled = false
62
+ })
63
+ }
64
+
63
65
  return Promise.resolve()
64
66
  }
65
67
 
66
68
  _flush() {
67
- if (!this.hasPending) return Promise.resolve()
69
+ if (!this.updates) return
68
70
 
69
71
  const updates = this.updates
70
72
  this.updates = null
71
- this.hasPending = false
72
- this.timeoutId = 0
73
-
74
- return this.player.updatePlayer(updates)
73
+ this.player.updatePlayer(updates).catch(err => {
74
+ console.error('Update player error:', err)
75
+ })
75
76
  }
76
77
 
77
78
  destroy() {
78
- if (this.timeoutId) {
79
- clearTimeout(this.timeoutId)
80
- this.timeoutId = 0
81
- }
82
79
  this.updates = null
83
- this.hasPending = false
80
+ this.isScheduled = false
84
81
  }
85
82
  }
86
83
 
@@ -99,9 +96,7 @@ class CircularBuffer {
99
96
  }
100
97
 
101
98
  getLast() {
102
- return this.count > 0
103
- ? this.buffer[(this.index - 1 + this.size) % this.size]
104
- : null
99
+ return this.count ? this.buffer[(this.index - 1 + this.size) % this.size] : null
105
100
  }
106
101
 
107
102
  clear() {
@@ -115,6 +110,18 @@ class Player extends EventEmitter {
115
110
  static EVENT_HANDLERS = EVENT_HANDLERS
116
111
  static validModes = VALID_MODES
117
112
 
113
+ playing = false
114
+ paused = false
115
+ connected = false
116
+ isAutoplayEnabled = false
117
+ isAutoplay = false
118
+ autoplaySeed = null
119
+ current = null
120
+ position = 0
121
+ timestamp = 0
122
+ ping = 0
123
+ nowPlayingMessage = null
124
+
118
125
  constructor(aqua, nodes, options = {}) {
119
126
  super()
120
127
 
@@ -124,37 +131,19 @@ class Player extends EventEmitter {
124
131
  this.textChannel = options.textChannel
125
132
  this.voiceChannel = options.voiceChannel
126
133
 
127
- this.connection = new Connection(this, {
128
- sessionId: options.sessionId,
129
- token: options.token,
130
- endpoint: options.endpoint
131
- })
132
-
134
+ this.connection = new Connection(this)
133
135
  this.filters = new Filters(this)
134
136
  this.queue = new Queue()
135
137
 
136
138
  const vol = options.defaultVolume ?? 100
137
- this.volume = _isValidVolume(vol) ? vol : _clampVolume(vol)
139
+ this.volume = isValidVolume(vol) ? vol : clamp(vol)
138
140
 
139
141
  this.loop = VALID_MODES.has(options.loop) ? options.loop : LOOP_MODES.NONE
140
- this.shouldDeleteMessage = !!this.aqua.options.shouldDeleteMessage
141
- this.leaveOnEnd = !!this.aqua.options.leaveOnEnd
142
+ this.shouldDeleteMessage = !!aqua.options?.shouldDeleteMessage
143
+ this.leaveOnEnd = !!aqua.options?.leaveOnEnd
142
144
 
143
145
  this.previousTracks = new CircularBuffer(50)
144
-
145
- this.playing = false
146
- this.paused = false
147
- this.connected = false
148
- this.isAutoplayEnabled = false
149
- this.isAutoplay = false
150
-
151
- this.current = null
152
- this.position = 0
153
- this.timestamp = 0
154
- this.ping = 0
155
- this.nowPlayingMessage = null
156
-
157
- this._updateBatcher = new OptimizedUpdateBatcher(this)
146
+ this._updateBatcher = new MicrotaskUpdateBatcher(this)
158
147
  this._dataStore = new Map()
159
148
 
160
149
  this._boundPlayerUpdate = this._handlePlayerUpdate.bind(this)
@@ -165,11 +154,11 @@ class Player extends EventEmitter {
165
154
  }
166
155
 
167
156
  _handlePlayerUpdate(packet) {
168
- const { position, connected, ping, time } = packet.state
169
- this.position = position
170
- this.connected = connected
171
- this.ping = ping
172
- this.timestamp = time
157
+ const state = packet.state
158
+ this.position = state.position
159
+ this.connected = state.connected
160
+ this.ping = state.ping
161
+ this.timestamp = state.time
173
162
  this.aqua.emit('playerUpdate', this, packet)
174
163
  }
175
164
 
@@ -178,7 +167,7 @@ class Player extends EventEmitter {
178
167
 
179
168
  if (!handlerName || typeof this[handlerName] !== 'function') {
180
169
  this.aqua.emit('nodeError', this, new Error(`Unknown event: ${payload.type}`))
181
- return;
170
+ return
182
171
  }
183
172
 
184
173
  try {
@@ -188,76 +177,21 @@ class Player extends EventEmitter {
188
177
  }
189
178
  }
190
179
 
191
- get previous() {
192
- return this.previousTracks.getLast()
193
- }
194
-
195
- get currenttrack() {
196
- return this.current
197
- }
180
+ get previous() { return this.previousTracks.getLast() }
181
+ get currenttrack() { return this.current }
182
+ getQueue() { return this.queue }
198
183
 
199
184
  batchUpdatePlayer(data, immediate = false) {
200
185
  return this._updateBatcher.batch(data, immediate)
201
186
  }
202
187
 
203
- async autoplay() {
204
- if (!this.isAutoplayEnabled || !this.previous) return this
205
-
206
- this.isAutoplay = true
207
- const prevInfo = this.previous.info
208
- const { sourceName, identifier, uri, requester } = prevInfo
209
-
210
- try {
211
- let query = null
212
- let source = null
213
-
214
- if (sourceName === 'youtube') {
215
- query = `https://www.youtube.com/watch?v=${identifier}&list=RD${identifier}`
216
- source = 'ytmsearch'
217
- } else if (sourceName === 'soundcloud') {
218
- const scResults = await scAutoPlay(uri)
219
- if (!scResults?.length) return this
220
- query = scResults[0]
221
- source = 'scsearch'
222
- } else if (sourceName === 'spotify') {
223
- const spResult = await spAutoPlay(identifier)
224
- if (!spResult) return this
225
- query = `https://open.spotify.com/track/${spResult}`
226
- source = 'spotify'
227
- } else {
228
- return this
229
- }
230
-
231
- const response = await this.aqua.resolve({ query, source, requester })
232
-
233
- if (!response?.tracks?.length || FAIL_LOAD_TYPES.has(response.loadType)) {
234
- return this.stop()
235
- }
236
-
237
- const tracks = response.tracks
238
- const track = tracks[Math.floor(Math.random() * tracks.length)]
239
-
240
- if (!track?.info?.title) {
241
- throw new Error('Invalid track object')
242
- }
243
-
244
- track.requester = this.previous.requester || { id: 'Unknown' }
245
- this.queue.push(track)
246
- await this.play()
247
-
248
- return this
249
- } catch {
250
- return this.stop()
251
- }
252
- }
253
-
254
188
  setAutoplay(enabled) {
255
189
  this.isAutoplayEnabled = !!enabled
256
190
  return this
257
191
  }
258
192
 
259
193
  async play() {
260
- if (!this.connected || !this.queue.length) return;
194
+ if (!this.connected || !this.queue.length) return
261
195
 
262
196
  const item = this.queue.shift()
263
197
  this.current = item.track ? item : await item.resolve(this.aqua)
@@ -269,7 +203,6 @@ class Player extends EventEmitter {
269
203
 
270
204
  connect(options = this) {
271
205
  const { guildId, voiceChannel, deaf = true, mute = false } = options
272
-
273
206
  this.deaf = deaf
274
207
  this.mute = mute
275
208
  this.connected = true
@@ -280,45 +213,41 @@ class Player extends EventEmitter {
280
213
  self_deaf: deaf,
281
214
  self_mute: mute
282
215
  })
283
-
284
216
  return this
285
217
  }
286
218
 
287
219
  destroy() {
288
220
  if (!this.connected) return this
289
221
 
290
- this._updateBatcher.destroy()
222
+ this.connected = false
223
+ this._updateBatcher?.destroy()
291
224
 
292
225
  this.send({ guild_id: this.guildId, channel_id: null })
293
- this.connected = false
294
- this.voiceChannel = null
295
226
 
296
227
  if (this.nowPlayingMessage) {
297
- this.nowPlayingMessage.delete().catch(() => {})
228
+ this.nowPlayingMessage.delete().catch(() => { })
298
229
  this.nowPlayingMessage = null
299
230
  }
300
231
 
232
+ this.voiceChannel = null
301
233
  this.isAutoplay = false
302
- this.aqua.destroyPlayer(this.guildId)
303
234
 
235
+ this.aqua.destroyPlayer(this.guildId)
304
236
  if (this.nodes?.connected) {
305
- try {
306
- this.nodes.rest.destroyPlayer(this.guildId)
307
- } catch (error) {
237
+ this.nodes.rest.destroyPlayer(this.guildId).catch(error => {
308
238
  if (!error.message.includes('ECONNREFUSED')) {
309
- console.error('Error destroying player:', error)
239
+ console.error(`[Player ${this.guildId}] Destroy error:`, error.message)
310
240
  }
311
- }
241
+ })
312
242
  }
313
243
 
314
- this.previousTracks.clear()
315
- this._dataStore.clear()
244
+ this.previousTracks?.clear()
245
+ this._dataStore?.clear()
246
+
316
247
  this.removeAllListeners()
317
248
 
318
- this.queue = null
319
- this.previousTracks = null
320
- this.connection = null
321
- this.filters = null
249
+ this.queue = this.previousTracks = this.connection = this.filters =
250
+ this._updateBatcher = this._dataStore = null
322
251
 
323
252
  return this
324
253
  }
@@ -330,44 +259,11 @@ class Player extends EventEmitter {
330
259
  return this
331
260
  }
332
261
 
333
- async getLyrics(options = {}) {
334
- const { query, useCurrentTrack = true, skipTrackSource = false } = options
335
-
336
- if (query) {
337
- return this.nodes.rest.getLyrics({
338
- track: { info: { title: query } },
339
- skipTrackSource
340
- })
341
- }
342
-
343
- if (useCurrentTrack && this.playing && this.current?.info) {
344
- return this.nodes.rest.getLyrics({
345
- track: {
346
- info: this.current.info,
347
- identifier: this.current.info.identifier,
348
- guild_id: this.guildId
349
- },
350
- skipTrackSource
351
- })
352
- }
353
-
354
- return null
355
- }
356
-
357
- subscribeLiveLyrics() {
358
- return this.nodes.rest.subscribeLiveLyrics(this.guildId, false)
359
- }
360
-
361
- unsubscribeLiveLyrics() {
362
- return this.nodes.rest.unsubscribeLiveLyrics(this.guildId)
363
- }
364
-
365
262
  seek(position) {
366
- if (!this.playing || !_isValidPosition(position)) return this
263
+ if (!this.playing || !isValidPosition(position)) return this
367
264
 
368
265
  const maxPos = this.current?.info?.length
369
- this.position = Math.max(0, maxPos ? Math.min(position, maxPos) : position)
370
-
266
+ this.position = maxPos ? Math.min(Math.max(0, position), maxPos) : Math.max(0, position)
371
267
  this.batchUpdatePlayer({ position: this.position })
372
268
  return this
373
269
  }
@@ -381,20 +277,16 @@ class Player extends EventEmitter {
381
277
  }
382
278
 
383
279
  setVolume(volume) {
384
- if (!_isValidVolume(volume)) return this
385
-
386
- const vol = _clampVolume(volume)
280
+ if (!isValidVolume(volume)) return this
281
+ const vol = clamp(volume)
387
282
  if (this.volume === vol) return this
388
-
389
283
  this.volume = vol
390
284
  this.batchUpdatePlayer({ volume: vol })
391
285
  return this
392
286
  }
393
287
 
394
288
  setLoop(mode) {
395
- if (!VALID_MODES.has(mode)) {
396
- throw new Error('Invalid loop mode')
397
- }
289
+ if (!VALID_MODES.has(mode)) throw new Error('Invalid loop mode')
398
290
  this.loop = mode
399
291
  this.batchUpdatePlayer({ loop: mode })
400
292
  return this
@@ -417,7 +309,6 @@ class Player extends EventEmitter {
417
309
  deaf: this.deaf,
418
310
  guildId: this.guildId,
419
311
  voiceChannel: channel,
420
- textChannel: this.textChannel,
421
312
  mute: this.mute
422
313
  })
423
314
  return this
@@ -435,29 +326,125 @@ class Player extends EventEmitter {
435
326
  const queue = this.queue
436
327
  const len = queue.length
437
328
 
438
- if (len <= 1) return this
439
-
440
329
  for (let i = len - 1; i > 0; i--) {
441
- const j = Math.floor(Math.random() * (i + 1))
442
- ;[queue[i], queue[j]] = [queue[j], queue[i]]
330
+ const j = (Math.random() * (i + 1)) | 0
331
+ ;[queue[i], queue[j]] = [queue[j], queue[i]]
443
332
  }
444
-
445
333
  return this
446
334
  }
447
335
 
448
- getQueue() {
449
- return this.queue
450
- }
451
-
452
- replay() {
453
- return this.seek(0)
454
- }
336
+ replay() { return this.seek(0) }
455
337
 
456
338
  skip() {
457
339
  this.stop()
458
340
  return this.playing ? this.play() : undefined
459
341
  }
460
342
 
343
+ async getLyrics({ query, useCurrentTrack = true, skipTrackSource = false } = {}) {
344
+ if (query) {
345
+ return this.nodes.rest.getLyrics({
346
+ track: { info: { title: query } },
347
+ skipTrackSource
348
+ })
349
+ }
350
+
351
+ if (useCurrentTrack && this.playing && this.current) {
352
+ const currentInfo = this.current.info
353
+ return this.nodes.rest.getLyrics({
354
+ track: {
355
+ info: currentInfo,
356
+ encoded: this.current.track,
357
+ identifier: currentInfo.identifier,
358
+ guild_id: this.guildId
359
+ },
360
+ skipTrackSource
361
+ })
362
+ }
363
+
364
+ return null
365
+ }
366
+
367
+ subscribeLiveLyrics() {
368
+ return this.nodes.rest.subscribeLiveLyrics(this.guildId, false)
369
+ }
370
+
371
+ unsubscribeLiveLyrics() {
372
+ return this.nodes.rest.unsubscribeLiveLyrics(this.guildId)
373
+ }
374
+
375
+ async autoplay() {
376
+ if (!this.isAutoplayEnabled || !this.previous) return this
377
+
378
+ this.isAutoplay = true
379
+ const prevInfo = this.previous.info
380
+ const sourceName = prevInfo.sourceName
381
+ const identifier = prevInfo.identifier
382
+ const uri = prevInfo.uri
383
+ const requester = prevInfo.requester
384
+ const author = prevInfo.author
385
+
386
+ try {
387
+ let query = null
388
+ let source = null
389
+ let resolved = null
390
+ let track = null
391
+
392
+ if (sourceName === 'youtube') {
393
+ query = `https://www.youtube.com/watch?v=${identifier}&list=RD${identifier}`
394
+ source = 'ytmsearch'
395
+ } else if (sourceName === 'soundcloud') {
396
+ const scResults = await scAutoPlay(uri)
397
+ if (!scResults?.length) return this
398
+ query = scResults[0]
399
+ source = 'scsearch'
400
+ } else if (sourceName === 'spotify') {
401
+ this.previousIdentifiers ??= []
402
+
403
+ if (this.previous) {
404
+ this.previousIdentifiers.unshift(this.previous.identifier)
405
+ if (this.previousIdentifiers.length >= 20) this.previousIdentifiers.pop()
406
+ }
407
+
408
+ if (!this.autoplaySeed) {
409
+ this.autoplaySeed = {
410
+ trackId: identifier,
411
+ artistIds: Array.isArray(author) ? author.join(',') : author
412
+ }
413
+ }
414
+
415
+ resolved = await spAutoPlay(
416
+ this.autoplaySeed,
417
+ this,
418
+ requester,
419
+ this.previousIdentifiers
420
+ )
421
+ if (!resolved?.length) return this
422
+ } else {
423
+ return this
424
+ }
425
+
426
+ if (resolved) {
427
+ track = resolved[getRandomIndex(resolved)]
428
+ } else {
429
+ const response = await this.aqua.resolve({ query, source, requester })
430
+ if (!response?.tracks?.length || FAIL_LOAD_TYPES.has(response.loadType)) {
431
+ return this.stop()
432
+ }
433
+ track = response.tracks[getRandomIndex(response.tracks)]
434
+ }
435
+
436
+ if (!track?.info?.title) throw new Error('Invalid track object')
437
+
438
+ track.requester = this.previous.requester || { id: 'Unknown' }
439
+ this.queue.push(track)
440
+ await this.play()
441
+ return this
442
+ } catch (err) {
443
+ console.error('Autoplay failed:', err)
444
+ return this.stop()
445
+ }
446
+ }
447
+
461
448
  async trackStart(player, track) {
462
449
  this.playing = true
463
450
  this.paused = false
@@ -465,26 +452,24 @@ class Player extends EventEmitter {
465
452
  }
466
453
 
467
454
  async trackEnd(player, track, payload) {
468
- if (track) {
469
- this.previousTracks.push(track)
470
- }
455
+ if (track) this.previousTracks.push(track)
471
456
 
472
457
  if (this.shouldDeleteMessage && this.nowPlayingMessage) {
473
- this.nowPlayingMessage.delete().catch(() => {})
458
+ this.nowPlayingMessage.delete().catch(() => { })
474
459
  this.nowPlayingMessage = null
475
460
  }
476
461
 
477
- const { reason } = payload
462
+ const reason = payload.reason
463
+
478
464
  if (FAILURE_REASONS.has(reason)) {
479
- if (this.queue.length === 0) {
480
- this.previousTracks.clear()
481
- this._dataStore.clear()
465
+ if (!this.queue.length) {
466
+ this.clearData()
482
467
  this.aqua.emit('queueEnd', player)
483
468
  } else {
484
469
  this.aqua.emit('trackEnd', player, track, reason)
485
470
  await player.play()
486
471
  }
487
- return;
472
+ return
488
473
  }
489
474
 
490
475
  if (this.loop === LOOP_MODES.TRACK) {
@@ -499,8 +484,7 @@ class Player extends EventEmitter {
499
484
  } else {
500
485
  this.playing = false
501
486
  if (this.leaveOnEnd) {
502
- this.previousTracks.clear()
503
- this._dataStore.clear()
487
+ this.clearData()
504
488
  this.destroy()
505
489
  }
506
490
  this.aqua.emit('queueEnd', player)
@@ -522,100 +506,105 @@ class Player extends EventEmitter {
522
506
  }
523
507
 
524
508
  async socketClosed(player, track, payload) {
525
- const { code, guildId } = payload || {}
509
+ if (payload.code === 4014) return this.destroy()
526
510
 
527
- if (RECONNECT_CODES.has(code)) {
511
+ if (payload.code === 4015) {
528
512
  try {
529
- const voiceChannelId = this.voiceChannel?.id || this.voiceChannel
530
- const textChannelId = this.textChannel?.id || this.textChannel
531
- const currentTrack = this.current
532
-
533
- const savedConnectionState = {
534
- sessionId: this.connection?.sessionId,
535
- endpoint: this.connection?.endpoint,
536
- token: this.connection?.token,
537
- region: this.connection?.region,
538
- volume: this.volume,
539
- position: this.position,
540
- paused: this.paused,
541
- loop: this.loop,
542
- isAutoplayEnabled: this.isAutoplayEnabled
543
- }
544
-
545
- if (!voiceChannelId) {
546
- this.aqua.emit('socketClosed', player, payload)
513
+ if (this.connection) {
514
+ this.connection._updatePlayerVoiceData(true);
515
+ this.aqua.emit('debug', `[Player ${this.guildId}] Attempting resume...`);
547
516
  return;
548
517
  }
518
+ } catch (error) {
519
+ console.error('Resume failed, falling back to reconnect', error);
520
+ }
521
+ }
549
522
 
550
- if (!player.destroyed) {
551
- await player.destroy()
552
- this.aqua.emit('playerDestroy', player)
553
- }
523
+ if (!RECONNECT_CODES.has(payload.code)) {
524
+ this.aqua.emit('socketClosed', player, payload)
525
+ return
526
+ }
554
527
 
555
- const newPlayer = await this.aqua.createConnection({
556
- guildId,
557
- voiceChannel: voiceChannelId,
558
- textChannel: textChannelId,
559
- deaf: this.deaf,
560
- mute: this.mute,
561
- defaultVolume: savedConnectionState.volume,
562
- sessionId: savedConnectionState.sessionId,
563
- token: savedConnectionState.token,
564
- endpoint: savedConnectionState.endpoint
565
- })
566
-
567
- if (newPlayer) {
568
- newPlayer.loop = savedConnectionState.loop
569
- newPlayer.isAutoplayEnabled = savedConnectionState.isAutoplayEnabled
570
-
571
- if (savedConnectionState.sessionId && newPlayer.connection) {
572
- Object.assign(newPlayer.connection, {
573
- sessionId: savedConnectionState.sessionId,
574
- endpoint: savedConnectionState.endpoint,
575
- token: savedConnectionState.token,
576
- region: savedConnectionState.region
577
- })
578
- }
528
+ try {
529
+ const voiceChannelId = this.voiceChannel?.id || this.voiceChannel
530
+ if (!voiceChannelId) {
531
+ this.aqua.emit('socketClosed', player, payload)
532
+ return
533
+ }
579
534
 
580
- if (currentTrack) {
581
- newPlayer.queue.add(currentTrack)
535
+ const connection = this.connection
536
+ const savedState = {
537
+ sessionId: connection?.sessionId,
538
+ endpoint: connection?.endpoint,
539
+ token: connection?.token,
540
+ region: connection?.region,
541
+ volume: this.volume,
542
+ position: this.position,
543
+ paused: this.paused,
544
+ loop: this.loop,
545
+ isAutoplayEnabled: this.isAutoplayEnabled,
546
+ currentTrack: this.current,
547
+ queue: [...this.queue]
548
+ }
582
549
 
583
- if (this.queue?.length > 0) {
584
- this.queue.forEach(item => newPlayer.queue.add(item))
585
- }
550
+ if (!player.destroyed) {
551
+ await player.destroy()
552
+ this.aqua.emit('playerDestroy', player)
553
+ }
586
554
 
587
- await newPlayer.play()
555
+ const newPlayer = await this.aqua.createConnection({
556
+ guildId: payload.guildId,
557
+ voiceChannel: voiceChannelId,
558
+ textChannel: this.textChannel?.id || this.textChannel,
559
+ deaf: this.deaf,
560
+ mute: this.mute,
561
+ defaultVolume: savedState.volume,
562
+ })
588
563
 
589
- if (savedConnectionState.position > 5000) {
590
- setTimeout(() => {
591
- newPlayer.seek(savedConnectionState.position)
592
- }, 1000)
593
- }
564
+ if (!newPlayer) throw new Error('Failed to create a new player during reconnection.')
594
565
 
595
- if (savedConnectionState.paused) {
596
- setTimeout(() => {
597
- newPlayer.pause(true)
598
- }, 1500)
599
- }
600
- }
566
+ newPlayer.loop = savedState.loop
567
+ newPlayer.isAutoplayEnabled = savedState.isAutoplayEnabled
601
568
 
602
- this.aqua.emit('playerReconnected', newPlayer, {
603
- oldPlayer: player,
604
- code,
605
- restoredState: savedConnectionState
606
- })
569
+ if (savedState.sessionId && newPlayer.connection) {
570
+ const newConnection = newPlayer.connection
571
+ newConnection.sessionId = savedState.sessionId
572
+ newConnection.endpoint = savedState.endpoint
573
+ newConnection.token = savedState.token
574
+ newConnection.region = savedState.region
575
+ }
576
+
577
+ if (savedState.currentTrack) {
578
+ newPlayer.queue.add(savedState.currentTrack)
579
+ const queue = savedState.queue
580
+ for (let i = 0; i < queue.length; i++) {
581
+ newPlayer.queue.add(queue[i])
607
582
  }
608
583
 
609
- return;
610
- } catch (error) {
611
- console.error('Reconnection failed:', error)
612
- this.aqua.emit('socketClosed', player, payload)
613
- this.aqua.emit('reconnectionFailed', player, { error, code, payload })
584
+ await newPlayer.play()
585
+
586
+ if (savedState.position > 5000) {
587
+ setTimeout(() => newPlayer.seek(savedState.position), 1000)
588
+ }
589
+
590
+ if (savedState.paused) {
591
+ setTimeout(() => newPlayer.pause(true), 1500)
592
+ }
614
593
  }
615
- return;
616
- }
617
594
 
618
- this.aqua.emit('socketClosed', player, payload)
595
+ this.aqua.emit('playerReconnected', newPlayer, {
596
+ oldPlayer: player,
597
+ restoredState: savedState
598
+ })
599
+ } catch (error) {
600
+ console.error('Reconnection failed:', error)
601
+ this.aqua.emit('reconnectionFailed', player, {
602
+ error,
603
+ code: payload.code,
604
+ payload
605
+ })
606
+ this.aqua.emit('socketClosed', player, payload)
607
+ }
619
608
  }
620
609
 
621
610
  async lyricsLine(player, track, payload) {
@@ -630,21 +619,13 @@ class Player extends EventEmitter {
630
619
  this.aqua.emit('lyricsNotFound', player, track, payload)
631
620
  }
632
621
 
633
- send(data) {
634
- this.aqua.send({ op: 4, d: data })
635
- }
636
-
637
- set(key, value) {
638
- this._dataStore.set(key, value)
639
- }
640
-
641
- get(key) {
642
- return this._dataStore.get(key)
643
- }
622
+ send(data) { this.aqua.send({ op: 4, d: data }) }
623
+ set(key, value) { this._dataStore.set(key, value) }
624
+ get(key) { return this._dataStore.get(key) }
644
625
 
645
626
  clearData() {
646
- this.previousTracks.clear()
647
- this._dataStore.clear()
627
+ this.previousTracks?.clear()
628
+ this._dataStore?.clear()
648
629
  return this
649
630
  }
650
631
 
@@ -653,9 +634,7 @@ class Player extends EventEmitter {
653
634
  }
654
635
 
655
636
  async cleanup() {
656
- if (!this.playing && !this.paused && this.queue.isEmpty()) {
657
- this.destroy()
658
- }
637
+ if (!this.playing && !this.paused && this.queue.isEmpty()) this.destroy()
659
638
  }
660
639
  }
661
640