From 1f45b64a620df6486608dd57a5c4e2c26dba1a29 Mon Sep 17 00:00:00 2001 From: Rafael Vargas Date: Sun, 19 Feb 2023 13:40:37 -0300 Subject: [PATCH] Sending more code --- DiscordCogs/MusicCog.py | 2 +- Handlers/JumpMusicHandler.py | 2 +- Handlers/PauseHandler.py | 2 +- Handlers/PlayHandler.py | 4 +- Handlers/PrevHandler.py | 2 +- Handlers/ResetHandler.py | 2 +- Handlers/ResumeHandler.py | 2 +- Handlers/SkipHandler.py | 2 +- Handlers/StopHandler.py | 2 +- Parallelism/AbstractProcessManager.py | 2 +- .../{PlayerProcess.py => ProcessPlayer.py} | 12 ++--- Parallelism/ProcessPlayerManager.py | 12 ++--- .../{PlayerThread.py => ThreadPlayer.py} | 24 +++++----- Parallelism/ThreadPlayerManager.py | 48 +++++++++++++++---- 14 files changed, 69 insertions(+), 49 deletions(-) rename Parallelism/{PlayerProcess.py => ProcessPlayer.py} (98%) rename Parallelism/{PlayerThread.py => ThreadPlayer.py} (95%) diff --git a/DiscordCogs/MusicCog.py b/DiscordCogs/MusicCog.py index 05d5d10..5d6709b 100644 --- a/DiscordCogs/MusicCog.py +++ b/DiscordCogs/MusicCog.py @@ -40,7 +40,7 @@ class MusicCog(Cog): self.__bot: VulkanBot = bot self.__embeds = VEmbeds() configs = VConfigs() - if configs.SHOULD_AUTO_DISCONNECT_WHEN_ALONE: + if configs.SONG_PLAYBACK_IN_SEPARATE_PROCESS: configs.setPlayersManager(ProcessPlayerManager(bot)) else: configs.setPlayersManager(ThreadPlayerManager(bot)) diff --git a/Handlers/JumpMusicHandler.py b/Handlers/JumpMusicHandler.py index 4ad5fe6..2210eee 100644 --- a/Handlers/JumpMusicHandler.py +++ b/Handlers/JumpMusicHandler.py @@ -49,7 +49,7 @@ class JumpMusicHandler(AbstractHandler): # Send a command to the player to skip the music command = VCommands(VCommandsType.SKIP, None) - playersManager.sendCommandToPlayer(command, self.guild) + await playersManager.sendCommandToPlayer(command, self.guild) return HandlerResponse(self.ctx) except: diff --git a/Handlers/PauseHandler.py b/Handlers/PauseHandler.py index 3447ccc..681a48e 100644 --- a/Handlers/PauseHandler.py +++ b/Handlers/PauseHandler.py @@ -16,7 +16,7 @@ class PauseHandler(AbstractHandler): playersManager: AbstractPlayersManager = self.config.getPlayersManager() if playersManager.verifyIfPlayerExists(self.guild): command = VCommands(VCommandsType.PAUSE, None) - playersManager.sendCommandToPlayer(command, self.guild) + await playersManager.sendCommandToPlayer(command, self.guild) embed = self.embeds.PLAYER_PAUSED() return HandlerResponse(self.ctx, embed) diff --git a/Handlers/PlayHandler.py b/Handlers/PlayHandler.py index a8e3979..2aae0d3 100644 --- a/Handlers/PlayHandler.py +++ b/Handlers/PlayHandler.py @@ -71,7 +71,7 @@ class PlayHandler(AbstractHandler): # Release the acquired Lock playerLock.release() playCommand = VCommands(VCommandsType.PLAY, None) - playersManager.sendCommandToPlayer(playCommand, self.guild) + await playersManager.sendCommandToPlayer(playCommand, self.guild) else: playersManager.resetPlayer(self.guild, self.ctx) embed = self.embeds.PLAYER_RESTARTED() @@ -127,7 +127,7 @@ class PlayHandler(AbstractHandler): acquired = playerLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT) if acquired: playlist.add_song(song) - playersManager.sendCommandToPlayer(playCommand, self.guild) + await playersManager.sendCommandToPlayer(playCommand, self.guild) playerLock.release() else: playersManager.resetPlayer(self.guild, self.ctx) diff --git a/Handlers/PrevHandler.py b/Handlers/PrevHandler.py index 22a0577..09c5b14 100644 --- a/Handlers/PrevHandler.py +++ b/Handlers/PrevHandler.py @@ -38,7 +38,7 @@ class PrevHandler(AbstractHandler): # Send a prev command, together with the user voice channel prevCommand = VCommands(VCommandsType.PREV, self.author.voice.channel.id) - playersManager.sendCommandToPlayer(prevCommand, self.guild) + await playersManager.sendCommandToPlayer(prevCommand, self.guild) embed = self.embeds.RETURNING_SONG() return HandlerResponse(self.ctx, embed) diff --git a/Handlers/ResetHandler.py b/Handlers/ResetHandler.py index bdc3451..4cf8eb7 100644 --- a/Handlers/ResetHandler.py +++ b/Handlers/ResetHandler.py @@ -16,7 +16,7 @@ class ResetHandler(AbstractHandler): playersManager: AbstractPlayersManager = self.config.getPlayersManager() if playersManager.verifyIfPlayerExists(self.guild): command = VCommands(VCommandsType.RESET, None) - playersManager.sendCommandToPlayer(command, self.guild) + await playersManager.sendCommandToPlayer(command, self.guild) return HandlerResponse(self.ctx) else: embed = self.embeds.NOT_PLAYING() diff --git a/Handlers/ResumeHandler.py b/Handlers/ResumeHandler.py index 260f448..5a187e9 100644 --- a/Handlers/ResumeHandler.py +++ b/Handlers/ResumeHandler.py @@ -16,7 +16,7 @@ class ResumeHandler(AbstractHandler): playersManager: AbstractPlayersManager = self.config.getPlayersManager() if playersManager.verifyIfPlayerExists(self.guild): command = VCommands(VCommandsType.RESUME, None) - playersManager.sendCommandToPlayer(command, self.guild) + await playersManager.sendCommandToPlayer(command, self.guild) embed = self.embeds.PLAYER_RESUMED() return HandlerResponse(self.ctx, embed) else: diff --git a/Handlers/SkipHandler.py b/Handlers/SkipHandler.py index a740443..6400fc3 100644 --- a/Handlers/SkipHandler.py +++ b/Handlers/SkipHandler.py @@ -16,7 +16,7 @@ class SkipHandler(AbstractHandler): playersManager: AbstractPlayersManager = self.config.getPlayersManager() if playersManager.verifyIfPlayerExists(self.guild): command = VCommands(VCommandsType.SKIP, None) - playersManager.sendCommandToPlayer(command, self.guild) + await playersManager.sendCommandToPlayer(command, self.guild) embed = self.embeds.SKIPPING_SONG() return HandlerResponse(self.ctx, embed) else: diff --git a/Handlers/StopHandler.py b/Handlers/StopHandler.py index a3b2d32..07972dc 100644 --- a/Handlers/StopHandler.py +++ b/Handlers/StopHandler.py @@ -16,7 +16,7 @@ class StopHandler(AbstractHandler): playersManager: AbstractPlayersManager = self.config.getPlayersManager() if playersManager.verifyIfPlayerExists(self.guild): command = VCommands(VCommandsType.STOP, None) - playersManager.sendCommandToPlayer(command, self.guild) + await playersManager.sendCommandToPlayer(command, self.guild) embed = self.embeds.STOPPING_PLAYER() return HandlerResponse(self.ctx, embed) else: diff --git a/Parallelism/AbstractProcessManager.py b/Parallelism/AbstractProcessManager.py index 693f803..edcd432 100644 --- a/Parallelism/AbstractProcessManager.py +++ b/Parallelism/AbstractProcessManager.py @@ -13,7 +13,7 @@ class AbstractPlayersManager(ABC): pass @abstractmethod - def sendCommandToPlayer(self, command: VCommands, guild: Guild, forceCreation: bool = False, context: Union[Context, Interaction] = None): + async def sendCommandToPlayer(self, command: VCommands, guild: Guild, forceCreation: bool = False, context: Union[Context, Interaction] = None): """If the forceCreation boolean is True, then the context must be provided for the Player to be created""" pass diff --git a/Parallelism/PlayerProcess.py b/Parallelism/ProcessPlayer.py similarity index 98% rename from Parallelism/PlayerProcess.py rename to Parallelism/ProcessPlayer.py index c953a19..a1ac5f2 100644 --- a/Parallelism/PlayerProcess.py +++ b/Parallelism/ProcessPlayer.py @@ -2,11 +2,11 @@ import asyncio from time import sleep, time from urllib.parse import parse_qs, urlparse from Music.VulkanInitializer import VulkanInitializer -from discord import Member, VoiceClient +from discord import VoiceClient from asyncio import AbstractEventLoop, Semaphore, Queue from multiprocessing import Process, RLock, Lock, Queue from threading import Thread -from typing import Callable, List +from typing import Callable from discord import Guild, FFmpegPCMAudio, VoiceChannel from Music.Playlist import Playlist from Music.Song import Song @@ -29,7 +29,7 @@ class TimeoutClock: self.__task.cancel() -class PlayerProcess(Process): +class ProcessPlayer(Process): """Process that will play songs, receive commands from the main process by a Queue""" def __init__(self, name: str, playlist: Playlist, lock: Lock, queueToReceive: Queue, queueToSend: Queue, guildID: int, voiceID: int) -> None: @@ -421,9 +421,3 @@ class PlayerProcess(Process): except Exception as e: print(f'[ERROR CONNECTING TO VC] -> {e}') return False - - def __getBotMember(self) -> Member: - guild_members: List[Member] = self.__guild.members - for member in guild_members: - if member.id == self.__bot.user.id: - return member diff --git a/Parallelism/ProcessPlayerManager.py b/Parallelism/ProcessPlayerManager.py index 6f302cc..50bcaa0 100644 --- a/Parallelism/ProcessPlayerManager.py +++ b/Parallelism/ProcessPlayerManager.py @@ -6,12 +6,12 @@ from queue import Empty from threading import Thread from typing import Dict, Tuple, Union from Config.Singleton import Singleton -from discord import Guild, Interaction, TextChannel +from discord import Guild, Interaction, TextChannel, VoiceChannel from discord.ext.commands import Context from Parallelism.AbstractProcessManager import AbstractPlayersManager from Parallelism.ProcessExecutor import ProcessCommandsExecutor from Music.Song import Song -from Parallelism.PlayerProcess import PlayerProcess +from Parallelism.ProcessPlayer import ProcessPlayer from Music.Playlist import Playlist from Parallelism.Commands import VCommands, VCommandsType from Music.VulkanBot import VulkanBot @@ -74,18 +74,18 @@ class ProcessPlayerManager(Singleton, AbstractPlayersManager): if not super().created: self.__bot = bot VManager.register('Playlist', Playlist) + VManager.register('VoiceChannel', VoiceChannel) self.__manager = VManager() self.__manager.start() self.__playersProcess: Dict[int, PlayerProcessInfo] = {} self.__playersListeners: Dict[int, Tuple[Thread, bool]] = {} self.__playersCommandsExecutor: Dict[int, ProcessCommandsExecutor] = {} - def sendCommandToPlayer(self, command: VCommands, guild: Guild, forceCreation: bool = False, context: Union[Context, Interaction] = None): + async def sendCommandToPlayer(self, command: VCommands, guild: Guild, forceCreation: bool = False, context: Union[Context, Interaction] = None): if forceCreation: processInfo = self.createPlayerForGuild(guild, context) else: processInfo = self.__getRunningPlayerInfo(guild) - if processInfo == None: return @@ -149,7 +149,7 @@ class ProcessPlayerManager(Singleton, AbstractPlayersManager): lock = Lock() queueToListen = Queue() queueToSend = Queue() - process = PlayerProcess(context.guild.name, playlist, lock, queueToSend, + process = ProcessPlayer(context.guild.name, playlist, lock, queueToSend, queueToListen, guildID, voiceID) processInfo = PlayerProcessInfo(process, queueToSend, queueToListen, playlist, lock, context.channel) @@ -196,7 +196,7 @@ class ProcessPlayerManager(Singleton, AbstractPlayersManager): lock = Lock() queueToListen = Queue() queueToSend = Queue() - process = PlayerProcess(context.guild.name, playlist, lock, queueToSend, + process = ProcessPlayer(context.guild.name, playlist, lock, queueToSend, queueToListen, guildID, textID, voiceID, authorID) processInfo = PlayerProcessInfo(process, queueToSend, queueToListen, playlist, lock, context.channel) diff --git a/Parallelism/PlayerThread.py b/Parallelism/ThreadPlayer.py similarity index 95% rename from Parallelism/PlayerThread.py rename to Parallelism/ThreadPlayer.py index e15b48a..3111def 100644 --- a/Parallelism/PlayerThread.py +++ b/Parallelism/ThreadPlayer.py @@ -5,7 +5,7 @@ from discord import VoiceClient from asyncio import AbstractEventLoop from threading import RLock, Thread from multiprocessing import Lock -from typing import Callable +from typing import Callable, Coroutine from discord import Guild, FFmpegPCMAudio, VoiceChannel from Music.Playlist import Playlist from Music.Song import Song @@ -28,10 +28,10 @@ class TimeoutClock: self.__task.cancel() -class PlayerThread(Thread): +class ThreadPlayer(Thread): """Player Thread to control the song playback in the same Process of the Main Process""" - def __init__(self, bot: VulkanBot, guild: Guild, name: str, voiceChannel: VoiceChannel, playlist: Playlist, lock: Lock, guildID: int, voiceID: int) -> None: + def __init__(self, bot: VulkanBot, guild: Guild, name: str, voiceChannel: VoiceChannel, playlist: Playlist, lock: Lock, guildID: int, voiceID: int, callbackToSendCommand: Callable, exitCB: Callable) -> None: Thread.__init__(self, name=name, group=None, target=None, args=(), kwargs={}) # Synchronization objects self.__playlist: Playlist = playlist @@ -39,14 +39,15 @@ class PlayerThread(Thread): self.__loop: AbstractEventLoop = None self.__playerLock: RLock = RLock() # Discord context ID - self.__guildID = guildID self.__voiceChannelID = voiceID self.__guild: Guild = guild - self.__bot: VulkanBot = bot self.__voiceChannel: VoiceChannel = voiceChannel self.__voiceClient: VoiceClient = None self.__downloader = Downloader() + self.__callback = callbackToSendCommand + self.__exitCB = exitCB + self.__bot = bot self.__playing = False self.__forceStop = False @@ -57,8 +58,7 @@ class PlayerThread(Thread): """This method is called automatically when the Thread starts""" try: print(f'Starting Player Thread for Guild {self.name}') - self.__loop = asyncio.get_event_loop_policy().new_event_loop() - asyncio.set_event_loop(self.__loop) + self.__loop = self.__bot.loop self.__loop.run_until_complete(self._run()) except Exception as e: @@ -89,6 +89,7 @@ class PlayerThread(Thread): song = self.__playlist.next_song() if song is not None: + print('Criando song') self.__loop.create_task(self.__playSong(song), name=f'Song {song.identifier}') self.__playing = True @@ -131,7 +132,7 @@ class PlayerThread(Thread): self.__timer = TimeoutClock(self.__timeoutHandler, self.__loop) nowPlayingCommand = VCommands(VCommandsType.NOW_PLAYING, song) - self.__queueSend.put(nowPlayingCommand) + await self.__callback(nowPlayingCommand, self.__guild.id, song) except Exception as e: print(f'[ERROR IN PLAY SONG FUNCTION] -> {e}, {type(e)}') self.__playNext(None) @@ -155,11 +156,8 @@ class PlayerThread(Thread): self.__playlist.loop_off() self.__songPlaying = None self.__playing = False - # Send a command to the main process put this one to sleep - sleepCommand = VCommands(VCommandsType.SLEEPING) - self.__queueSend.put(sleepCommand) - # Release the semaphore to finish the process - self.__semStopPlaying.release() + # Send a command to the main process to kill this thread + self.__exitCB(self.__guild.id) def __verifyIfSongAvailable(self, song: Song) -> bool: """Verify the song source to see if it's already expired""" diff --git a/Parallelism/ThreadPlayerManager.py b/Parallelism/ThreadPlayerManager.py index aaabec3..2e8a13c 100644 --- a/Parallelism/ThreadPlayerManager.py +++ b/Parallelism/ThreadPlayerManager.py @@ -1,5 +1,5 @@ from multiprocessing import Lock -from typing import Dict, Union +from typing import Any, Dict, Union from Config.Singleton import Singleton from discord import Guild, Interaction, TextChannel from discord.ext.commands import Context @@ -8,7 +8,7 @@ from Music.Song import Song from Music.Playlist import Playlist from Parallelism.Commands import VCommands, VCommandsType from Music.VulkanBot import VulkanBot -from Parallelism.PlayerThread import PlayerThread +from Parallelism.ThreadPlayer import ThreadPlayer class ThreadPlayerInfo: @@ -16,13 +16,13 @@ class ThreadPlayerInfo: Class to store the reference to all structures to maintain a player thread """ - def __init__(self, thread: PlayerThread, playlist: Playlist, lock: Lock, textChannel: TextChannel) -> None: + def __init__(self, thread: ThreadPlayer, playlist: Playlist, lock: Lock, textChannel: TextChannel) -> None: self.__thread = thread self.__playlist = playlist self.__lock = lock self.__textChannel = textChannel - def getThread(self) -> PlayerThread: + def getPlayer(self) -> ThreadPlayer: return self.__thread def getPlaylist(self) -> Playlist: @@ -45,8 +45,23 @@ class ThreadPlayerManager(Singleton, AbstractPlayersManager): self.__bot = bot self.__playersThreads: Dict[int, ThreadPlayerInfo] = {} - def sendCommandToPlayer(self, command: VCommands, guild: Guild, forceCreation: bool = False, context: Union[Context, Interaction] = None): - return super().sendCommandToPlayer(command, guild, forceCreation, context) + async def sendCommandToPlayer(self, command: VCommands, guild: Guild, forceCreation: bool = False, context: Union[Context, Interaction] = None): + playerInfo = self.__playersThreads[guild.id] + player = playerInfo.getPlayer() + if player is None and forceCreation: + self.__createPlayerThreadInfo(context) + if player is None: + return + + await player.receiveCommand(command) + + async def __receiveCommand(self, command: VCommands, guildID: int, args: Any) -> None: + commandType = command.getType() + if commandType == VCommandsType.NOW_PLAYING: + await self.showNowPlaying(guildID, args) + else: + print( + f'[ERROR] -> Command not processable received from Thread {guildID}: {commandType}') def getPlayerPlaylist(self, guild: Guild) -> Playlist: playerInfo = self.__getRunningPlayerInfo(guild) @@ -67,7 +82,7 @@ class ThreadPlayerManager(Singleton, AbstractPlayersManager): self.__playersThreads[guild.id] = self.__createPlayerThreadInfo(context) else: # If the thread has ended create a new one - if not self.__playersThreads[guild.id].getThread().is_alive(): + if not self.__playersThreads[guild.id].getPlayer().is_alive(): self.__playersThreads[guild.id] = self.__recreateThread(guild, context) return self.__playersThreads[guild.id] @@ -80,7 +95,7 @@ class ThreadPlayerManager(Singleton, AbstractPlayersManager): # Recreate the thread keeping the playlist newPlayerInfo = self.__recreateThread(guild, context) - newPlayerInfo.getThread().start() + newPlayerInfo.getPlayer().start() # Send a command to start the play again playCommand = VCommands(VCommandsType.PLAY) newPlayerInfo.getQueueToPlayer().put(playCommand) @@ -100,14 +115,25 @@ class ThreadPlayerManager(Singleton, AbstractPlayersManager): else: voiceID: int = context.author.voice.channel.id + voiceChannel = self.__bot.get_channel(voiceID) + playlist = Playlist() lock = Lock() - player = PlayerThread(context.guild.name, playlist, lock, guildID, voiceID) + player = ThreadPlayer(self.__bot, context.guild, context.guild.name, + voiceChannel, playlist, lock, guildID, voiceID, self.__receiveCommand, self.__deleteThread) playerInfo = ThreadPlayerInfo(player, playlist, lock, context.channel) player.start() return playerInfo + def __deleteThread(self, guildID: int) -> None: + """Tries to delete the thread and removes all the references to it""" + playerInfo = self.__playersThreads[guildID] + if playerInfo: + thread = playerInfo.getPlayer() + del thread + self.__playersThreads.popitem(thread) + def __recreateThread(self, guild: Guild, context: Union[Context, Interaction]) -> ThreadPlayerInfo: self.__stopPossiblyRunningProcess(guild) @@ -116,10 +142,12 @@ class ThreadPlayerManager(Singleton, AbstractPlayersManager): voiceID: int = context.user.voice.channel.id else: voiceID: int = context.author.voice.channel.id + voiceChannel = self.__bot.get_channel(voiceID) playlist = self.__playersThreads[guildID].getPlaylist() lock = Lock() - player = PlayerThread(context.guild.name, playlist, lock, guildID, voiceID) + player = ThreadPlayer(self.__bot, context.guild, context.guild.name, + voiceChannel, playlist, lock, guildID, voiceID, self.__receiveCommand, self.__deleteThread) playerInfo = ThreadPlayerInfo(player, playlist, lock, context.channel) player.start()