magmastream 2.9.0-dev.2 → 2.9.0-dev.21

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.
@@ -235,11 +235,11 @@ class Node {
235
235
  identifier: this.options.identifier,
236
236
  address: this.address,
237
237
  sessionId: this.sessionId,
238
- playerCount: this.manager.players.filter((p) => p.node == this).size,
238
+ playerCount: (await this.manager.players.filter((p) => p.node == this)).size,
239
239
  };
240
240
  this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[NODE] Destroying node: ${JSON.stringify(debugInfo)}`);
241
241
  // Automove all players connected to that node
242
- const players = this.manager.players.filter((p) => p.node == this);
242
+ const players = await this.manager.players.filter((p) => p.node == this);
243
243
  if (players.size) {
244
244
  players.forEach(async (player) => {
245
245
  await player.autoMoveNode();
@@ -405,7 +405,7 @@ class Node {
405
405
  this.stats = { ...payload };
406
406
  break;
407
407
  case "playerUpdate":
408
- player = this.manager.players.get(payload.guildId);
408
+ player = await this.manager.players.get(payload.guildId);
409
409
  if (player && player.node.options.identifier !== this.options.identifier) {
410
410
  return;
411
411
  }
@@ -413,7 +413,7 @@ class Node {
413
413
  player.position = payload.state.position || 0;
414
414
  break;
415
415
  case "event":
416
- player = this.manager.players.get(payload.guildId);
416
+ player = await this.manager.players.get(payload.guildId);
417
417
  if (player && player.node.options.identifier !== this.options.identifier) {
418
418
  return;
419
419
  }
@@ -452,10 +452,10 @@ class Node {
452
452
  async handleEvent(payload) {
453
453
  if (!payload.guildId)
454
454
  return;
455
- const player = this.manager.players.get(payload.guildId);
455
+ const player = await this.manager.players.get(payload.guildId);
456
456
  if (!player)
457
457
  return;
458
- const track = player.queue.current;
458
+ const track = await player.queue.getCurrent();
459
459
  const type = payload.type;
460
460
  let error;
461
461
  switch (type) {
@@ -478,16 +478,16 @@ class Node {
478
478
  this.socketClosed(player, payload);
479
479
  break;
480
480
  case "SegmentsLoaded":
481
- this.sponsorBlockSegmentLoaded(player, player.queue.current, payload);
481
+ this.sponsorBlockSegmentLoaded(player, track, payload);
482
482
  break;
483
483
  case "SegmentSkipped":
484
- this.sponsorBlockSegmentSkipped(player, player.queue.current, payload);
484
+ this.sponsorBlockSegmentSkipped(player, track, payload);
485
485
  break;
486
486
  case "ChaptersLoaded":
487
- this.sponsorBlockChaptersLoaded(player, player.queue.current, payload);
487
+ this.sponsorBlockChaptersLoaded(player, track, payload);
488
488
  break;
489
489
  case "ChapterStarted":
490
- this.sponsorBlockChapterStarted(player, player.queue.current, payload);
490
+ this.sponsorBlockChapterStarted(player, track, payload);
491
491
  break;
492
492
  default:
493
493
  error = new Error(`Node#event unknown event '${type}'.`);
