aqualink 2.20.1 → 3.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/README.md +174 -184
- package/build/handlers/autoplay.js +5 -1
- package/build/structures/Aqua.js +226 -539
- package/build/structures/AquaRecovery.js +901 -0
- package/build/structures/Connection.js +72 -261
- package/build/structures/ConnectionRecovery.js +398 -0
- package/build/structures/Filters.js +93 -12
- package/build/structures/Node.js +93 -54
- package/build/structures/Player.js +283 -326
- package/build/structures/PlayerLifecycle.js +575 -0
- package/build/structures/PlayerLifecycleState.js +42 -0
- package/build/structures/Reporting.js +32 -0
- package/build/structures/Rest.js +25 -2
- package/build/structures/Track.js +2 -2
- package/package.json +1 -1
|
@@ -2,6 +2,9 @@ const { EventEmitter } = require('node:events')
|
|
|
2
2
|
const { AqualinkEvents } = require('./AqualinkEvents')
|
|
3
3
|
const Connection = require('./Connection')
|
|
4
4
|
const Filters = require('./Filters')
|
|
5
|
+
const PlayerLifecycle = require('./PlayerLifecycle')
|
|
6
|
+
const { attachPlayerLifecycleState } = require('./PlayerLifecycleState')
|
|
7
|
+
const { reportSuppressedError } = require('./Reporting')
|
|
5
8
|
const { spAutoPlay, scAutoPlay } = require('../handlers/autoplay')
|
|
6
9
|
const Queue = require('./Queue')
|
|
7
10
|
|
|
@@ -43,6 +46,8 @@ const MUTE_TOGGLE_DELAY = 300
|
|
|
43
46
|
const SEEK_DELAY = 800
|
|
44
47
|
const PAUSE_DELAY = 1200
|
|
45
48
|
const VOICE_TRACE_INTERVAL = 15000
|
|
49
|
+
const PLAYER_UPDATE_SILENCE_THRESHOLD = 45000
|
|
50
|
+
const VOICE_FORCE_DESTROY_MS = 15 * 60 * 1000
|
|
46
51
|
const RETRY_BACKOFF_BASE = 1500
|
|
47
52
|
const RETRY_BACKOFF_MAX = 5000
|
|
48
53
|
const PREVIOUS_TRACKS_SIZE = 50
|
|
@@ -75,6 +80,20 @@ const _functions = {
|
|
|
75
80
|
for (const t of set) clearTimeout(t)
|
|
76
81
|
set.clear()
|
|
77
82
|
},
|
|
83
|
+
safeCall(fn) {
|
|
84
|
+
try {
|
|
85
|
+
return fn?.()
|
|
86
|
+
} catch {}
|
|
87
|
+
return null
|
|
88
|
+
},
|
|
89
|
+
emitAquaError(aqua, error) {
|
|
90
|
+
if (!aqua?.listenerCount) return
|
|
91
|
+
try {
|
|
92
|
+
if (aqua.listenerCount(AqualinkEvents.Error) > 0) {
|
|
93
|
+
aqua.emit(AqualinkEvents.Error, error)
|
|
94
|
+
}
|
|
95
|
+
} catch {}
|
|
96
|
+
},
|
|
78
97
|
emitIfActive(player, event, ...args) {
|
|
79
98
|
if (!player.destroyed) player.aqua.emit(event, player, ...args)
|
|
80
99
|
}
|
|
@@ -105,11 +124,10 @@ class MicrotaskUpdateBatcher {
|
|
|
105
124
|
this.scheduled = false
|
|
106
125
|
if (!u || !p) return Promise.resolve()
|
|
107
126
|
return p.updatePlayer(u).catch((err) => {
|
|
108
|
-
|
|
109
|
-
|
|
127
|
+
_functions.emitAquaError(
|
|
128
|
+
p.aqua,
|
|
110
129
|
new Error(`Update error: ${err.message}`)
|
|
111
130
|
)
|
|
112
|
-
throw err
|
|
113
131
|
})
|
|
114
132
|
}
|
|
115
133
|
|
|
@@ -190,11 +208,11 @@ class Player extends EventEmitter {
|
|
|
190
208
|
this.mute = !!options.mute
|
|
191
209
|
this.autoplayRetries = this.reconnectionRetries = 0
|
|
192
210
|
this._voiceDownSince = 0
|
|
193
|
-
this
|
|
194
|
-
this._resuming = !!options.resuming
|
|
211
|
+
attachPlayerLifecycleState(this, { resuming: !!options.resuming })
|
|
195
212
|
this._voiceWatchdogTimer = null
|
|
196
213
|
this._pendingTimers = new Set()
|
|
197
214
|
this._reconnectTimers = null
|
|
215
|
+
this._reconnectNonce = 0
|
|
198
216
|
this._dataStore = null
|
|
199
217
|
|
|
200
218
|
this.volume = _functions.clamp(options.defaultVolume || 100)
|
|
@@ -210,12 +228,30 @@ class Player extends EventEmitter {
|
|
|
210
228
|
this.previousIdentifiers = new Set()
|
|
211
229
|
this.previousTracks = new CircularBuffer(PREVIOUS_TRACKS_SIZE)
|
|
212
230
|
this._updateBatcher = batcherPool.acquire(this)
|
|
231
|
+
this._lifecycleController = new PlayerLifecycle(this, {
|
|
232
|
+
_functions,
|
|
233
|
+
PLAYER_STATE,
|
|
234
|
+
VOICE_TRACE_INTERVAL,
|
|
235
|
+
PLAYER_UPDATE_SILENCE_THRESHOLD,
|
|
236
|
+
VOICE_DOWN_THRESHOLD,
|
|
237
|
+
VOICE_ABANDON_MULTIPLIER,
|
|
238
|
+
VOICE_FORCE_DESTROY_MS,
|
|
239
|
+
RECONNECT_MAX,
|
|
240
|
+
MUTE_TOGGLE_DELAY,
|
|
241
|
+
SEEK_DELAY,
|
|
242
|
+
PAUSE_DELAY,
|
|
243
|
+
RETRY_BACKOFF_BASE,
|
|
244
|
+
RETRY_BACKOFF_MAX
|
|
245
|
+
})
|
|
213
246
|
|
|
214
247
|
this._voiceRequestAt = 0
|
|
215
248
|
this._voiceRequestChannel = null
|
|
216
249
|
this._suppressResumeUntil = 0
|
|
217
|
-
this._deferredStart = false
|
|
218
250
|
this._lastVoiceUpTraceAt = 0
|
|
251
|
+
this._lastPlayerUpdateAt = Date.now()
|
|
252
|
+
this._voiceRecoverySeq = 0
|
|
253
|
+
this._activeVoiceRecoveryToken = 0
|
|
254
|
+
this._voiceRecoveryReason = null
|
|
219
255
|
this._bindEvents()
|
|
220
256
|
this._startWatchdog()
|
|
221
257
|
}
|
|
@@ -252,58 +288,26 @@ class Player extends EventEmitter {
|
|
|
252
288
|
return new Promise((r) => this._createTimer(r, ms))
|
|
253
289
|
}
|
|
254
290
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
if (
|
|
273
|
-
!this._voiceDownSince &&
|
|
274
|
-
!this._reconnecting &&
|
|
275
|
-
!this._voiceRecovering
|
|
276
|
-
) {
|
|
277
|
-
this._voiceDownSince = Date.now()
|
|
278
|
-
this._createTimer(() => {
|
|
279
|
-
if (
|
|
280
|
-
this.connected ||
|
|
281
|
-
this.destroyed ||
|
|
282
|
-
this._reconnecting ||
|
|
283
|
-
this._voiceRecovering ||
|
|
284
|
-
this.nodes?.info?.isNodelink
|
|
285
|
-
)
|
|
286
|
-
return
|
|
287
|
-
this.connection.attemptResume()
|
|
288
|
-
}, 1000)
|
|
289
|
-
}
|
|
290
|
-
} else {
|
|
291
|
-
this._voiceDownSince = 0
|
|
292
|
-
this.state = PLAYER_STATE.READY
|
|
293
|
-
const now = Date.now()
|
|
294
|
-
if (
|
|
295
|
-
!wasConnected ||
|
|
296
|
-
now - this._lastVoiceUpTraceAt >= VOICE_TRACE_INTERVAL
|
|
297
|
-
) {
|
|
298
|
-
this._lastVoiceUpTraceAt = now
|
|
299
|
-
this.aqua?._trace?.('player.voice.up', {
|
|
300
|
-
guildId: this.guildId,
|
|
301
|
-
ping: this.ping
|
|
302
|
-
})
|
|
303
|
-
}
|
|
304
|
-
}
|
|
291
|
+
_claimVoiceRecovery(reason = 'unknown') {
|
|
292
|
+
const token = ++this._voiceRecoverySeq
|
|
293
|
+
this._activeVoiceRecoveryToken = token
|
|
294
|
+
this._voiceRecoveryReason = reason
|
|
295
|
+
return token
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
_isVoiceRecoveryActive(token) {
|
|
299
|
+
return !!token && !this.destroyed && this._activeVoiceRecoveryToken === token
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
_clearVoiceRecovery(token = this._activeVoiceRecoveryToken, reason = null) {
|
|
303
|
+
if (!token || this._activeVoiceRecoveryToken !== token) return false
|
|
304
|
+
this._activeVoiceRecoveryToken = 0
|
|
305
|
+
this._voiceRecoveryReason = reason
|
|
306
|
+
return true
|
|
307
|
+
}
|
|
305
308
|
|
|
306
|
-
|
|
309
|
+
_handlePlayerUpdate(packet) {
|
|
310
|
+
return this._lifecycleController.handlePlayerUpdate(packet)
|
|
307
311
|
}
|
|
308
312
|
|
|
309
313
|
async _handleEvent(payload) {
|
|
@@ -318,13 +322,9 @@ class Player extends EventEmitter {
|
|
|
318
322
|
return
|
|
319
323
|
}
|
|
320
324
|
try {
|
|
321
|
-
|
|
322
|
-
payload.type === 'TrackStartEvent'
|
|
323
|
-
? payload.track || this.current
|
|
324
|
-
: this.current
|
|
325
|
-
await this[handler](this, trackArg, payload)
|
|
325
|
+
await this[handler](this, this.current, payload)
|
|
326
326
|
} catch (error) {
|
|
327
|
-
this.aqua
|
|
327
|
+
_functions.emitAquaError(this.aqua, error)
|
|
328
328
|
}
|
|
329
329
|
}
|
|
330
330
|
|
|
@@ -370,30 +370,85 @@ class Player extends EventEmitter {
|
|
|
370
370
|
}
|
|
371
371
|
this.current = resolvedItem
|
|
372
372
|
if (this.destroyed) return this
|
|
373
|
-
if (!this.current?.track)
|
|
373
|
+
if (!this.current?.track) {
|
|
374
|
+
this.current = null
|
|
375
|
+
this.playing = false
|
|
376
|
+
if (this.aqua?.debugTrace) {
|
|
377
|
+
this.aqua._trace('player.play.unresolved', {
|
|
378
|
+
guildId: this.guildId,
|
|
379
|
+
reconnecting: !!this._reconnecting,
|
|
380
|
+
resuming: !!this._resuming,
|
|
381
|
+
voiceRecovering: !!this._voiceRecovering
|
|
382
|
+
})
|
|
383
|
+
}
|
|
384
|
+
if (this._reconnecting || this._resuming || this._voiceRecovering)
|
|
385
|
+
return this
|
|
386
|
+
throw new Error('Failed to resolve track')
|
|
387
|
+
}
|
|
374
388
|
|
|
375
389
|
this.playing = true
|
|
376
390
|
this.paused = !!options.paused
|
|
377
391
|
this.position = options.startTime || 0
|
|
378
|
-
this.aqua?.
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
392
|
+
if (this.aqua?.debugTrace) {
|
|
393
|
+
this.aqua._trace('player.play', {
|
|
394
|
+
guildId: this.guildId,
|
|
395
|
+
paused: this.paused,
|
|
396
|
+
startTime: this.position,
|
|
397
|
+
hasTrack: !!this.current?.track
|
|
398
|
+
})
|
|
399
|
+
}
|
|
384
400
|
|
|
385
401
|
if (this.destroyed || !this._updateBatcher) return this
|
|
386
402
|
|
|
403
|
+
if (
|
|
404
|
+
this.voiceChannel &&
|
|
405
|
+
!this.connected &&
|
|
406
|
+
!this._reconnecting &&
|
|
407
|
+
!this._voiceRecovering
|
|
408
|
+
) {
|
|
409
|
+
this._deferredStart = true
|
|
410
|
+
const recoveryToken = this._claimVoiceRecovery('play_deferred')
|
|
411
|
+
if (this.aqua?.debugTrace) {
|
|
412
|
+
this.aqua._trace('player.play.deferred', {
|
|
413
|
+
guildId: this.guildId,
|
|
414
|
+
reason: 'voice_not_connected'
|
|
415
|
+
})
|
|
416
|
+
}
|
|
417
|
+
const now = Date.now()
|
|
418
|
+
if (
|
|
419
|
+
now - (this._voiceRequestAt || 0) >= 1200 &&
|
|
420
|
+
this._isVoiceRecoveryActive(recoveryToken)
|
|
421
|
+
) {
|
|
422
|
+
this._voiceRequestAt = now
|
|
423
|
+
if (this._isVoiceRecoveryActive(recoveryToken))
|
|
424
|
+
this.connection?._requestVoiceState?.()
|
|
425
|
+
if (this._isVoiceRecoveryActive(recoveryToken))
|
|
426
|
+
this.connection?.resendVoiceUpdate?.(true)
|
|
427
|
+
if (this._isVoiceRecoveryActive(recoveryToken))
|
|
428
|
+
_functions.safeCall(() =>
|
|
429
|
+
this.connect({
|
|
430
|
+
guildId: this.guildId,
|
|
431
|
+
voiceChannel: this.voiceChannel,
|
|
432
|
+
deaf: this.deaf,
|
|
433
|
+
mute: this.mute
|
|
434
|
+
})
|
|
435
|
+
)
|
|
436
|
+
}
|
|
437
|
+
return this
|
|
438
|
+
}
|
|
439
|
+
|
|
387
440
|
if (
|
|
388
441
|
this.aqua?.autoRegionMigrate &&
|
|
389
442
|
!this._resuming &&
|
|
390
443
|
!this.connection?.endpoint
|
|
391
444
|
) {
|
|
392
445
|
this._deferredStart = true
|
|
393
|
-
this.aqua?.
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
446
|
+
if (this.aqua?.debugTrace) {
|
|
447
|
+
this.aqua._trace('player.play.deferred', {
|
|
448
|
+
guildId: this.guildId,
|
|
449
|
+
reason: 'awaiting_voice_server_update'
|
|
450
|
+
})
|
|
451
|
+
}
|
|
397
452
|
return this
|
|
398
453
|
}
|
|
399
454
|
|
|
@@ -404,10 +459,26 @@ class Player extends EventEmitter {
|
|
|
404
459
|
if (this.position > 0) updateData.position = this.position
|
|
405
460
|
|
|
406
461
|
this._deferredStart = false
|
|
407
|
-
await this.batchUpdatePlayer(updateData, true)
|
|
462
|
+
await this.batchUpdatePlayer(updateData, true).catch((err) => {
|
|
463
|
+
if (!this.destroyed) _functions.emitAquaError(this.aqua, err)
|
|
464
|
+
})
|
|
408
465
|
} catch (error) {
|
|
409
|
-
if (
|
|
410
|
-
|
|
466
|
+
if (
|
|
467
|
+
!this.destroyed &&
|
|
468
|
+
!this._reconnecting &&
|
|
469
|
+
!this._resuming &&
|
|
470
|
+
!this._voiceRecovering
|
|
471
|
+
) {
|
|
472
|
+
_functions.emitAquaError(this.aqua, error)
|
|
473
|
+
}
|
|
474
|
+
if (
|
|
475
|
+
this.queue?.size &&
|
|
476
|
+
!track &&
|
|
477
|
+
!this._reconnecting &&
|
|
478
|
+
!this._resuming &&
|
|
479
|
+
!this._voiceRecovering
|
|
480
|
+
)
|
|
481
|
+
return this.play()
|
|
411
482
|
}
|
|
412
483
|
return this
|
|
413
484
|
}
|
|
@@ -429,19 +500,22 @@ class Player extends EventEmitter {
|
|
|
429
500
|
this._voiceRequestChannel = voiceChannel
|
|
430
501
|
|
|
431
502
|
this.voiceChannel = voiceChannel
|
|
503
|
+
this._voiceDownSince = 0
|
|
432
504
|
this.send({
|
|
433
505
|
guild_id: this.guildId,
|
|
434
506
|
channel_id: voiceChannel,
|
|
435
507
|
self_deaf: this.deaf,
|
|
436
508
|
self_mute: this.mute
|
|
437
509
|
})
|
|
438
|
-
this.aqua?.
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
510
|
+
if (this.aqua?.debugTrace) {
|
|
511
|
+
this.aqua._trace('player.connect.request', {
|
|
512
|
+
guildId: this.guildId,
|
|
513
|
+
txId: this.txId,
|
|
514
|
+
voiceChannel,
|
|
515
|
+
deaf: this.deaf,
|
|
516
|
+
mute: this.mute
|
|
517
|
+
})
|
|
518
|
+
}
|
|
445
519
|
return this
|
|
446
520
|
}
|
|
447
521
|
|
|
@@ -464,50 +538,7 @@ class Player extends EventEmitter {
|
|
|
464
538
|
}
|
|
465
539
|
|
|
466
540
|
async _voiceWatchdog() {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
const hasVoiceData =
|
|
470
|
-
this.connection?.sessionId &&
|
|
471
|
-
this.connection?.endpoint &&
|
|
472
|
-
this.connection?.token
|
|
473
|
-
if (!hasVoiceData) {
|
|
474
|
-
if (
|
|
475
|
-
Date.now() - this._voiceDownSince >
|
|
476
|
-
VOICE_DOWN_THRESHOLD * VOICE_ABANDON_MULTIPLIER
|
|
477
|
-
)
|
|
478
|
-
this.destroy()
|
|
479
|
-
return
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
this._voiceRecovering = true
|
|
483
|
-
try {
|
|
484
|
-
if (await this.connection.attemptResume()) {
|
|
485
|
-
this.reconnectionRetries = this._voiceDownSince = 0
|
|
486
|
-
return
|
|
487
|
-
}
|
|
488
|
-
const originalMute = this.mute
|
|
489
|
-
this.send({
|
|
490
|
-
guild_id: this.guildId,
|
|
491
|
-
channel_id: this.voiceChannel,
|
|
492
|
-
self_deaf: this.deaf,
|
|
493
|
-
self_mute: !originalMute
|
|
494
|
-
})
|
|
495
|
-
await this._delay(MUTE_TOGGLE_DELAY)
|
|
496
|
-
if (!this.destroyed) {
|
|
497
|
-
this.send({
|
|
498
|
-
guild_id: this.guildId,
|
|
499
|
-
channel_id: this.voiceChannel,
|
|
500
|
-
self_deaf: this.deaf,
|
|
501
|
-
self_mute: originalMute
|
|
502
|
-
})
|
|
503
|
-
}
|
|
504
|
-
this.connection.resendVoiceUpdate()
|
|
505
|
-
this.reconnectionRetries++
|
|
506
|
-
} catch {
|
|
507
|
-
if (++this.reconnectionRetries >= RECONNECT_MAX) this.destroy()
|
|
508
|
-
} finally {
|
|
509
|
-
this._voiceRecovering = false
|
|
510
|
-
}
|
|
541
|
+
return this._lifecycleController.voiceWatchdog()
|
|
511
542
|
}
|
|
512
543
|
|
|
513
544
|
destroy(options = {}) {
|
|
@@ -518,16 +549,29 @@ class Player extends EventEmitter {
|
|
|
518
549
|
preserveReconnecting = false,
|
|
519
550
|
preserveTracks = false
|
|
520
551
|
} = options
|
|
521
|
-
if (this.destroyed && !this.queue)
|
|
552
|
+
if (this.destroyed && !this.queue) {
|
|
553
|
+
this._reconnectNonce++
|
|
554
|
+
if (this._reconnectTimers) {
|
|
555
|
+
_functions.clearTimers(this._reconnectTimers)
|
|
556
|
+
this._reconnectTimers = null
|
|
557
|
+
}
|
|
558
|
+
this._reconnecting = false
|
|
559
|
+
this._isActivelyReconnecting = false
|
|
560
|
+
return this
|
|
561
|
+
}
|
|
522
562
|
|
|
523
563
|
if (!this.destroyed) {
|
|
564
|
+
this._reconnectNonce++
|
|
524
565
|
this.destroyed = true
|
|
525
|
-
this.
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
566
|
+
this._clearVoiceRecovery(undefined, 'destroyed')
|
|
567
|
+
if (this.aqua?.debugTrace) {
|
|
568
|
+
this.aqua._trace('player.destroy', {
|
|
569
|
+
guildId: this.guildId,
|
|
570
|
+
skipRemote: !!skipRemote,
|
|
571
|
+
preserveTracks: !!preserveTracks,
|
|
572
|
+
preserveReconnecting: !!preserveReconnecting
|
|
573
|
+
})
|
|
574
|
+
}
|
|
531
575
|
this.emit('destroy')
|
|
532
576
|
}
|
|
533
577
|
|
|
@@ -553,6 +597,7 @@ class Player extends EventEmitter {
|
|
|
553
597
|
this._lastVoiceChannel = this.voiceChannel
|
|
554
598
|
this._lastTextChannel = this.textChannel
|
|
555
599
|
this.voiceChannel = null
|
|
600
|
+
this._isActivelyReconnecting = false
|
|
556
601
|
|
|
557
602
|
if (
|
|
558
603
|
this.shouldDeleteMessage &&
|
|
@@ -574,6 +619,16 @@ class Player extends EventEmitter {
|
|
|
574
619
|
this._updateBatcher = null
|
|
575
620
|
}
|
|
576
621
|
|
|
622
|
+
if (this.filters) {
|
|
623
|
+
try {
|
|
624
|
+
this.filters.destroy()
|
|
625
|
+
} catch (error) {
|
|
626
|
+
reportSuppressedError(this, 'player.destroy.filters', error, {
|
|
627
|
+
guildId: this.guildId
|
|
628
|
+
})
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
577
632
|
this.previousTracks?.clear()
|
|
578
633
|
this.previousTracks = null
|
|
579
634
|
this.previousIdentifiers?.clear()
|
|
@@ -592,17 +647,34 @@ class Player extends EventEmitter {
|
|
|
592
647
|
if (this.connection) {
|
|
593
648
|
try {
|
|
594
649
|
this.connection.destroy()
|
|
595
|
-
} catch {
|
|
650
|
+
} catch (error) {
|
|
651
|
+
reportSuppressedError(this, 'player.destroy.connection', error, {
|
|
652
|
+
guildId: this.guildId
|
|
653
|
+
})
|
|
654
|
+
}
|
|
596
655
|
}
|
|
597
|
-
this.connection =
|
|
656
|
+
this.connection =
|
|
657
|
+
this.filters =
|
|
658
|
+
this.current =
|
|
659
|
+
this.autoplaySeed =
|
|
660
|
+
this._lifecycleController =
|
|
661
|
+
null
|
|
598
662
|
|
|
599
663
|
if (!skipRemote) {
|
|
600
664
|
try {
|
|
601
665
|
this.send({ guild_id: this.guildId, channel_id: null })
|
|
602
666
|
this.aqua?.destroyPlayer?.(this.guildId)
|
|
603
667
|
if (this.nodes?.connected)
|
|
604
|
-
this.nodes.rest?.destroyPlayer(this.guildId).catch(() =>
|
|
605
|
-
|
|
668
|
+
this.nodes.rest?.destroyPlayer(this.guildId).catch((error) =>
|
|
669
|
+
reportSuppressedError(this, 'player.destroy.remote', error, {
|
|
670
|
+
guildId: this.guildId
|
|
671
|
+
})
|
|
672
|
+
)
|
|
673
|
+
} catch (error) {
|
|
674
|
+
reportSuppressedError(this, 'player.destroy.gateway', error, {
|
|
675
|
+
guildId: this.guildId
|
|
676
|
+
})
|
|
677
|
+
}
|
|
606
678
|
}
|
|
607
679
|
|
|
608
680
|
if (!preserveClient) this.aqua = this.nodes = null
|
|
@@ -612,7 +684,12 @@ class Player extends EventEmitter {
|
|
|
612
684
|
pause(paused) {
|
|
613
685
|
if (this.destroyed || this.paused === !!paused) return this
|
|
614
686
|
this.paused = !!paused
|
|
615
|
-
this.batchUpdatePlayer({ paused: this.paused }, true).catch(() =>
|
|
687
|
+
this.batchUpdatePlayer({ paused: this.paused }, true).catch((error) =>
|
|
688
|
+
reportSuppressedError(this, 'player.pause', error, {
|
|
689
|
+
guildId: this.guildId,
|
|
690
|
+
paused: this.paused
|
|
691
|
+
})
|
|
692
|
+
)
|
|
616
693
|
return this
|
|
617
694
|
}
|
|
618
695
|
|
|
@@ -624,7 +701,12 @@ class Player extends EventEmitter {
|
|
|
624
701
|
? Math.min(Math.max(position, 0), len)
|
|
625
702
|
: Math.max(position, 0)
|
|
626
703
|
this.position = clamped
|
|
627
|
-
this.batchUpdatePlayer({ position: clamped }, true).catch(() =>
|
|
704
|
+
this.batchUpdatePlayer({ position: clamped }, true).catch((error) =>
|
|
705
|
+
reportSuppressedError(this, 'player.seek', error, {
|
|
706
|
+
guildId: this.guildId,
|
|
707
|
+
position: clamped
|
|
708
|
+
})
|
|
709
|
+
)
|
|
628
710
|
return this
|
|
629
711
|
}
|
|
630
712
|
|
|
@@ -678,7 +760,11 @@ class Player extends EventEmitter {
|
|
|
678
760
|
this.batchUpdatePlayer(
|
|
679
761
|
{ track: { encoded: null }, paused: this.paused },
|
|
680
762
|
true
|
|
681
|
-
).catch(() =>
|
|
763
|
+
).catch((error) =>
|
|
764
|
+
reportSuppressedError(this, 'player.stop', error, {
|
|
765
|
+
guildId: this.guildId
|
|
766
|
+
})
|
|
767
|
+
)
|
|
682
768
|
return this
|
|
683
769
|
}
|
|
684
770
|
|
|
@@ -686,7 +772,12 @@ class Player extends EventEmitter {
|
|
|
686
772
|
const vol = _functions.clamp(volume)
|
|
687
773
|
if (this.destroyed || this.volume === vol) return this
|
|
688
774
|
this.volume = vol
|
|
689
|
-
this.batchUpdatePlayer({ volume: vol }).catch(() =>
|
|
775
|
+
this.batchUpdatePlayer({ volume: vol }).catch((error) =>
|
|
776
|
+
reportSuppressedError(this, 'player.setVolume', error, {
|
|
777
|
+
guildId: this.guildId,
|
|
778
|
+
volume: vol
|
|
779
|
+
})
|
|
780
|
+
)
|
|
690
781
|
return this
|
|
691
782
|
}
|
|
692
783
|
|
|
@@ -703,7 +794,6 @@ class Player extends EventEmitter {
|
|
|
703
794
|
const id = _functions.toId(channel)
|
|
704
795
|
if (!id) throw new TypeError('Invalid text channel')
|
|
705
796
|
this.textChannel = id
|
|
706
|
-
this.batchUpdatePlayer({ text_channel: id }).catch(() => {})
|
|
707
797
|
return this
|
|
708
798
|
}
|
|
709
799
|
|
|
@@ -739,7 +829,31 @@ class Player extends EventEmitter {
|
|
|
739
829
|
replay() {
|
|
740
830
|
return this.seek(0)
|
|
741
831
|
}
|
|
742
|
-
skip() {
|
|
832
|
+
skip(target) {
|
|
833
|
+
if (this.destroyed || !this.playing) return this
|
|
834
|
+
|
|
835
|
+
if (target === undefined || target === null) return this.stop()
|
|
836
|
+
|
|
837
|
+
if (typeof target === 'number') {
|
|
838
|
+
const idx = target | 0
|
|
839
|
+
if (idx <= 0) return this.stop()
|
|
840
|
+
if (!this.queue?.size || idx >= this.queue.size) return this.stop()
|
|
841
|
+
for (let i = 0; i < idx; i++) this.queue.dequeue()
|
|
842
|
+
return this.stop()
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
const targetId = _functions.toId(target)
|
|
846
|
+
if (targetId && this.queue?.size) {
|
|
847
|
+
const arr = this.queue.toArray()
|
|
848
|
+
const idx = arr.findIndex(
|
|
849
|
+
(t) =>
|
|
850
|
+
_functions.toId(t) === targetId ||
|
|
851
|
+
_functions.toId(t?.info?.identifier) === targetId
|
|
852
|
+
)
|
|
853
|
+
if (idx > 0) {
|
|
854
|
+
for (let i = 0; i < idx; i++) this.queue.dequeue()
|
|
855
|
+
}
|
|
856
|
+
}
|
|
743
857
|
return this.stop()
|
|
744
858
|
}
|
|
745
859
|
|
|
@@ -835,8 +949,8 @@ class Player extends EventEmitter {
|
|
|
835
949
|
}
|
|
836
950
|
} catch (err) {
|
|
837
951
|
if (this.destroyed) return this
|
|
838
|
-
|
|
839
|
-
|
|
952
|
+
_functions.emitAquaError(
|
|
953
|
+
this.aqua,
|
|
840
954
|
new Error(`Autoplay ${i + 1} fail: ${err.message}`)
|
|
841
955
|
)
|
|
842
956
|
}
|
|
@@ -858,7 +972,7 @@ class Player extends EventEmitter {
|
|
|
858
972
|
}
|
|
859
973
|
|
|
860
974
|
async _getAutoplayTrack(sourceName, identifier, uri, requester) {
|
|
861
|
-
if (sourceName === 'youtube') {
|
|
975
|
+
if (sourceName === 'youtube' || sourceName === 'ytmusic') {
|
|
862
976
|
const res = await this.aqua.resolve({
|
|
863
977
|
query: `https://www.youtube.com/watch?v=${identifier}&list=RD${identifier}`,
|
|
864
978
|
source: 'ytmsearch',
|
|
@@ -930,12 +1044,15 @@ class Player extends EventEmitter {
|
|
|
930
1044
|
return
|
|
931
1045
|
}
|
|
932
1046
|
|
|
933
|
-
if (
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
1047
|
+
if (track && reason === 'finished') {
|
|
1048
|
+
if (this.loop === LOOP_MODES.TRACK) {
|
|
1049
|
+
this.aqua.emit(AqualinkEvents.TrackEnd, this, track, reason)
|
|
1050
|
+
await this.play(track)
|
|
1051
|
+
return
|
|
1052
|
+
}
|
|
1053
|
+
if (this.loop === LOOP_MODES.QUEUE) {
|
|
1054
|
+
this.queue.add(track)
|
|
1055
|
+
}
|
|
939
1056
|
}
|
|
940
1057
|
|
|
941
1058
|
if (this.queue.size) {
|
|
@@ -1007,170 +1124,27 @@ class Player extends EventEmitter {
|
|
|
1007
1124
|
}
|
|
1008
1125
|
|
|
1009
1126
|
async _attemptVoiceResume() {
|
|
1010
|
-
|
|
1011
|
-
if (!(await this.connection.attemptResume()))
|
|
1012
|
-
throw new Error('Resume failed')
|
|
1127
|
+
return this._lifecycleController.attemptVoiceResume()
|
|
1013
1128
|
}
|
|
1014
1129
|
|
|
1015
1130
|
async socketClosed(_player, _track, payload) {
|
|
1016
|
-
|
|
1017
|
-
this.aqua?._trace?.('player.socketClosed', {
|
|
1018
|
-
guildId: this.guildId,
|
|
1019
|
-
code: payload?.code
|
|
1020
|
-
})
|
|
1021
|
-
|
|
1022
|
-
const code = payload?.code
|
|
1023
|
-
if (code === 4006 && this._resuming) {
|
|
1024
|
-
this.aqua?._trace?.('player.socketClosed.ignored', {
|
|
1025
|
-
guildId: this.guildId,
|
|
1026
|
-
code,
|
|
1027
|
-
reason: 'transient_while_resuming'
|
|
1028
|
-
})
|
|
1029
|
-
return
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
let isRecoverable = [4015, 4009, 4006, 4014, 4022].includes(code)
|
|
1033
|
-
if (code === 4014 && this.connection?.isWaitingForDisconnect)
|
|
1034
|
-
isRecoverable = false
|
|
1035
|
-
|
|
1036
|
-
if (code === 4015 && !this.nodes?.info?.isNodelink) {
|
|
1037
|
-
this._reconnecting = true
|
|
1038
|
-
try {
|
|
1039
|
-
await this._attemptVoiceResume()
|
|
1040
|
-
this._reconnecting = false
|
|
1041
|
-
return
|
|
1042
|
-
} catch {
|
|
1043
|
-
this._reconnecting = false
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
if (!isRecoverable) {
|
|
1048
|
-
this.aqua.emit(AqualinkEvents.SocketClosed, this, payload)
|
|
1049
|
-
this.destroy()
|
|
1050
|
-
return
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
if (code === 4014 || code === 4022) {
|
|
1054
|
-
this.connected = false
|
|
1055
|
-
if (!this._voiceDownSince) this._voiceDownSince = Date.now()
|
|
1056
|
-
if (code === 4022) this._suppressResumeUntil = Date.now() + 3000
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
const aqua = this.aqua
|
|
1060
|
-
const vcId = _functions.toId(this.voiceChannel)
|
|
1061
|
-
const tcId = _functions.toId(this.textChannel)
|
|
1062
|
-
const { guildId, deaf, mute } = this
|
|
1063
|
-
|
|
1064
|
-
if (!vcId) {
|
|
1065
|
-
aqua?.emit?.(AqualinkEvents.SocketClosed, this, payload)
|
|
1066
|
-
return
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
const state = {
|
|
1070
|
-
volume: this.volume,
|
|
1071
|
-
position: this.position,
|
|
1072
|
-
paused: this.paused,
|
|
1073
|
-
loop: this.loop,
|
|
1074
|
-
isAutoplayEnabled: this.isAutoplayEnabled,
|
|
1075
|
-
currentTrack: this.current,
|
|
1076
|
-
queue: this.queue?.toArray() || [],
|
|
1077
|
-
previousIdentifiers: Array.from(this.previousIdentifiers),
|
|
1078
|
-
autoplaySeed: this.autoplaySeed,
|
|
1079
|
-
nowPlayingMessage: this.nowPlayingMessage
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
this._reconnecting = true
|
|
1083
|
-
this.destroy({
|
|
1084
|
-
preserveClient: true,
|
|
1085
|
-
skipRemote: true,
|
|
1086
|
-
preserveMessage: true,
|
|
1087
|
-
preserveReconnecting: true,
|
|
1088
|
-
preserveTracks: true
|
|
1089
|
-
})
|
|
1090
|
-
|
|
1091
|
-
// Store reconnect timers on instance for cleanup in destroy()
|
|
1092
|
-
this._reconnectTimers = new Set()
|
|
1093
|
-
const reconnectTimers = this._reconnectTimers
|
|
1094
|
-
const tryReconnect = async (attempt) => {
|
|
1095
|
-
if (aqua?.destroyed) {
|
|
1096
|
-
_functions.clearTimers(reconnectTimers)
|
|
1097
|
-
return
|
|
1098
|
-
}
|
|
1099
|
-
try {
|
|
1100
|
-
const np = await aqua.createConnection({
|
|
1101
|
-
guildId,
|
|
1102
|
-
voiceChannel: vcId,
|
|
1103
|
-
textChannel: tcId,
|
|
1104
|
-
deaf,
|
|
1105
|
-
mute,
|
|
1106
|
-
defaultVolume: state.volume,
|
|
1107
|
-
preserveMessage: true,
|
|
1108
|
-
resuming: true
|
|
1109
|
-
})
|
|
1110
|
-
if (!np) throw new Error('Failed to create player')
|
|
1111
|
-
|
|
1112
|
-
np.reconnectionRetries = 0
|
|
1113
|
-
np.loop = state.loop
|
|
1114
|
-
np.isAutoplayEnabled = state.isAutoplayEnabled
|
|
1115
|
-
np.autoplaySeed = state.autoplaySeed
|
|
1116
|
-
np.previousIdentifiers = new Set(state.previousIdentifiers)
|
|
1117
|
-
np.nowPlayingMessage = state.nowPlayingMessage
|
|
1118
|
-
|
|
1119
|
-
const ct = state.currentTrack
|
|
1120
|
-
if (ct) np.queue.add(ct)
|
|
1121
|
-
for (const q of state.queue) if (q !== ct) np.queue.add(q)
|
|
1122
|
-
|
|
1123
|
-
if (ct) {
|
|
1124
|
-
await np.play()
|
|
1125
|
-
if (state.position > 5000)
|
|
1126
|
-
np._createTimer(
|
|
1127
|
-
() => !np.destroyed && np.seek(state.position),
|
|
1128
|
-
SEEK_DELAY
|
|
1129
|
-
)
|
|
1130
|
-
if (state.paused)
|
|
1131
|
-
np._createTimer(() => !np.destroyed && np.pause(true), PAUSE_DELAY)
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
_functions.clearTimers(reconnectTimers)
|
|
1135
|
-
this._reconnecting = false
|
|
1136
|
-
aqua.emit(AqualinkEvents.PlayerReconnected, np, {
|
|
1137
|
-
oldPlayer: this,
|
|
1138
|
-
restoredState: state
|
|
1139
|
-
})
|
|
1140
|
-
} catch (error) {
|
|
1141
|
-
const retriesLeft = RECONNECT_MAX - attempt
|
|
1142
|
-
aqua.emit(AqualinkEvents.ReconnectionFailed, this, {
|
|
1143
|
-
error,
|
|
1144
|
-
code,
|
|
1145
|
-
payload,
|
|
1146
|
-
retriesLeft
|
|
1147
|
-
})
|
|
1148
|
-
|
|
1149
|
-
if (retriesLeft > 0) {
|
|
1150
|
-
_functions.createTimer(
|
|
1151
|
-
() => tryReconnect(attempt + 1),
|
|
1152
|
-
Math.min(RETRY_BACKOFF_BASE * attempt, RETRY_BACKOFF_MAX),
|
|
1153
|
-
reconnectTimers
|
|
1154
|
-
)
|
|
1155
|
-
} else {
|
|
1156
|
-
_functions.clearTimers(reconnectTimers)
|
|
1157
|
-
this._reconnecting = false
|
|
1158
|
-
aqua.emit(AqualinkEvents.SocketClosed, this, payload)
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
tryReconnect(1)
|
|
1131
|
+
return this._lifecycleController.socketClosed(_player, _track, payload)
|
|
1164
1132
|
}
|
|
1165
1133
|
|
|
1166
1134
|
send(data) {
|
|
1167
1135
|
try {
|
|
1168
|
-
this.aqua
|
|
1136
|
+
if (this.aqua?.queueVoiceStateUpdate) {
|
|
1137
|
+
return this.aqua.queueVoiceStateUpdate(data)
|
|
1138
|
+
} else {
|
|
1139
|
+
this.aqua.send({ op: 4, d: data })
|
|
1140
|
+
return true
|
|
1141
|
+
}
|
|
1169
1142
|
} catch (err) {
|
|
1170
|
-
|
|
1171
|
-
|
|
1143
|
+
_functions.emitAquaError(
|
|
1144
|
+
this.aqua,
|
|
1172
1145
|
new Error(`Send fail: ${err.message}`)
|
|
1173
1146
|
)
|
|
1147
|
+
return false
|
|
1174
1148
|
}
|
|
1175
1149
|
}
|
|
1176
1150
|
|
|
@@ -1203,24 +1177,7 @@ class Player extends EventEmitter {
|
|
|
1203
1177
|
}
|
|
1204
1178
|
|
|
1205
1179
|
_flushDeferredPlay() {
|
|
1206
|
-
|
|
1207
|
-
!this._deferredStart ||
|
|
1208
|
-
this.destroyed ||
|
|
1209
|
-
!this.current?.track ||
|
|
1210
|
-
!this._updateBatcher
|
|
1211
|
-
)
|
|
1212
|
-
return
|
|
1213
|
-
this._deferredStart = false
|
|
1214
|
-
const updateData = {
|
|
1215
|
-
track: { encoded: this.current.track },
|
|
1216
|
-
paused: this.paused
|
|
1217
|
-
}
|
|
1218
|
-
if (this.position > 0) updateData.position = this.position
|
|
1219
|
-
this.aqua?._trace?.('player.play.deferred.flush', {
|
|
1220
|
-
guildId: this.guildId,
|
|
1221
|
-
hasEndpoint: !!this.connection?.endpoint
|
|
1222
|
-
})
|
|
1223
|
-
this.batchUpdatePlayer(updateData, true).catch(() => {})
|
|
1180
|
+
return this._lifecycleController.flushDeferredPlay()
|
|
1224
1181
|
}
|
|
1225
1182
|
|
|
1226
1183
|
cleanup() {
|