Fixing erros due to processing

This commit is contained in:
Rafael Vargas 2022-07-24 14:25:44 -03:00
parent 56456bf2ed
commit b904c75caa
16 changed files with 179 additions and 305 deletions

View File

@ -14,8 +14,8 @@ class Messages(Singleton):
self.SONGINFO_REQUESTER = 'Requester: ' self.SONGINFO_REQUESTER = 'Requester: '
self.SONGINFO_POSITION = 'Position: ' self.SONGINFO_POSITION = 'Position: '
self.SONGS_ADDED = 'You added {} songs to the queue' self.SONGS_ADDED = 'Downloading `{}` songs to add to the queue'
self.SONG_ADDED = 'You added the song `{}` to the queue' self.SONG_ADDED = 'Downloading the song `{}` to add to the queue'
self.SONG_ADDED_TWO = '🎧 Song added to the queue' self.SONG_ADDED_TWO = '🎧 Song added to the queue'
self.SONG_PLAYING = '🎧 Song playing now' self.SONG_PLAYING = '🎧 Song playing now'
self.SONG_PLAYER = '🎧 Song Player' self.SONG_PLAYER = '🎧 Song Player'
@ -61,7 +61,7 @@ class Messages(Singleton):
self.NO_CHANNEL = 'To play some music, connect to any voice channel first.' self.NO_CHANNEL = 'To play some music, connect to any voice channel first.'
self.NO_GUILD = f'This server does not has a Player, try {configs.BOT_PREFIX}reset' self.NO_GUILD = f'This server does not has a Player, try {configs.BOT_PREFIX}reset'
self.INVALID_INPUT = f'This URL was too strange, try something better or type {configs.BOT_PREFIX}help play' self.INVALID_INPUT = f'This URL was too strange, try something better or type {configs.BOT_PREFIX}help play'
self.DOWNLOADING_ERROR = '❌ An error occurred while downloading' self.DOWNLOADING_ERROR = "❌ It's impossible to download and play this video"
self.EXTRACTING_ERROR = '❌ An error ocurred while searching for the songs' self.EXTRACTING_ERROR = '❌ An error ocurred while searching for the songs'
self.MY_ERROR_BAD_COMMAND = 'This string serves to verify if some error was raised by myself on purpose' self.MY_ERROR_BAD_COMMAND = 'This string serves to verify if some error was raised by myself on purpose'

View File

