diff --git a/cogs/player/jockey.py b/cogs/player/jockey.py index b2950e4..5d840ea 100644 --- a/cogs/player/jockey.py +++ b/cogs/player/jockey.py @@ -2,7 +2,7 @@ Music player class for Blanco. Subclass of mafic.Player. """ -from asyncio import get_event_loop +from asyncio import get_event_loop, sleep from time import time from typing import TYPE_CHECKING, List, Optional, Tuple @@ -18,7 +18,8 @@ from utils.time import human_readable_time from views.now_playing import NowPlayingView -from .jockey_helpers import find_lavalink_track, parse_query +from .jockey_helpers import (find_lavalink_track, invalidate_lavalink_track, + parse_query) from .queue import QueueManager if TYPE_CHECKING: @@ -196,7 +197,36 @@ async def _play(self, item: 'QueueItem') -> bool: raise RuntimeError(err.args[0]) from err # Play track - await self.play(item.lavalink_track, volume=self.volume) + has_retried = False + while True: + try: + await self.play(item.lavalink_track, volume=self.volume) + except PlayerNotConnected as err: + # If we've already retried, give up + if has_retried: + raise JockeyError(err.args[0]) from err + + # Wait until we're connected + wait_time = 0 + self._logger.warning( + 'PlayerNotConnected raised while trying to play `%s\', retrying...', + item.title + ) + while not self.connected: + if wait_time >= 10: + raise JockeyError('Timeout while waiting for player to connect') from err + + # Print wait message only once + if wait_time == 0: + self._logger.debug('Waiting 10 sec for player to connect...') + await sleep(0.1) + wait_time += 0.1 + + # Remove cached Lavalink track and try again + invalidate_lavalink_track(item) + has_retried = True + else: + break # We don't want to play if the player is not idle # as that will effectively skip the current track. diff --git a/cogs/player/jockey_helpers.py b/cogs/player/jockey_helpers.py index 77dfa3d..3bebf34 100644 --- a/cogs/player/jockey_helpers.py +++ b/cogs/player/jockey_helpers.py @@ -260,6 +260,38 @@ async def find_lavalink_track( # pylint: disable=too-many-statements return lavalink_track +def invalidate_lavalink_track(item: QueueItem): + """ + Removes a cached Lavalink track from Redis. + + :param item: The QueueItem to invalidate the track for. + """ + if REDIS is None: + return + + # Determine key type + redis_key = None + redis_key_type = None + if item.spotify_id is not None: + redis_key = item.spotify_id + redis_key_type = 'spotify_id' + elif item.isrc is not None: + redis_key = item.isrc + redis_key_type = 'isrc' + + # Invalidate cached Lavalink track + if redis_key is not None and redis_key_type is not None: + REDIS.invalidate_lavalink_track( + redis_key, + key_type=redis_key_type + ) + else: + LOGGER.warning( + 'Could not invalidate cached track for `%s\': no key', + item.title + ) + + async def parse_query( node: 'Node', spotify: Spotify, diff --git a/database/redis.py b/database/redis.py index 1079a31..a8b2fd0 100644 --- a/database/redis.py +++ b/database/redis.py @@ -26,6 +26,15 @@ def __init__(self, host: str, port: int, password: Optional[str] = None): # Logger self._logger = create_logger(self.__class__.__name__) + self._logger.debug('Attempting to connect to Redis server...') + + # Test connection + try: + self._client.ping() + except redis.ConnectionError as err: + self._logger.critical('Could not connect to Redis server. Check your configuration.') + raise RuntimeError('Could not connect to Redis server.') from err + self._logger.info('Connected to Redis server. Enable debug logging to see cache hits.') def set_lavalink_track(self, key: str, value: str, *, key_type: str): @@ -52,6 +61,17 @@ def get_lavalink_track(self, key: str, *, key_type: str) -> Optional[str]: self._logger.debug('Got cached Lavalink track for %s:%s', key_type, key) return self._client.get(f'lavalink:{key_type}:{key}') # type: ignore + def invalidate_lavalink_track(self, key: str, *, key_type: str): + """ + Removes a cached Lavalink track. + + :param key: The key to remove the track for. + :param key_type: The type of key to remove the track for, e.g. 'isrc' or 'spotify_id'. + """ + self._logger.debug('Invalidating Lavalink track for %s:%s', key_type, key) + if self._client.exists(f'lavalink:{key_type}:{key}'): + self._client.delete(f'lavalink:{key_type}:{key}') + def set_spotify_track(self, spotify_id: str, track: 'SpotifyTrack'): """ Save a Spotify track. diff --git a/utils/blanco.py b/utils/blanco.py index 87b17d2..34786ca 100644 --- a/utils/blanco.py +++ b/utils/blanco.py @@ -9,7 +9,7 @@ from mafic import EndReason, NodePool, VoiceRegion from nextcord import (Activity, ActivityType, Forbidden, HTTPException, Interaction, NotFound, PartialMessageable, StageChannel, - TextChannel, Thread, VoiceChannel) + TextChannel, Thread, VoiceChannel, MessageFlags) from nextcord.ext.commands import Bot from cogs.player.jockey_helpers import find_lavalink_track @@ -488,7 +488,11 @@ async def send_now_playing(self, event: 'TrackStartEvent[Jockey]'): current_track = event.player.queue_manager.current embed = event.player.now_playing(event.track) view = NowPlayingView(self, event.player, current_track.spotify_id) - msg = await channel.send(embed=embed, view=view) + + # Send message silently + flags = MessageFlags() + flags.suppress_notifications = True + msg = await channel.send(embed=embed, view=view, flags=flags) # Save now playing message ID self.database.set_now_playing(guild_id, msg.id)