@@ -536,12 +536,14 @@ class Node {
536
536
  async trackEnd(player, track, payload) {
537
537
  const { reason } = payload;
538
538
  const skipFlag = player.get("skipFlag");
539
- if (!skipFlag && (player.queue.previous.length === 0 || (player.queue.previous[0] && player.queue.previous[0].track !== player.queue.current?.track))) {
540
- // Store the current track in the previous tracks queue
541
- player.queue.previous.push(player.queue.current);
542
- // Limit the previous tracks queue to maxPreviousTracks
543
- if (player.queue.previous.length > this.manager.options.maxPreviousTracks) {
544
- player.queue.previous.shift();
539
+ const previous = await player.queue.getPrevious();
540
+ const current = await player.queue.getCurrent();
541
+ if (!skipFlag && (previous.length === 0 || (previous[0] && previous[0].track !== current?.track))) {
542
+ await player.queue.addPrevious(current);
543
+ const updated = await player.queue.getPrevious();
544
+ if (updated.length > this.manager.options.maxPreviousTracks) {
545
+ const trimmed = updated.slice(0, this.manager.options.maxPreviousTracks);
546
+ await player.queue.setPrevious(trimmed);
545
547
  }
546
548
  }
547
549
  const oldPlayer = player;
@@ -557,7 +559,7 @@ class Node {
557
559
  break;
558
560
  case Utils_1.TrackEndReasonTypes.Stopped:
559
561
  // If the track was forcibly replaced
560
- if (player.queue.length) {
562
+ if (await player.queue.size()) {
561
563
  await this.playNextTrack(player, track, payload);
562
564
  }
563
565
  else {
@@ -571,7 +573,7 @@ class Node {
571
573
  break;
572
574
  }
573
575
  // If there's another track in the queue
574
- if (player.queue.length) {
576
+ if (await player.queue.size()) {
575
577
  await this.playNextTrack(player, track, payload);
576
578
  }
577
579
  else {
@@ -602,13 +604,16 @@ class Node {
602
604
  */
603
605
  async handleAutoplay(player, attempt = 0) {
604
606
  // If autoplay is not enabled or all attempts have failed, early exit
605
- if (!player.isAutoplay || attempt === player.autoplayTries || !player.queue.previous.length)
607
+ if (!player.isAutoplay || attempt > player.autoplayTries || !(await player.queue.getPrevious()).length)
606
608
  return false;
607
- const lastTrack = player.queue.previous[player.queue.previous.length - 1];
609
+ const PreviousQueue = await player.queue.getPrevious();
610
+ const lastTrack = PreviousQueue?.at(-1);
608
611
  lastTrack.requester = player.get("Internal_BotUser");
609
- const tracks = await Utils_1.AutoPlayUtils.getRecommendedTracks(player, lastTrack, attempt);
612
+ if (!lastTrack)
613
+ return false;
614
+ const tracks = await Utils_1.AutoPlayUtils.getRecommendedTracks(lastTrack);
610
615
  if (tracks.length) {
611
- player.queue.add(tracks[0]);
616
+ await player.queue.add(tracks[0]);
612
617
  await player.play();
613
618
  return true;
614
619
  }
@@ -629,8 +634,8 @@ class Node {
629
634
  * @private
630
635
  */
631
636
  async handleFailedTrack(player, track, payload) {
632
- player.queue.current = player.queue.shift();
633
- if (!player.queue.current) {
637
+ await player.queue.setCurrent(await player.queue.dequeue());
638
+ if (!(await player.queue.getCurrent())) {
634
639
  await this.queueEnd(player, track, payload);
635
640
  return;
636
641
  }
@@ -655,24 +660,28 @@ class Node {
655
660
  const { playNextOnEnd } = this.manager.options;
656
661
  if (trackRepeat) {
657
662
  // Prevent duplicate repeat insertion
658
- if (queue[0] !== queue.current) {
659
- queue.unshift(queue.current);
663
+ if (queue[0] !== (await queue.getCurrent())) {
664
+ await queue.enqueueFront(await queue.getCurrent());
660
665
  }
661
666
  }
662
667
  else if (queueRepeat) {
663
668
  // Prevent duplicate queue insertion
664
- if (queue[queue.length - 1] !== queue.current) {
665
- queue.add(queue.current);
669
+ if (queue[(await queue.size()) - 1] !== (await queue.getCurrent())) {
670
+ await queue.add(await queue.getCurrent());
666
671
  }
667
672
  }
668
673
  // Move to the next track
669
- queue.current = queue.shift();
674
+ await queue.setCurrent(await queue.dequeue());
670
675
  // Emit track end event
671
676
  this.manager.emit(Manager_1.ManagerEventTypes.TrackEnd, player, track, payload);
672
677
  // If the track was stopped manually and no more tracks exist, end the queue
673
- if (payload.reason === Utils_1.TrackEndReasonTypes.Stopped && !(queue.current = queue.shift())) {
674
- await this.queueEnd(player, track, payload);
675
- return;
678
+ if (payload.reason === Utils_1.TrackEndReasonTypes.Stopped) {
679
+ const next = await queue.dequeue();
680
+ await queue.setCurrent(next ?? null);
681
+ if (!next) {
682
+ await this.queueEnd(player, track, payload);
683
+ return;
684
+ }
676
685
  }
677
686
  // If autoplay is enabled, play the next track
678
687
  if (playNextOnEnd)
@@ -691,7 +700,7 @@ class Node {
691
700
  */
692
701
  async playNextTrack(player, track, payload) {
693
702
  // Shift the queue to set the next track as current
694
- player.queue.current = player.queue.shift();
703
+ await player.queue.setCurrent(await player.queue.dequeue());
695
704
  // Emit the track end event
696
705
  this.manager.emit(Manager_1.ManagerEventTypes.TrackEnd, player, track, payload);
697
706
  // If autoplay is enabled, play the next track
@@ -708,19 +717,19 @@ class Node {
708
717
  * @returns {Promise<void>} A promise that resolves when the queue end processing is complete.
709
718
  */
710
719
  async queueEnd(player, track, payload) {
711
- player.queue.current = null;
720
+ await player.queue.setCurrent(null);
712
721
  if (!player.isAutoplay) {
713
722
  player.playing = false;
714
723
  this.manager.emit(Manager_1.ManagerEventTypes.QueueEnd, player, track, payload);
715
724
  return;
716
725
  }
717
- let attempts = 1;
726
+ let attempt = 1;
718
727
  let success = false;
719
- while (attempts <= player.autoplayTries) {
720
- success = await this.handleAutoplay(player, attempts);
728
+ while (attempt <= player.autoplayTries) {
729
+ success = await this.handleAutoplay(player, attempt);
721
730
  if (success)
722
731
  return;
723
- attempts++;
732
+ attempt++;
724
733
  }
725
734
  // If all attempts fail, reset the player state and emit queueEnd
726
735
  player.playing = false;
@@ -9,6 +9,7 @@ const Queue_1 = require("./Queue");
9
9
  const Utils_1 = require("./Utils");
10
10
  const _ = tslib_1.__importStar(require("lodash"));
11
11
  const playerCheck_1 = tslib_1.__importDefault(require("../utils/playerCheck"));
12
+ const RedisQueue_1 = require("./RedisQueue");
12
13
  class Player {
13
14
  options;
14
15
  /** The Queue for the Player. */
@@ -50,7 +51,7 @@ class Player {
50
51
  /** The autoplay state of the player. */
51
52
  isAutoplay = false;
52
53
  /** The number of times to try autoplay before emitting queueEnd. */
53
- autoplayTries = null;
54
+ autoplayTries = 3;
54
55
  static _manager;
55
56
  data = {};
56
57
  dynamicLoopInterval = null;
@@ -67,10 +68,6 @@ class Player {
67
68
  this.manager = Utils_1.Structure.get("Player")._manager;
68
69
  if (!this.manager)
69
70
  throw new RangeError("Manager has not been initiated.");
70
- // If a player with the same guild ID already exists, return it.
71
- if (this.manager.players.has(options.guildId)) {
72
- return this.manager.players.get(options.guildId);
73
- }
74
71
  // Check the player options for errors.
75
72
  (0, playerCheck_1.default)(options);
76
73
  // Set the guild ID and voice state.
@@ -91,8 +88,15 @@ class Player {
91
88
  if (!this.node)
92
89
  throw new RangeError("No available nodes.");
93
90
  // Initialize the queue with the guild ID and manager.
94
- this.queue = new Queue_1.Queue(this.guildId, this.manager);
95
- this.queue.previous = new Array();
91
+ if (this.manager.options.stateStorage.type === Manager_1.StateStorageType.Redis) {
92
+ this.queue = new RedisQueue_1.RedisQueue(this.guildId, this.manager);
93
+ }
94
+ else {
95
+ this.queue = new Queue_1.Queue(this.guildId, this.manager);
96
+ }
97
+ if (this.queue instanceof Queue_1.Queue) {
98
+ this.queue.previous = [];
99
+ }
96
100
  // Add the player to the manager's player collection.
97
101
  this.manager.players.set(options.guildId, this);
98
102
  // Set the initial volume.
@@ -220,20 +224,27 @@ class Player {
220
224
  */
221
225
  async destroy(disconnect = true) {
222
226
  const oldPlayer = this ? { ...this } : null;
227
+ if (this.state === Utils_1.StateTypes.Destroying || this.state === Utils_1.StateTypes.Disconnected) {
228
+ console.debug(`[Player#destroy] Already destroying/destroyed for ${this.guildId}`);
229
+ return false;
230
+ }
223
231
  this.state = Utils_1.StateTypes.Destroying;
224
232
  if (disconnect) {
225
- await this.disconnect();
233
+ await this.disconnect().catch((err) => {
234
+ console.warn(`[Player#destroy] Failed to disconnect player ${this.guildId}:`, err);
235
+ });
226
236
  }
227
- await this.node.rest.destroyPlayer(this.guildId);
228
- this.queue.clear();
237
+ await this.node.rest.destroyPlayer(this.guildId).catch((err) => {
238
+ console.warn(`[Player#destroy] REST failed to destroy player ${this.guildId}:`, err);
239
+ });
240
+ await this.queue.clear();
241
+ await this.queue.clearPrevious();
242
+ await this.queue.setCurrent(null);
229
243
  this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, null, {
230
244
  changeType: Manager_1.PlayerStateEventTypes.PlayerDestroy,
231
245
  });
232
246
  this.manager.emit(Manager_1.ManagerEventTypes.PlayerDestroy, this);
233
247
  const deleted = this.manager.players.delete(this.guildId);
234
- if (!deleted) {
235
- console.warn(`Failed to delete player with guildId: ${this.guildId}`);
236
- }
237
248
  return deleted;
238
249
  }
239
250
  /**
@@ -307,9 +318,9 @@ class Player {
307
318
  }
308
319
  async play(optionsOrTrack, playOptions) {
309
320
  if (typeof optionsOrTrack !== "undefined" && Utils_1.TrackUtils.validate(optionsOrTrack)) {
310
- this.queue.current = optionsOrTrack;
321
+ await this.queue.setCurrent(optionsOrTrack);
311
322
  }
312
- if (!this.queue.current)
323
+ if (!(await this.queue.getCurrent()))
313
324
  throw new RangeError("No current track.");
314
325
  const finalOptions = playOptions
315
326
  ? playOptions
@@ -319,7 +330,7 @@ class Player {
319
330
  await this.node.rest.updatePlayer({
320
331
  guildId: this.guildId,
321
332
  data: {
322
- encodedTrack: this.queue.current?.track,
333
+ encodedTrack: (await this.queue.getCurrent()).track,
323
334
  ...finalOptions,
324
335
  },
325
336
  });
@@ -375,7 +386,7 @@ class Player {
375
386
  * @returns {Promise<Track[]>} - Array of recommended tracks.
376
387
  */
377
388
  async getRecommendedTracks(track) {
378
- const tracks = await Utils_1.AutoPlayUtils.getRecommendedTracks(this, track);
389
+ const tracks = await Utils_1.AutoPlayUtils.getRecommendedTracks(track);
379
390
  return tracks;
380
391
  }
381
392
  /**
@@ -509,13 +520,13 @@ class Player {
509
520
  * @throws {TypeError} If the repeat parameter is not a boolean.
510
521
  * @throws {RangeError} If the queue size is less than or equal to 1.
511
522
  */
512
- setDynamicRepeat(repeat, ms) {
523
+ async setDynamicRepeat(repeat, ms) {
513
524
  // Validate the repeat parameter
514
525
  if (typeof repeat !== "boolean") {
515
526
  throw new TypeError('Repeat can only be "true" or "false".');
516
527
  }
517
528
  // Ensure the queue has more than one track for dynamic repeat
518
- if (this.queue.size <= 1) {
529
+ if ((await this.queue.size()) <= 1) {
519
530
  throw new RangeError("The queue size must be greater than 1.");
520
531
  }
521
532
  // Clone the current player state for comparison
@@ -526,15 +537,14 @@ class Player {
526
537
  this.queueRepeat = false;
527
538
  this.dynamicRepeat = true;
528
539
  // Set an interval to shuffle the queue periodically
529
- this.dynamicLoopInterval = setInterval(() => {
540
+ this.dynamicLoopInterval = setInterval(async () => {
530
541
  if (!this.dynamicRepeat)
531
542
  return;
532
543
  // Shuffle the queue and replace it with the shuffled tracks
533
- const shuffled = _.shuffle(this.queue);
534
- this.queue.clear();
535
- shuffled.forEach((track) => {
536
- this.queue.add(track);
537
- });
544
+ const tracks = await this.queue.getTracks();
545
+ const shuffled = _.shuffle(tracks);
546
+ await this.queue.clear();
547
+ await this.queue.add(shuffled);
538
548
  }, ms);
539
549
  // Store the ms value
540
550
  this.dynamicRepeatIntervalMs = ms;
@@ -565,9 +575,9 @@ class Player {
565
575
  */
566
576
  async restart() {
567
577
  // Check if there is a current track in the queue
568
- if (!this.queue.current?.track) {
578
+ if (!(await this.queue.getCurrent())?.track) {
569
579
  // If the queue has tracks, play the next one
570
- if (this.queue.length)
580
+ if (await this.queue.size())
571
581
  await this.play();
572
582
  return this;
573
583
  }
@@ -576,7 +586,7 @@ class Player {
576
586
  guildId: this.guildId,
577
587
  data: {
578
588
  position: 0,
579
- encodedTrack: this.queue.current?.track,
589
+ encodedTrack: (await this.queue.getCurrent())?.track,
580
590
  },
581
591
  });
582
592
  return this;
@@ -591,10 +601,10 @@ class Player {
591
601
  const oldPlayer = { ...this };
592
602
  let removedTracks = [];
593
603
  if (typeof amount === "number" && amount > 1) {
594
- if (amount > this.queue.length)
604
+ if (amount > (await this.queue.size()))
595
605
  throw new RangeError("Cannot skip more than the queue length.");
596
- removedTracks = this.queue.slice(0, amount - 1);
597
- this.queue.splice(0, amount - 1);
606
+ removedTracks = await this.queue.getSlice(0, amount - 1);
607
+ await this.queue.modifyAt(0, amount - 1);
598
608
  }
599
609
  this.node.rest.updatePlayer({
600
610
  guildId: this.guildId,
@@ -653,22 +663,17 @@ class Player {
653
663
  * @emits {PlayerStateUpdate} - With {@link PlayerStateEventTypes.TrackChange} as the change type.
654
664
  */
655
665
  async previous() {
656
- // Check if there are previous tracks in the queue.
657
- if (!this.queue.previous.length) {
666
+ // Get and remove the most recent previous track
667
+ const lastTrack = await this.queue.popPrevious();
668
+ if (!lastTrack) {
658
669
  throw new Error("No previous track available.");
659
670
  }
660
671
  // Capture the current state of the player before making changes.
661
672
  const oldPlayer = { ...this };
662
- // Store the current track before changing it.
663
- // let currentTrackBeforeChange: Track | null = this.queue.current ? (this.queue.current as Track) : null;
664
- // Get the last played track and remove it from the history
665
- const lastTrack = this.queue.previous.pop();
666
- // Set the skip flag to true to prevent the onTrackEnd event from playing the next track.
673
+ // Set skip flag so trackEnd doesn't add current to previous
667
674
  this.set("skipFlag", true);
668
675
  await this.play(lastTrack);
669
- // Add the current track back to the end of the previous queue
670
- // if (currentTrackBeforeChange) this.queue.push(currentTrackBeforeChange);
671
- // Emit a player state update event indicating the track change to previous.
676
+ // Emit state update
672
677
  this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this, {
673
678
  changeType: Manager_1.PlayerStateEventTypes.TrackChange,
674
679
  details: {
@@ -676,7 +681,6 @@ class Player {
676
681
  track: lastTrack,
677
682
  },
678
683
  });
679
- // Reset the skip flag.
680
684
  this.set("skipFlag", false);
681
685
  return this;
682
686
  }
@@ -688,7 +692,7 @@ class Player {
688
692
  * @emits {PlayerStateUpdate} - With {@link PlayerStateEventTypes.TrackChange} as the change type.
689
693
  */
690
694
  async seek(position) {
691
- if (!this.queue.current)
695
+ if (!(await this.queue.getCurrent()))
692
696
  return undefined;
693
697
  position = Number(position);
694
698
  // Check if the position is valid.
@@ -698,8 +702,8 @@ class Player {
698
702
  // Get the old player state.
699
703
  const oldPlayer = this ? { ...this } : null;
700
704
  // Clamp the position to ensure it is within the valid range.
701
- if (position < 0 || position > this.queue.current.duration) {
702
- position = Math.max(Math.min(position, this.queue.current.duration), 0);
705
+ if (position < 0 || position > (await this.queue.getCurrent()).duration) {
706
+ position = Math.max(Math.min(position, (await this.queue.getCurrent()).duration), 0);
703
707
  }
704
708
  // Update the player's position.
705
709
  this.position = position;
@@ -764,7 +768,7 @@ class Player {
764
768
  try {
765
769
  const playerPosition = this.position;
766
770
  const { sessionId, event: { token, endpoint }, } = this.voiceState;
767
- const currentTrack = this.queue.current ? this.queue.current : null;
771
+ const currentTrack = (await this.queue.getCurrent()) ? await this.queue.getCurrent() : null;
768
772
  await this.node.rest.destroyPlayer(this.guildId).catch(() => { });
769
773
  this.manager.players.delete(this.guildId);
770
774
  this.node = node;
@@ -795,7 +799,7 @@ class Player {
795
799
  if (!newOptions.textChannelId)
796
800
  throw new Error("Text channel ID is required");
797
801
  // Check if a player already exists for the new guild
798
- let newPlayer = this.manager.players.get(newOptions.guildId);
802
+ let newPlayer = this.manager.getPlayer(newOptions.guildId);
799
803
  // If the player already exists and force is false, return the existing player
800
804
  if (newPlayer && !force)
801
805
  return newPlayer;
@@ -806,9 +810,9 @@ class Player {
806
810
  volume: this.volume,
807
811
  position: this.position,
808
812
  queue: {
809
- current: this.queue.current,
810
- tracks: [...this.queue],
811
- previous: [...this.queue.previous],
813
+ current: await this.queue.getCurrent(),
814
+ tracks: [...(await this.queue.getTracks())],
815
+ previous: [...(await this.queue.getPrevious())],
812
816
  },
813
817
  trackRepeat: this.trackRepeat,
814
818
  queueRepeat: this.queueRepeat,
@@ -841,9 +845,9 @@ class Player {
841
845
  encodedTrack: oldPlayerProperties.queue.current?.track,
842
846
  },
843
847
  });
844
- clonedPlayer.queue.current = oldPlayerProperties.queue.current;
845
- clonedPlayer.queue.previous = oldPlayerProperties.queue.previous;
846
- clonedPlayer.queue.add(oldPlayerProperties.queue.tracks);
848
+ await clonedPlayer.queue.setCurrent(oldPlayerProperties.queue.current);
849
+ await clonedPlayer.queue.addPrevious(oldPlayerProperties.queue.previous);
850
+ await clonedPlayer.queue.add(oldPlayerProperties.queue.tracks);
847
851
  clonedPlayer.filters = oldPlayerProperties.filters;
848
852
  clonedPlayer.isAutoplay = oldPlayerProperties.isAutoplay;
849
853
  clonedPlayer.nowPlayingMessage = oldPlayerProperties.nowPlayingMessage;
@@ -858,7 +862,7 @@ class Player {
858
862
  // Debug information
859
863
  const debugInfo = {
860
864
  success: true,
861
- message: `Transferred ${clonedPlayer.queue.length} tracks successfully to <#${newOptions.voiceChannelId}> bound to <#${newOptions.textChannelId}>.`,
865
+ message: `Transferred ${await clonedPlayer.queue.size()} tracks successfully to <#${newOptions.voiceChannelId}> bound to <#${newOptions.textChannelId}>.`,
862
866
  player: {
863
867
  guildId: clonedPlayer.guildId,
864
868
  voiceChannelId: clonedPlayer.voiceChannelId,
@@ -885,7 +889,7 @@ class Player {
885
889
  throw new RangeError(`There is no lavalyrics-plugin available in the Lavalink node: ${this.node.options.identifier}`);
886
890
  }
887
891
  // Fetch the lyrics for the current track from the Lavalink node
888
- let result = (await this.node.getLyrics(this.queue.current, skipTrackSource));
892
+ let result = (await this.node.getLyrics(await this.queue.getCurrent(), skipTrackSource));
889
893
  // If no lyrics are found, return a default empty lyrics object
890
894
  if (!result) {
891
895
  result = {