@ -19,7 +19,7 @@ class ControlCog(commands.Cog):
self.__messages = Messages() self.__messages = Messages()
self.__colors = Colors() self.__colors = Colors()
self.__embeds = Embeds() self.__embeds = Embeds()
self.__comandos = { self.__commands = {
'MUSIC': ['resume', 'pause', 'loop', 'stop', 'MUSIC': ['resume', 'pause', 'loop', 'stop',
'skip', 'play', 'queue', 'clear', 'skip', 'play', 'queue', 'clear',
'np', 'shuffle', 'move', 'remove', 'np', 'shuffle', 'move', 'remove',
@ -80,10 +80,10 @@ class ControlCog(commands.Cog):
help_help = '👾 `HELP`\n' help_help = '👾 `HELP`\n'
for command in self.__bot.commands: for command in self.__bot.commands:
if command.name in self.__comandos['MUSIC']: if command.name in self.__commands['MUSIC']:
help_music += f'**{command}** - {command.help}\n' help_music += f'**{command}** - {command.help}\n'
elif command.name in self.__comandos['RANDOM']: elif command.name in self.__commands['RANDOM']:
help_random += f'**{command}** - {command.help}\n' help_random += f'**{command}** - {command.help}\n'
else: else:

View File

@ -6,12 +6,10 @@ from Handlers.ClearHandler import ClearHandler
from Handlers.MoveHandler import MoveHandler from Handlers.MoveHandler import MoveHandler
from Handlers.NowPlayingHandler import NowPlayingHandler from Handlers.NowPlayingHandler import NowPlayingHandler
from Handlers.PlayHandler import PlayHandler from Handlers.PlayHandler import PlayHandler
from Handlers.PlayersController import PlayersController
from Handlers.PrevHandler import PrevHandler from Handlers.PrevHandler import PrevHandler
from Handlers.RemoveHandler import RemoveHandler from Handlers.RemoveHandler import RemoveHandler
from Handlers.ResetHandler import ResetHandler from Handlers.ResetHandler import ResetHandler
from Handlers.ShuffleHandler import ShuffleHandler from Handlers.ShuffleHandler import ShuffleHandler
from Utils.Cleaner import Cleaner
from Handlers.SkipHandler import SkipHandler from Handlers.SkipHandler import SkipHandler
from Handlers.PauseHandler import PauseHandler from Handlers.PauseHandler import PauseHandler
from Handlers.StopHandler import StopHandler from Handlers.StopHandler import StopHandler
@ -34,16 +32,6 @@ class MusicCog(commands.Cog):
def __init__(self, bot) -> None: def __init__(self, bot) -> None:
self.__bot: Client = bot self.__bot: Client = bot
self.__cleaner = Cleaner(self.__bot)
self.__controller = PlayersController(self.__bot)
@commands.Cog.listener()
async def on_ready(self) -> None:
self.__controller = PlayersController(self.__bot)
@commands.Cog.listener()
async def on_guild_join(self, guild: Guild) -> None:
self.__controller.create_player(guild)
@commands.command(name="play", help=helper.HELP_PLAY, description=helper.HELP_PLAY_LONG, aliases=['p', 'tocar']) @commands.command(name="play", help=helper.HELP_PLAY, description=helper.HELP_PLAY_LONG, aliases=['p', 'tocar'])
async def play(self, ctx: Context, *args) -> None: async def play(self, ctx: Context, *args) -> None:

View File

@ -3,8 +3,6 @@ from typing import List
from discord.ext.commands import Context from discord.ext.commands import Context
from discord import Client, Guild, ClientUser, Member from discord import Client, Guild, ClientUser, Member
from Config.Messages import Messages from Config.Messages import Messages
from Handlers.PlayersController import PlayersController
from Music.Player import Player
from Handlers.HandlerResponse import HandlerResponse from Handlers.HandlerResponse import HandlerResponse
from Config.Configs import Configs from Config.Configs import Configs
from Config.Helper import Helper from Config.Helper import Helper
@ -14,8 +12,6 @@ from Views.Embeds import Embeds
class AbstractHandler(ABC): class AbstractHandler(ABC):
def __init__(self, ctx: Context, bot: Client) -> None: def __init__(self, ctx: Context, bot: Client) -> None:
self.__bot: Client = bot self.__bot: Client = bot
self.__controller = PlayersController(self.__bot)
self.__player: Player = self.__controller.get_player(ctx.guild)
self.__guild: Guild = ctx.guild self.__guild: Guild = ctx.guild
self.__ctx: Context = ctx self.__ctx: Context = ctx
self.__bot_user: ClientUser = self.__bot.user self.__bot_user: ClientUser = self.__bot.user
@ -46,14 +42,6 @@ class AbstractHandler(ABC):
def guild(self) -> Guild: def guild(self) -> Guild:
return self.__guild return self.__guild
@property
def player(self) -> Player:
return self.__player
@property
def controller(self) -> PlayersController:
return self.__controller
@property @property
def bot(self) -> Client: def bot(self) -> Client:
return self.__bot return self.__bot

View File

@ -37,11 +37,10 @@ class PlayHandler(AbstractHandler):
# Get the process context for the current guild # Get the process context for the current guild
manager = ProcessManager() manager = ProcessManager()
processInfo = manager.getPlayerContext(self.guild, self.ctx) processInfo = manager.getPlayerInfo(self.guild, self.ctx)
playlist = processInfo.getPlaylist() playlist = processInfo.getPlaylist()
process = processInfo.getProcess() process = processInfo.getProcess()
if not process.is_alive(): # If process has not yet started, start if not process.is_alive(): # If process has not yet started, start
print('Starting process')
process.start() process.start()
# Create the Songs objects # Create the Songs objects
@ -57,22 +56,21 @@ class PlayHandler(AbstractHandler):
error = DownloadingError() error = DownloadingError()
return HandlerResponse(self.ctx, embed, error) return HandlerResponse(self.ctx, embed, error)
# If not playing
if not playlist.getCurrentSong():
embed = self.embeds.SONG_ADDED(song.title)
response = HandlerResponse(self.ctx, embed)
else: # If already playing
pos = len(playlist.getSongs())
embed = self.embeds.SONG_ADDED_TWO(song.info, pos)
response = HandlerResponse(self.ctx, embed)
# Add the unique song to the playlist and send a command to player process # Add the unique song to the playlist and send a command to player process
with processInfo.getLock(): with processInfo.getLock():
playlist.add_song(song) playlist.add_song(song)
queue = processInfo.getQueue() queue = processInfo.getQueue()
playCommand = VCommands(VCommandsType.PLAY, None) playCommand = VCommands(VCommandsType.PLAY, None)
queue.put(playCommand) queue.put(playCommand)
return response
# If not playing
if not playlist.getCurrentSong():
embed = self.embeds.SONG_ADDED(song.title)
return HandlerResponse(self.ctx, embed)
else: # If already playing
pos = len(playlist.getSongs())
embed = self.embeds.SONG_ADDED_TWO(song.info, pos)
return HandlerResponse(self.ctx, embed)
else: # If multiple songs added else: # If multiple songs added
# Trigger a task to download all songs and then store them in the process playlist # Trigger a task to download all songs and then store them in the process playlist
@ -81,13 +79,15 @@ class PlayHandler(AbstractHandler):
embed = self.embeds.SONGS_ADDED(len(songs)) embed = self.embeds.SONGS_ADDED(len(songs))
return HandlerResponse(self.ctx, embed) return HandlerResponse(self.ctx, embed)
except Exception as err: except DownloadingError as error:
if isinstance(err, VulkanError): # If error was already processed embed = self.embeds.DOWNLOADING_ERROR()
print(f'DEVELOPER NOTE -> PlayController Error: {err.message}') return HandlerResponse(self.ctx, embed, error)
error = err except Exception as error:
if isinstance(error, VulkanError): # If error was already processed
print(f'DEVELOPER NOTE -s> PlayController Error: {error.message}', {type(error)})
embed = self.embeds.CUSTOM_ERROR(error) embed = self.embeds.CUSTOM_ERROR(error)
else: else:
print(f'DEVELOPER NOTE -> PlayController Error: {err}') print(f'DEVELOPER NOTE -> PlayController Error: {error}, {type(error)}')
error = UnknownError() error = UnknownError()
embed = self.embeds.UNKNOWN_ERROR() embed = self.embeds.UNKNOWN_ERROR()

View File

@ -1,56 +0,0 @@
from typing import Dict, List, Union
from Config.Singleton import Singleton
from discord import Guild, Client, VoiceClient, Member
from Music.Player import Player
class PlayersController(Singleton):
def __init__(self, bot: Client = None) -> None:
if not super().created:
self.__bot: Client = bot
if bot is not None:
self.__players: Dict[Guild, Player] = self.__create_players()
def set_bot(self, bot: Client) -> None:
self.__bot: Client = bot
self.__players: Dict[Guild, Player] = self.__create_players()
def get_player(self, guild: Guild) -> Player:
if guild not in self.__players.keys():
player = Player(self.__bot, guild)
self.__players[guild] = player
return self.__players[guild]
def reset_player(self, guild: Guild) -> None:
if isinstance(guild, Guild):
player = Player(self.__bot, guild)
self.__players[guild] == player
def get_guild_voice(self, guild: Guild) -> Union[VoiceClient, None]:
if guild.voice_client is None:
return None
else:
return guild.voice_client
def create_player(self, guild: Guild) -> None:
player = Player(self.__bot, guild)
self.__players[guild] = player
print(f'Player for guild {guild.name} created')
def __create_players(self) -> Dict[Guild, Player]:
list_guilds: List[Guild] = self.__bot.guilds
players: Dict[Guild, Player] = {}
for guild in list_guilds:
player = Player(self.__bot, guild)
players[guild] = player
print(f'Player for guild {guild.name} created')
return players
def __get_guild_bot_member(self, guild: Guild) -> Member:
members: List[Member] = guild.members
for member in members:
if member.id == self.__bot.user.id:
return member

View File

@ -13,7 +13,7 @@ class PrevHandler(AbstractHandler):
async def run(self) -> HandlerResponse: async def run(self) -> HandlerResponse:
processManager = ProcessManager() processManager = ProcessManager()
processInfo = processManager.getRunningPlayerInfo(self.guild) processInfo = processManager.getPlayerInfo(self.guild, self.ctx)
if not processInfo: if not processInfo:
embed = self.embeds.NOT_PLAYING() embed = self.embeds.NOT_PLAYING()
error = BadCommandUsage() error = BadCommandUsage()
@ -35,6 +35,11 @@ class PrevHandler(AbstractHandler):
embed = self.embeds.FAIL_DUE_TO_LOOP_ON() embed = self.embeds.FAIL_DUE_TO_LOOP_ON()
return HandlerResponse(self.ctx, embed, error) return HandlerResponse(self.ctx, embed, error)
# If not started, start the player process
process = processInfo.getProcess()
if not process.is_alive():
process.start()
# Send a prev command, together with the user voice channel # Send a prev command, together with the user voice channel
prevCommand = VCommands(VCommandsType.PREV, self.ctx.author.voice.channel.id) prevCommand = VCommands(VCommandsType.PREV, self.ctx.author.voice.channel.id)
queue = processInfo.getQueue() queue = processInfo.getQueue()

View File

@ -1,10 +1,11 @@
import asyncio import asyncio
from typing import List from typing import List
from Config.Configs import Configs from Config.Configs import Configs
from yt_dlp import YoutubeDL from yt_dlp import YoutubeDL, DownloadError
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from Music.Song import Song from Music.Song import Song
from Utils.Utils import Utils, run_async from Utils.Utils import Utils, run_async
from Config.Exceptions import DownloadingError
class Downloader: class Downloader:
@ -40,6 +41,7 @@ class Downloader:
self.__playlist_keys = ['entries'] self.__playlist_keys = ['entries']
def finish_one_song(self, song: Song) -> Song: def finish_one_song(self, song: Song) -> Song:
try:
if song.identifier is None: if song.identifier is None:
return None return None
@ -50,6 +52,9 @@ class Downloader:
song.finish_down(song_info) song.finish_down(song_info)
return song return song
# Convert yt_dlp error to my own error
except DownloadError:
raise DownloadingError()
async def preload(self, songs: List[Song]) -> None: async def preload(self, songs: List[Song]) -> None:
for song in songs: for song in songs:
@ -81,8 +86,11 @@ class Downloader:
else: # Failed to extract the songs else: # Failed to extract the songs
print(f'DEVELOPER NOTE -> Failed to Extract URL {url}') print(f'DEVELOPER NOTE -> Failed to Extract URL {url}')
return [] return []
# Convert the yt_dlp download error to own error
except DownloadError:
raise DownloadingError()
except Exception as e: except Exception as e:
print(f'DEVELOPER NOTE -> Error Extracting Music: {e}') print(f'DEVELOPER NOTE -> Error Extracting Music: {e}, {type(e)}')
raise e raise e
else: else:
return [] return []

View File

@ -1,116 +0,0 @@
import asyncio
from discord.ext import commands
from discord import Client, Guild, FFmpegPCMAudio
from discord.ext.commands import Context
from Music.Downloader import Downloader
from Music.Playlist import Playlist
from Music.Song import Song
from Utils.Utils import Timer
class Player(commands.Cog):
def __init__(self, bot: Client, guild: Guild):
self.__down: Downloader = Downloader()
self.__playlist: Playlist = Playlist()
self.__bot: Client = bot
self.__guild: Guild = guild
self.__timer = Timer(self.__timeout_handler)
self.__playing = False
# Flag to control if the player should stop totally the playing
self.__force_stop = False
self.FFMPEG_OPTIONS = {'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5',
'options': '-vn'}
@property
def playing(self) -> bool:
return self.__playing
@property
def playlist(self) -> Playlist:
return self.__playlist
async def play(self, ctx: Context) -> str:
if not self.__playing:
first_song = self.__playlist.next_song()
await self.__play_music(ctx, first_song)
async def play_prev(self, ctx: Context) -> None:
song = self.__playlist.prev_song()
if song is not None:
if self.__guild.voice_client.is_playing() or self.__guild.voice_client.is_paused():
# Will forbidden next_song to execute after stopping current player
self.__force_stop = True
self.__guild.voice_client.stop()
self.__playing = False
await self.__play_music(ctx, song)
async def force_stop(self) -> None:
try:
if self.__guild.voice_client is None:
return
self.__guild.voice_client.stop()
await self.__guild.voice_client.disconnect()
self.__playlist.clear()
self.__playlist.loop_off()
except Exception as e:
print(f'DEVELOPER NOTE -> Force Stop Error: {e}')
def __play_next(self, error, ctx: Context) -> None:
if self.__force_stop: # If it's forced to stop player
self.__force_stop = False
return None
song = self.__playlist.next_song()
if song is not None:
coro = self.__play_music(ctx, song)
self.__bot.loop.create_task(coro)
else:
self.__playing = False
async def __play_music(self, ctx: Context, song: Song) -> None:
try:
source = await self.__ensure_source(song)
if source is None:
self.__play_next(None, ctx)
self.__playing = True
player = FFmpegPCMAudio(song.source, **self.FFMPEG_OPTIONS)
voice = self.__guild.voice_client
voice.play(player, after=lambda e: self.__play_next(e, ctx))
self.__timer.cancel()
self.__timer = Timer(self.__timeout_handler)
await ctx.invoke(self.__bot.get_command('np'))
songs = self.__playlist.getSongsToPreload()
asyncio.create_task(self.__down.preload(songs))
except:
self.__play_next(None, ctx)
async def __timeout_handler(self) -> None:
if self.__guild.voice_client is None:
return
if self.__guild.voice_client.is_playing() or self.__guild.voice_client.is_paused():
self.__timer = Timer(self.__timeout_handler)
elif self.__guild.voice_client.is_connected():
self.__playlist.clear()
self.__playlist.loop_off()
await self.__guild.voice_client.disconnect()
async def __ensure_source(self, song: Song) -> str:
while True:
await asyncio.sleep(0.1)
if song.source is not None: # If song got downloaded
return song.source
if song.problematic: # If song got any error
return None

View File

@ -44,6 +44,9 @@ class Playlist:
def getCurrentSong(self) -> Song: def getCurrentSong(self) -> Song:
return self.__current return self.__current
def setCurrentSong(self, song: Song) -> Song:
self.__current = song
def getSongsToPreload(self) -> List[Song]: def getSongsToPreload(self) -> List[Song]:
return list(self.__queue)[:self.__configs.MAX_PRELOAD_SONGS] return list(self.__queue)[:self.__configs.MAX_PRELOAD_SONGS]
@ -73,6 +76,7 @@ class Playlist:
# Get the new song # Get the new song
if len(self.__queue) == 0: if len(self.__queue) == 0:
self.__current = None
return None return None
self.__current = self.__queue.popleft() self.__current = self.__queue.popleft()

View File

@ -1,4 +1,4 @@
from Config.Exceptions import DeezerError, InvalidInput, SpotifyError, YoutubeError from Config.Exceptions import DeezerError, InvalidInput, SpotifyError, VulkanError, YoutubeError
from Music.Downloader import Downloader from Music.Downloader import Downloader
from Music.Types import Provider from Music.Types import Provider
from Music.SpotifySearcher import SpotifySearch from Music.SpotifySearcher import SpotifySearch
@ -25,7 +25,10 @@ class Searcher():
track = self.__cleanYoutubeInput(track) track = self.__cleanYoutubeInput(track)
musics = await self.__down.extract_info(track) musics = await self.__down.extract_info(track)
return musics return musics
except: except VulkanError as error:
raise error
except Exception as error:
print(f'[Error in Searcher] -> {error}, {type(error)}')
raise YoutubeError(self.__messages.YOUTUBE_NOT_FOUND, self.__messages.GENERIC_TITLE) raise YoutubeError(self.__messages.YOUTUBE_NOT_FOUND, self.__messages.GENERIC_TITLE)
elif provider == Provider.Spotify: elif provider == Provider.Spotify:

View File

@ -23,6 +23,7 @@ class Song:
else: else:
print(f'DEVELOPER NOTE -> {key} not found in info of music: {self.identifier}') print(f'DEVELOPER NOTE -> {key} not found in info of music: {self.identifier}')
self.destroy() self.destroy()
return
for key in self.__useful_keys: for key in self.__useful_keys:
if key in info.keys(): if key in info.keys():

View File

@ -2,14 +2,16 @@ import asyncio
from os import listdir from os import listdir
from discord import Intents, User, Member from discord import Intents, User, Member
from asyncio import AbstractEventLoop, Semaphore from asyncio import AbstractEventLoop, Semaphore
from multiprocessing import Process, Queue from multiprocessing import Process, Queue, RLock
from threading import Lock, Thread from threading import Lock, Thread
from typing import Callable, List from typing import Callable, List
from discord import Client, Guild, FFmpegPCMAudio, VoiceChannel, TextChannel from discord import Client, Guild, FFmpegPCMAudio, VoiceChannel, TextChannel
from Music.Playlist import Playlist from Music.Playlist import Playlist
from Music.Song import Song from Music.Song import Song
from Config.Configs import Configs from Config.Configs import Configs
from Config.Messages import Messages
from discord.ext.commands import Bot from discord.ext.commands import Bot
from Views.Embeds import Embeds
from Parallelism.Commands import VCommands, VCommandsType from Parallelism.Commands import VCommands, VCommandsType
@ -37,7 +39,8 @@ class PlayerProcess(Process):
Process.__init__(self, name=name, group=None, target=None, args=(), kwargs={}) Process.__init__(self, name=name, group=None, target=None, args=(), kwargs={})
# Synchronization objects # Synchronization objects
self.__playlist: Playlist = playlist self.__playlist: Playlist = playlist
self.__lock: Lock = lock self.__playlistLock: Lock = lock
self.__playerLock: RLock = None
self.__queue: Queue = queue self.__queue: Queue = queue
self.__semStopPlaying: Semaphore = None self.__semStopPlaying: Semaphore = None
self.__loop: AbstractEventLoop = None self.__loop: AbstractEventLoop = None
@ -55,6 +58,8 @@ class PlayerProcess(Process):
self.__botMember: Member = None self.__botMember: Member = None
self.__configs: Configs = None self.__configs: Configs = None
self.__embeds: Embeds = None
self.__messages: Messages = None
self.__playing = False self.__playing = False
self.__forceStop = False self.__forceStop = False
self.FFMPEG_OPTIONS = {'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5', self.FFMPEG_OPTIONS = {'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5',
@ -64,11 +69,14 @@ class PlayerProcess(Process):
"""Method called by process.start(), this will exec the actually _run method in a event loop""" """Method called by process.start(), this will exec the actually _run method in a event loop"""
try: try:
print(f'Starting Process {self.name}') print(f'Starting Process {self.name}')
self.__playerLock = RLock()
self.__loop = asyncio.get_event_loop() self.__loop = asyncio.get_event_loop()
self.__configs = Configs() self.__configs = Configs()
self.__messages = Messages()
self.__embeds = Embeds()
self.__semStopPlaying = Semaphore(0) self.__semStopPlaying = Semaphore(0)
self.__stopped = asyncio.Event()
self.__loop.run_until_complete(self._run()) self.__loop.run_until_complete(self._run())
except Exception as e: except Exception as e:
print(f'[Error in Process {self.name}] -> {e}') print(f'[Error in Process {self.name}] -> {e}')
@ -100,12 +108,14 @@ class PlayerProcess(Process):
async def __playPlaylistSongs(self) -> None: async def __playPlaylistSongs(self) -> None:
if not self.__playing: if not self.__playing:
with self.__lock: with self.__playlistLock:
song = self.__playlist.next_song() song = self.__playlist.next_song()
await self.__playSong(song) if song is not None:
self.__loop.create_task(self.__playSong(song), name=f'Song {song.identifier}')
async def __playSong(self, song: Song) -> None: async def __playSong(self, song: Song) -> None:
"""Function that will trigger the player to play the song"""
try: try:
if song is None: if song is None:
return return
@ -113,36 +123,46 @@ class PlayerProcess(Process):
if song.source is None: if song.source is None:
return self.__playNext(None) return self.__playNext(None)
# If not connected, connect to bind channel
if self.__guild.voice_client is None:
self.__connectToVoiceChannel()
# If the player is already playing return
if self.__guild.voice_client.is_playing():
return
self.__playing = True self.__playing = True
player = FFmpegPCMAudio(song.source, **self.FFMPEG_OPTIONS) player = FFmpegPCMAudio(song.source, **self.FFMPEG_OPTIONS)
voice = self.__guild.voice_client self.__guild.voice_client.play(player, after=lambda e: self.__playNext(e))
voice.play(player, after=lambda e: self.__playNext(e))
self.__timer.cancel() self.__timer.cancel()
self.__timer = TimeoutClock(self.__timeoutHandler, self.__loop) self.__timer = TimeoutClock(self.__timeoutHandler, self.__loop)
# await self.__context.invoke(self.__bot.get_command('np')) await self.__showNowPlaying()
except Exception as e: except Exception as e:
print(f'[ERROR IN PLAY SONG] -> {e}') print(f'[ERROR IN PLAY SONG] -> {e}, {type(e)}')
self.__playNext(None) self.__playNext(None)
def __playNext(self, error) -> None: def __playNext(self, error) -> None:
with self.__playerLock:
if self.__forceStop: # If it's forced to stop player if self.__forceStop: # If it's forced to stop player
self.__forceStop = False self.__forceStop = False
return None return None
with self.__lock: with self.__playlistLock:
song = self.__playlist.next_song() song = self.__playlist.next_song()
if song is not None: if song is not None:
coro = self.__playSong(song) self.__loop.create_task(self.__playSong(song), name=f'Song {song.identifier}')
self.__bot.loop.create_task(coro)
else: else:
with self.__playlistLock:
self.__playlist.loop_off()
self.__playing = False self.__playing = False
async def __playPrev(self, voiceChannelID: int) -> None: async def __playPrev(self, voiceChannelID: int) -> None:
with self.__lock: with self.__playlistLock:
song = self.__playlist.prev_song() song = self.__playlist.prev_song()
if song is not None: if song is not None:
@ -158,7 +178,7 @@ class PlayerProcess(Process):
self.__guild.voice_client.stop() self.__guild.voice_client.stop()
self.__playing = False self.__playing = False
await self.__playSong(song) self.__loop.create_task(self.__playSong(song), name=f'Song {song.identifier}')
def __commandsReceiver(self) -> None: def __commandsReceiver(self) -> None:
while True: while True:
@ -166,23 +186,28 @@ class PlayerProcess(Process):
type = command.getType() type = command.getType()
args = command.getArgs() args = command.getArgs()
print(f'Process {self.name} receive Command: {type} with Args: {args}') try:
self.__playerLock.acquire()
if type == VCommandsType.PAUSE: if type == VCommandsType.PAUSE:
self.__pause() self.__pause()
elif type == VCommandsType.PLAY:
self.__loop.create_task(self.__playPlaylistSongs())
elif type == VCommandsType.PREV:
self.__loop.create_task(self.__playPrev(args))
elif type == VCommandsType.RESUME: elif type == VCommandsType.RESUME:
self.__resume() self.__resume()
elif type == VCommandsType.SKIP: elif type == VCommandsType.SKIP:
self.__skip() self.__skip()
elif type == VCommandsType.PLAY:
asyncio.run_coroutine_threadsafe(self.__playPlaylistSongs(), self.__loop)
elif type == VCommandsType.PREV:
asyncio.run_coroutine_threadsafe(self.__playPrev(args), self.__loop)
elif type == VCommandsType.RESET: elif type == VCommandsType.RESET:
self.__loop.create_task(self.__reset()) asyncio.run_coroutine_threadsafe(self.__reset(), self.__loop)
elif type == VCommandsType.STOP: elif type == VCommandsType.STOP:
self.__loop.create_task(self.__stop()) asyncio.run_coroutine_threadsafe(self.__stop(), self.__loop)
else: else:
print(f'[ERROR] -> Unknown Command Received: {command}') print(f'[ERROR] -> Unknown Command Received: {command}')
except Exception as e:
print(f'[ERROR IN COMMAND RECEIVER] -> {type} - {e}')
finally:
self.__playerLock.release()
def __pause(self) -> None: def __pause(self) -> None:
if self.__guild.voice_client is not None: if self.__guild.voice_client is not None:
@ -204,7 +229,7 @@ class PlayerProcess(Process):
async def __stop(self) -> None: async def __stop(self) -> None:
if self.__guild.voice_client is not None: if self.__guild.voice_client is not None:
if self.__guild.voice_client.is_connected(): if self.__guild.voice_client.is_connected():
with self.__lock: with self.__playlistLock:
self.__playlist.clear() self.__playlist.clear()
self.__playlist.loop_off() self.__playlist.loop_off()
self.__guild.voice_client.stop() self.__guild.voice_client.stop()
@ -220,17 +245,14 @@ class PlayerProcess(Process):
self.__guild.voice_client.stop() self.__guild.voice_client.stop()
async def __forceStop(self) -> None: async def __forceStop(self) -> None:
try:
if self.__guild.voice_client is None: if self.__guild.voice_client is None:
return return
self.__guild.voice_client.stop() self.__guild.voice_client.stop()
await self.__guild.voice_client.disconnect() await self.__guild.voice_client.disconnect()
with self.__lock: with self.__playlistLock:
self.__playlist.clear() self.__playlist.clear()
self.__playlist.loop_off() self.__playlist.loop_off()
except Exception as e:
print(f'DEVELOPER NOTE -> Force Stop Error: {e}')
async def __createBotInstance(self) -> Client: async def __createBotInstance(self) -> Client:
"""Load a new bot instance that should not be directly called. """Load a new bot instance that should not be directly called.
@ -268,12 +290,14 @@ class PlayerProcess(Process):
self.__timer = TimeoutClock(self.__timeoutHandler, self.__loop) self.__timer = TimeoutClock(self.__timeoutHandler, self.__loop)
elif self.__guild.voice_client.is_connected(): elif self.__guild.voice_client.is_connected():
with self.__lock: self.__playerLock.acquire()
with self.__playlistLock:
self.__playlist.clear() self.__playlist.clear()
self.__playlist.loop_off() self.__playlist.loop_off()
self.__playing = False self.__playing = False
await self.__guild.voice_client.disconnect() await self.__guild.voice_client.disconnect()
# Release semaphore to finish process # Release semaphore to finish process
self.__playerLock.release()
self.__semStopPlaying.release() self.__semStopPlaying.release()
except Exception as e: except Exception as e:
print(f'[Error in Timeout] -> {e}') print(f'[Error in Timeout] -> {e}')
@ -307,3 +331,20 @@ class PlayerProcess(Process):
for member in guild_members: for member in guild_members:
if member.id == self.__bot.user.id: if member.id == self.__bot.user.id:
return member return member
async def __showNowPlaying(self) -> None:
# Get the current process of the guild
with self.__playlistLock:
if not self.__playing or self.__playlist.getCurrentSong() is None:
embed = self.__embeds.NOT_PLAYING()
await self.__textChannel.send(embed=embed)
return
if self.__playlist.isLoopingOne():
title = self.__messages.ONE_SONG_LOOPING
else:
title = self.__messages.SONG_PLAYING
info = self.__playlist.getCurrentSong().info
embed = self.__embeds.SONG_INFO(info, title)
await self.__textChannel.send(embed=embed)

View File

@ -13,6 +13,9 @@ class ProcessInfo:
self.__playlist = playlist self.__playlist = playlist
self.__lock = lock self.__lock = lock
def setProcess(self, newProcess: Process) -> None:
self.__process = newProcess
def getProcess(self) -> Process: def getProcess(self) -> Process:
return self.__process return self.__process

View File

@ -23,29 +23,30 @@ class ProcessManager(Singleton):
self.__playersProcess: Dict[Guild, ProcessInfo] = {} self.__playersProcess: Dict[Guild, ProcessInfo] = {}
def setPlayerContext(self, guild: Guild, context: ProcessInfo): def setPlayerContext(self, guild: Guild, context: ProcessInfo):
self.__playersProcess[guild] = context self.__playersProcess[guild.id] = context
def getPlayerContext(self, guild: Guild, context: Context) -> ProcessInfo: def getPlayerInfo(self, guild: Guild, context: Context) -> ProcessInfo:
"""Return the process info for the guild, if not, create one""" """Return the process info for the guild, if not, create one"""
try: try:
if guild not in self.__playersProcess.keys(): if guild.id not in self.__playersProcess.keys():
self.__playersProcess[guild] = self.__createProcess(context) self.__playersProcess[guild.id] = self.__createProcessInfo(context)
else: else:
if not self.__playersProcess[guild].getProcess().is_alive(): # If the process has ended create a new one
self.__playersProcess[guild] = self.__createProcess(context) if not self.__playersProcess[guild.id].getProcess().is_alive():
self.__playersProcess[guild.id] = self.__recreateProcess(context)
return self.__playersProcess[guild] return self.__playersProcess[guild.id]
except Exception as e: except Exception as e:
print(f'[Error In GetPlayerContext] -> {e}') print(f'[Error In GetPlayerContext] -> {e}')
def getRunningPlayerInfo(self, guild: Guild) -> ProcessInfo: def getRunningPlayerInfo(self, guild: Guild) -> ProcessInfo:
"""Return the process info for the guild, if not, return None""" """Return the process info for the guild, if not, return None"""
if guild not in self.__playersProcess.keys(): if guild.id not in self.__playersProcess.keys():
return None return None
return self.__playersProcess[guild] return self.__playersProcess[guild.id]
def __createProcess(self, context: Context): def __createProcessInfo(self, context: Context) -> ProcessInfo:
guildID: int = context.guild.id guildID: int = context.guild.id
textID: int = context.channel.id textID: int = context.channel.id
voiceID: int = context.author.voice.channel.id voiceID: int = context.author.voice.channel.id
@ -60,6 +61,23 @@ class ProcessManager(Singleton):
return processInfo return processInfo
def __recreateProcess(self, context: Context) -> ProcessInfo:
"""Create a new process info using previous playlist"""
guildID: int = context.guild.id
textID: int = context.channel.id
voiceID: int = context.author.voice.channel.id
authorID: int = context.author.id
playlist: Playlist = self.__playersProcess[guildID].getPlaylist()
lock = Lock()
queue = Queue()
process = PlayerProcess(context.guild.name, playlist, lock, queue,
guildID, textID, voiceID, authorID)
processInfo = ProcessInfo(process, queue, playlist, lock)
return processInfo
class VManager(BaseManager): class VManager(BaseManager):
pass pass

View File

@ -32,19 +32,6 @@ class Utils:
return False return False
class Timer:
def __init__(self, callback):
self.__callback = callback
self.__task = asyncio.create_task(self.__executor())
async def __executor(self):
await asyncio.sleep(config.VC_TIMEOUT)
await self.__callback()
def cancel(self):
self.__task.cancel()
def run_async(func): def run_async(func):
@wraps(func) @wraps(func)
async def run(*args, loop=None, executor=None, **kwargs): async def run(*args, loop=None, executor=None, **kwargs):