aqualink 2.19.0 → 2.20.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.
@@ -1,6 +1,4 @@
1
- 'use strict'
2
-
3
- const { EventEmitter } = require('tseep')
1
+ const { EventEmitter } = require('node:events')
4
2
  const { AqualinkEvents } = require('./AqualinkEvents')
5
3
  const Connection = require('./Connection')
6
4
  const Filters = require('./Filters')
@@ -41,10 +39,10 @@ const WATCHDOG_INTERVAL = 15000
41
39
  const VOICE_DOWN_THRESHOLD = 10000
42
40
  const VOICE_ABANDON_MULTIPLIER = 12
43
41
  const RECONNECT_MAX = 15
44
- const RESUME_TIMEOUT = 5000
45
42
  const MUTE_TOGGLE_DELAY = 300
46
43
  const SEEK_DELAY = 800
47
44
  const PAUSE_DELAY = 1200
45
+ const VOICE_TRACE_INTERVAL = 15000
48
46
  const RETRY_BACKOFF_BASE = 1500
49
47
  const RETRY_BACKOFF_MAX = 5000
50
48
  const PREVIOUS_TRACKS_SIZE = 50
@@ -56,7 +54,7 @@ const INVALID_LOADS = new Set(['error', 'empty', 'LOAD_FAILED', 'NO_MATCHES'])
56
54
  const _functions = {
57
55
  clamp(v) {
58
56
  const n = +v
59
- return Number.isNaN(n) ? 100 : n < 0 ? 0 : n > 200 ? 200 : n
57
+ return Number.isNaN(n) ? 100 : n < 0 ? 0 : n > 1000 ? 1000 : n
60
58
  },
61
59
  randIdx: (len) => (Math.random() * len) | 0,
62
60
  toId: (v) => v?.id || v || null,
@@ -216,6 +214,8 @@ class Player extends EventEmitter {
216
214
  this._voiceRequestAt = 0
217
215
  this._voiceRequestChannel = null
218
216
  this._suppressResumeUntil = 0
217
+ this._deferredStart = false
218
+ this._lastVoiceUpTraceAt = 0
219
219
  this._bindEvents()
220
220
  this._startWatchdog()
221
221
  }
@@ -257,16 +257,34 @@ class Player extends EventEmitter {
257
257
  _handlePlayerUpdate(packet) {
258
258
  if (this.destroyed || !packet?.state) return
259
259
  const s = packet.state
260
+ const wasConnected = this.connected
260
261
  this.position = _functions.isNum(s.position) ? s.position : 0
261
262
  this.connected = !!s.connected
262
263
  this.ping = _functions.isNum(s.ping) ? s.ping : 0
263
264
  this.timestamp = _functions.isNum(s.time) ? s.time : Date.now()
264
265
 
265
266
  if (!this.connected) {
266
- if (!this._voiceDownSince) {
267
+ if (wasConnected || !this._voiceDownSince) {
268
+ this.aqua?._trace?.('player.voice.down', {
269
+ guildId: this.guildId,
270
+ reconnecting: !!this._reconnecting,
271
+ recovering: !!this._voiceRecovering
272
+ })
273
+ }
274
+ if (
275
+ !this._voiceDownSince &&
276
+ !this._reconnecting &&
277
+ !this._voiceRecovering
278
+ ) {
267
279
  this._voiceDownSince = Date.now()
268
280
  this._createTimer(() => {
269
- if (this.connected || this.destroyed || this.nodes?.info?.isNodelink)
281
+ if (
282
+ this.connected ||
283
+ this.destroyed ||
284
+ this._reconnecting ||
285
+ this._voiceRecovering ||
286
+ this.nodes?.info?.isNodelink
287
+ )
270
288
  return
271
289
  this.connection.attemptResume()
272
290
  }, 1000)
@@ -274,6 +292,17 @@ class Player extends EventEmitter {
274
292
  } else {
275
293
  this._voiceDownSince = 0
276
294
  this.state = PLAYER_STATE.READY
295
+ const now = Date.now()
296
+ if (
297
+ !wasConnected ||
298
+ now - this._lastVoiceUpTraceAt >= VOICE_TRACE_INTERVAL
299
+ ) {
300
+ this._lastVoiceUpTraceAt = now
301
+ this.aqua?._trace?.('player.voice.up', {
302
+ guildId: this.guildId,
303
+ ping: this.ping
304
+ })
305
+ }
277
306
  }
278
307
 
279
308
  this.aqua.emit(AqualinkEvents.PlayerUpdate, this, packet)
@@ -291,7 +320,11 @@ class Player extends EventEmitter {
291
320
  return
292
321
  }
293
322
  try {
294
- await this[handler](this, this.current, payload)
323
+ const trackArg =
324
+ payload.type === 'TrackStartEvent'
325
+ ? payload.track || this.current
326
+ : this.current
327
+ await this[handler](this, trackArg, payload)
295
328
  } catch (error) {
296
329
  this.aqua.emit(AqualinkEvents.Error, error)
297
330
  }
@@ -331,23 +364,48 @@ class Player extends EventEmitter {
331
364
  if (!item) return this
332
365
 
333
366
  try {
334
- this.current = item.track ? item : await item.resolve(this.aqua)
367
+ let resolvedItem = null
368
+ if (item?.track) {
369
+ resolvedItem = item
370
+ } else if (typeof item?.resolve === 'function') {
371
+ resolvedItem = await item.resolve(this.aqua)
372
+ }
373
+ this.current = resolvedItem
335
374
  if (this.destroyed) return this
336
375
  if (!this.current?.track) throw new Error('Failed to resolve track')
337
376
 
338
377
  this.playing = true
339
378
  this.paused = !!options.paused
340
379
  this.position = options.startTime || 0
380
+ this.aqua?._trace?.('player.play', {
381
+ guildId: this.guildId,
382
+ paused: this.paused,
383
+ startTime: this.position,
384
+ hasTrack: !!this.current?.track
385
+ })
341
386
 
342
387
  if (this.destroyed || !this._updateBatcher) return this
343
388
 
389
+ if (
390
+ this.aqua?.autoRegionMigrate &&
391
+ !this._resuming &&
392
+ !this.connection?.endpoint
393
+ ) {
394
+ this._deferredStart = true
395
+ this.aqua?._trace?.('player.play.deferred', {
396
+ guildId: this.guildId,
397
+ reason: 'awaiting_voice_server_update'
398
+ })
399
+ return this
400
+ }
401
+
344
402
  const updateData = {
345
- guildId: this.guildId,
346
403
  track: { encoded: this.current.track },
347
- paused: this.paused,
404
+ paused: this.paused
348
405
  }
349
406
  if (this.position > 0) updateData.position = this.position
350
407
 
408
+ this._deferredStart = false
351
409
  await this.batchUpdatePlayer(updateData, true)
352
410
  } catch (error) {
353
411
  if (!this.destroyed) this.aqua?.emit(AqualinkEvents.Error, error)
@@ -379,6 +437,13 @@ class Player extends EventEmitter {
379
437
  self_deaf: this.deaf,
380
438
  self_mute: this.mute
381
439
  })
440
+ this.aqua?._trace?.('player.connect.request', {
441
+ guildId: this.guildId,
442
+ txId: this.txId,
443
+ voiceChannel,
444
+ deaf: this.deaf,
445
+ mute: this.mute
446
+ })
382
447
  return this
383
448
  }
384
449
 
@@ -387,7 +452,9 @@ class Player extends EventEmitter {
387
452
  this.nodes?.info?.isNodelink ||
388
453
  this.destroyed ||
389
454
  !this.voiceChannel ||
390
- this.connected
455
+ this.connected ||
456
+ this._reconnecting ||
457
+ this._voiceRecovering
391
458
  )
392
459
  return false
393
460
  if (
@@ -395,7 +462,7 @@ class Player extends EventEmitter {
395
462
  Date.now() - this._voiceDownSince < VOICE_DOWN_THRESHOLD
396
463
  )
397
464
  return false
398
- return !this._voiceRecovering && this.reconnectionRetries < RECONNECT_MAX
465
+ return this.reconnectionRetries < RECONNECT_MAX
399
466
  }
400
467
 
401
468
  async _voiceWatchdog() {
@@ -457,6 +524,12 @@ class Player extends EventEmitter {
457
524
 
458
525
  if (!this.destroyed) {
459
526
  this.destroyed = true
527
+ this.aqua?._trace?.('player.destroy', {
528
+ guildId: this.guildId,
529
+ skipRemote: !!skipRemote,
530
+ preserveTracks: !!preserveTracks,
531
+ preserveReconnecting: !!preserveReconnecting
532
+ })
460
533
  this.emit('destroy')
461
534
  }
462
535
 
@@ -475,10 +548,12 @@ class Player extends EventEmitter {
475
548
  }
476
549
 
477
550
  this.connected = this.playing = this.paused = this.isAutoplay = false
551
+ this._deferredStart = false
478
552
  this.state = PLAYER_STATE.DESTROYED
479
553
  this.autoplayRetries = this.reconnectionRetries = 0
480
554
  if (!preserveReconnecting) this._reconnecting = false
481
555
  this._lastVoiceChannel = this.voiceChannel
556
+ this._lastTextChannel = this.textChannel
482
557
  this.voiceChannel = null
483
558
 
484
559
  if (
@@ -541,10 +616,7 @@ class Player extends EventEmitter {
541
616
  pause(paused) {
542
617
  if (this.destroyed || this.paused === !!paused) return this
543
618
  this.paused = !!paused
544
- this.batchUpdatePlayer(
545
- { guildId: this.guildId, paused: this.paused },
546
- true
547
- ).catch(() => {})
619
+ this.batchUpdatePlayer({ paused: this.paused }, true).catch(() => {})
548
620
  return this
549
621
  }
550
622
 
@@ -556,10 +628,7 @@ class Player extends EventEmitter {
556
628
  ? Math.min(Math.max(position, 0), len)
557
629
  : Math.max(position, 0)
558
630
  this.position = clamped
559
- this.batchUpdatePlayer(
560
- { guildId: this.guildId, position: clamped },
561
- true
562
- ).catch(() => {})
631
+ this.batchUpdatePlayer({ position: clamped }, true).catch(() => {})
563
632
  return this
564
633
  }
565
634
 
@@ -611,7 +680,7 @@ class Player extends EventEmitter {
611
680
  this.playing = this.paused = false
612
681
  this.position = 0
613
682
  this.batchUpdatePlayer(
614
- { guildId: this.guildId, track: { encoded: null, paused: this.paused } },
683
+ { track: { encoded: null }, paused: this.paused },
615
684
  true
616
685
  ).catch(() => {})
617
686
  return this
@@ -621,9 +690,7 @@ class Player extends EventEmitter {
621
690
  const vol = _functions.clamp(volume)
622
691
  if (this.destroyed || this.volume === vol) return this
623
692
  this.volume = vol
624
- this.batchUpdatePlayer({ guildId: this.guildId, volume: vol }).catch(
625
- () => {}
626
- )
693
+ this.batchUpdatePlayer({ volume: vol }).catch(() => {})
627
694
  return this
628
695
  }
629
696
 
@@ -640,9 +707,7 @@ class Player extends EventEmitter {
640
707
  const id = _functions.toId(channel)
641
708
  if (!id) throw new TypeError('Invalid text channel')
642
709
  this.textChannel = id
643
- this.batchUpdatePlayer({ guildId: this.guildId, text_channel: id }).catch(
644
- () => {}
645
- )
710
+ this.batchUpdatePlayer({ text_channel: id }).catch(() => {})
646
711
  return this
647
712
  }
648
713
 
@@ -728,7 +793,7 @@ class Player extends EventEmitter {
728
793
  this.destroyed ||
729
794
  !this.isAutoplayEnabled ||
730
795
  !this.previous ||
731
- (this.queue && this.queue.size)
796
+ this.queue?.size
732
797
  )
733
798
  return this
734
799
  const prev = this.previous
@@ -831,22 +896,26 @@ class Player extends EventEmitter {
831
896
  return null
832
897
  }
833
898
 
834
- trackStart(player, track, payload = {}) {
899
+ trackStart(_player, _track, payload = {}) {
835
900
  if (this.destroyed) return
901
+ const startedTrack = this.current || _track
902
+ if (!startedTrack) return
903
+ if (!this.current) this.current = startedTrack
836
904
  this.playing = true
837
905
  this.paused = false
838
- this.aqua.emit(AqualinkEvents.TrackStart, this, this.current, {
906
+ this.aqua.emit(AqualinkEvents.TrackStart, this, startedTrack, {
839
907
  ...payload,
840
908
  resumed: this._resuming
841
909
  })
842
910
  this._resuming = false
843
911
  }
844
912
 
845
- async trackEnd(player, track, payload) {
913
+ async trackEnd(_player, track, payload) {
846
914
  if (this.destroyed) return
847
915
 
848
916
  const reason = payload?.reason
849
- const isFailure = reason === 'loadFailed' || reason === 'cleanup'
917
+ const isFailure = reason === 'loadFailed'
918
+ const isCleanup = reason === 'cleanup'
850
919
  const isReplaced = reason === 'replaced'
851
920
 
852
921
  if (track) this.previousTracks.push(track)
@@ -854,8 +923,8 @@ class Player extends EventEmitter {
854
923
  _functions.safeDel(this.nowPlayingMessage)
855
924
  if (!isReplaced) this.current = null
856
925
 
857
- if (isFailure) {
858
- if (!this.queue.size) {
926
+ if (isFailure || isCleanup) {
927
+ if (!this.queue.size || isCleanup) {
859
928
  this.clearData({ preserveTracks: this._reconnecting || this._resuming })
860
929
  this.aqua.emit(AqualinkEvents.QueueEnd, this)
861
930
  } else {
@@ -873,8 +942,9 @@ class Player extends EventEmitter {
873
942
  this.queue.add(track)
874
943
  }
875
944
 
876
- if (this.queue.size && !isReplaced) {
877
- this.aqua.emit(AqualinkEvents.TrackEnd, this, track, reason)
945
+ if (this.queue.size) {
946
+ if (!isReplaced)
947
+ this.aqua.emit(AqualinkEvents.TrackEnd, this, track, reason)
878
948
  await this.play()
879
949
  } else if (this.isAutoplayEnabled && !isReplaced) {
880
950
  await this.autoplay()
@@ -888,55 +958,55 @@ class Player extends EventEmitter {
888
958
  }
889
959
  }
890
960
 
891
- trackError(player, track, payload) {
961
+ trackError(_player, track, payload) {
892
962
  if (this.destroyed) return
893
963
  this.aqua.emit(AqualinkEvents.TrackError, this, track, payload)
894
964
  this.stop()
895
965
  }
896
966
 
897
- trackStuck(player, track, payload) {
967
+ trackStuck(_player, track, payload) {
898
968
  if (this.destroyed) return
899
969
  this.aqua.emit(AqualinkEvents.TrackStuck, this, track, payload)
900
970
  this.stop()
901
971
  }
902
972
 
903
- trackChange(p, t, payload) {
973
+ trackChange(_p, t, payload) {
904
974
  _functions.emitIfActive(this, AqualinkEvents.TrackChange, t, payload)
905
975
  }
906
- lyricsLine(p, t, payload) {
976
+ lyricsLine(_p, t, payload) {
907
977
  _functions.emitIfActive(this, AqualinkEvents.LyricsLine, t, payload)
908
978
  }
909
- volumeChanged(p, t, payload) {
979
+ volumeChanged(_p, t, payload) {
910
980
  _functions.emitIfActive(this, AqualinkEvents.VolumeChanged, t, payload)
911
981
  }
912
- filtersChanged(p, t, payload) {
982
+ filtersChanged(_p, t, payload) {
913
983
  _functions.emitIfActive(this, AqualinkEvents.FiltersChanged, t, payload)
914
984
  }
915
- seekEvent(p, t, payload) {
985
+ seekEvent(_p, t, payload) {
916
986
  _functions.emitIfActive(this, AqualinkEvents.Seek, t, payload)
917
987
  }
918
- lyricsFound(p, t, payload) {
988
+ lyricsFound(_p, t, payload) {
919
989
  _functions.emitIfActive(this, AqualinkEvents.LyricsFound, t, payload)
920
990
  }
921
- lyricsNotFound(p, t, payload) {
991
+ lyricsNotFound(_p, t, payload) {
922
992
  _functions.emitIfActive(this, AqualinkEvents.LyricsNotFound, t, payload)
923
993
  }
924
- playerCreated(p, t, payload) {
994
+ playerCreated(_p, _t, payload) {
925
995
  _functions.emitIfActive(this, AqualinkEvents.PlayerCreated, payload)
926
996
  }
927
- playerConnected(p, t, payload) {
997
+ playerConnected(_p, _t, payload) {
928
998
  _functions.emitIfActive(this, AqualinkEvents.PlayerConnected, payload)
929
999
  }
930
- playerDestroyed(p, t, payload) {
1000
+ playerDestroyed(_p, _t, payload) {
931
1001
  _functions.emitIfActive(this, AqualinkEvents.PlayerDestroyed, payload)
932
1002
  }
933
- pauseEvent(p, t, payload) {
1003
+ pauseEvent(_p, _t, payload) {
934
1004
  _functions.emitIfActive(this, AqualinkEvents.PauseEvent, payload)
935
1005
  }
936
- mixStarted(p, t, payload) {
1006
+ mixStarted(_p, t, payload) {
937
1007
  _functions.emitIfActive(this, AqualinkEvents.MixStarted, t, payload)
938
1008
  }
939
- mixEnded(p, t, payload) {
1009
+ mixEnded(_p, t, payload) {
940
1010
  _functions.emitIfActive(this, AqualinkEvents.MixEnded, t, payload)
941
1011
  }
942
1012
 
@@ -946,19 +1016,35 @@ class Player extends EventEmitter {
946
1016
  throw new Error('Resume failed')
947
1017
  }
948
1018
 
949
- async socketClosed(player, track, payload) {
950
- if (this.destroyed) return
1019
+ async socketClosed(_player, _track, payload) {
1020
+ if (this.destroyed || this._reconnecting) return
1021
+ this.aqua?._trace?.('player.socketClosed', {
1022
+ guildId: this.guildId,
1023
+ code: payload?.code
1024
+ })
1025
+
951
1026
  const code = payload?.code
1027
+ if (code === 4006 && this._resuming) {
1028
+ this.aqua?._trace?.('player.socketClosed.ignored', {
1029
+ guildId: this.guildId,
1030
+ code,
1031
+ reason: 'transient_while_resuming'
1032
+ })
1033
+ return
1034
+ }
1035
+
952
1036
  let isRecoverable = [4015, 4009, 4006, 4014, 4022].includes(code)
953
1037
  if (code === 4014 && this.connection?.isWaitingForDisconnect)
954
1038
  isRecoverable = false
955
1039
 
956
1040
  if (code === 4015 && !this.nodes?.info?.isNodelink) {
1041
+ this._reconnecting = true
957
1042
  try {
958
1043
  await this._attemptVoiceResume()
1044
+ this._reconnecting = false
959
1045
  return
960
1046
  } catch {
961
- /* ignore */
1047
+ this._reconnecting = false
962
1048
  }
963
1049
  }
964
1050
 
@@ -974,8 +1060,6 @@ class Player extends EventEmitter {
974
1060
  if (code === 4022) this._suppressResumeUntil = Date.now() + 3000
975
1061
  }
976
1062
 
977
- if (this._reconnecting) return
978
-
979
1063
  const aqua = this.aqua
980
1064
  const vcId = _functions.toId(this.voiceChannel)
981
1065
  const tcId = _functions.toId(this.textChannel)
@@ -1102,7 +1186,10 @@ class Player extends EventEmitter {
1102
1186
 
1103
1187
  set(key, value) {
1104
1188
  if (this.destroyed) return
1105
- ;(this._dataStore || (this._dataStore = new Map())).set(key, value)
1189
+ if (!this._dataStore) {
1190
+ this._dataStore = new Map()
1191
+ }
1192
+ this._dataStore.set(key, value)
1106
1193
  }
1107
1194
 
1108
1195
  get(key) {
@@ -1125,6 +1212,27 @@ class Player extends EventEmitter {
1125
1212
  return this.nodes.rest.updatePlayer({ guildId: this.guildId, data })
1126
1213
  }
1127
1214
 
1215
+ _flushDeferredPlay() {
1216
+ if (
1217
+ !this._deferredStart ||
1218
+ this.destroyed ||
1219
+ !this.current?.track ||
1220
+ !this._updateBatcher
1221
+ )
1222
+ return
1223
+ this._deferredStart = false
1224
+ const updateData = {
1225
+ track: { encoded: this.current.track },
1226
+ paused: this.paused
1227
+ }
1228
+ if (this.position > 0) updateData.position = this.position
1229
+ this.aqua?._trace?.('player.play.deferred.flush', {
1230
+ guildId: this.guildId,
1231
+ hasEndpoint: !!this.connection?.endpoint
1232
+ })
1233
+ this.batchUpdatePlayer(updateData, true).catch(() => {})
1234
+ }
1235
+
1128
1236
  cleanup() {
1129
1237
  if (!this.playing && !this.paused && !this.queue?.size) this.destroy()
1130
1238
  }
@@ -3,8 +3,8 @@ class Plugin {
3
3
  this.name = name
4
4
  }
5
5
 
6
- load(aqua) {}
7
- unload(aqua) {}
6
+ load(_aqua) {}
7
+ unload(_aqua) {}
8
8
  }
9
9
 
10
10
  module.exports = Plugin
@@ -1,5 +1,3 @@
1
- 'use strict'
2
-
3
1
  class Queue {
4
2
  constructor() {
5
3
  this._items = []
@@ -40,12 +38,19 @@ class Queue {
40
38
  this._head = 0
41
39
  }
42
40
 
43
- shuffle() {
44
- // Compact first if needed
45
- if (this._head > 0) {
46
- this._items = this._items.slice(this._head)
47
- this._head = 0
41
+ _compact(force = false) {
42
+ if (this._head <= 0) return
43
+ if (!force && this._head <= this._items.length / 2) return
44
+ const len = this._items.length - this._head
45
+ for (let i = 0; i < len; i++) {
46
+ this._items[i] = this._items[this._head + i]
48
47
  }
48
+ this._items.length = len
49
+ this._head = 0
50
+ }
51
+
52
+ shuffle() {
53
+ this._compact(true)
49
54
  for (let i = this._items.length - 1; i > 0; i--) {
50
55
  const j = Math.floor(Math.random() * (i + 1))
51
56
  const temp = this._items[i]
@@ -103,11 +108,7 @@ class Queue {
103
108
  const item = this._items[this._head]
104
109
  this._items[this._head] = undefined // Allow GC
105
110
  this._head++
106
- // Compact when head is > 50% of array to prevent unbounded growth
107
- if (this._head > 0 && this._head > this._items.length / 2) {
108
- this._items = this._items.slice(this._head)
109
- this._head = 0
110
- }
111
+ this._compact(false)
111
112
  return item
112
113
  }
113
114