Finishing to add the ThreadPlayer to the Bot, allowing the user to switch between the versions

This commit is contained in:
Rafael Vargas
2023-02-20 01:52:59 -03:00
parent 1f45b64a62
commit a34a6a78d7
5 changed files with 125 additions and 77 deletions

View File

@@ -1,5 +1,5 @@
from typing import List
from discord import Button, TextChannel
from discord import Button, Guild, TextChannel
from discord.ui import View
from Config.Emojis import VEmojis
from Messages.MessagesCategory import MessagesCategory
@@ -21,6 +21,11 @@ from Handlers.QueueHandler import QueueHandler
class ProcessCommandsExecutor:
MESSAGES = Messages()
EMBEDS = VEmbeds()
EMOJIS = VEmojis()
MSG_MANAGER = MessagesManager()
def __init__(self, bot: VulkanBot, guildID: int) -> None:
self.__bot = bot
self.__guildID = guildID
@@ -29,6 +34,56 @@ class ProcessCommandsExecutor:
self.__embeds = VEmbeds()
self.__emojis = VEmojis()
@classmethod
async def sendNowPlayingToGuild(cls, bot: VulkanBot, playlist: Playlist, channel: TextChannel, song: Song, guild: Guild) -> None:
# Get the lock of the playlist
if playlist.isLoopingOne():
title = cls.MESSAGES.ONE_SONG_LOOPING
else:
title = cls.MESSAGES.SONG_PLAYING
# Create View and Embed
embed = cls.EMBEDS.SONG_INFO(song.info, title)
view = cls.__getPlayerViewForGuild(channel, guild.id, bot)
# Send Message and add to the MessagesManager
message = await channel.send(embed=embed, view=view)
await cls.MSG_MANAGER.addMessageAndClearPrevious(guild.id, MessagesCategory.NOW_PLAYING, message, view)
# Set in the view the message witch contains the view
view.set_message(message=message)
@classmethod
def __getPlayerViewForGuild(cls, channel: TextChannel, guildID: int, bot: VulkanBot) -> View:
buttons = cls.__getPlayerButtonsForGuild(channel, guildID, bot)
view = BasicView(bot, buttons)
return view
@classmethod
def __getPlayerButtonsForGuild(cls, textChannel: TextChannel, guildID: int, bot: VulkanBot) -> List[Button]:
"""Create the Buttons to be inserted in the Player View"""
buttons: List[Button] = []
buttons.append(HandlerButton(bot, PrevHandler, cls.EMOJIS.BACK,
textChannel, guildID, MessagesCategory.PLAYER, "Back"))
buttons.append(HandlerButton(bot, PauseHandler, cls.EMOJIS.PAUSE,
textChannel, guildID, MessagesCategory.PLAYER, "Pause"))
buttons.append(HandlerButton(bot, ResumeHandler, cls.EMOJIS.PLAY,
textChannel, guildID, MessagesCategory.PLAYER, "Play"))
buttons.append(HandlerButton(bot, StopHandler, cls.EMOJIS.STOP,
textChannel, guildID, MessagesCategory.PLAYER, "Stop"))
buttons.append(HandlerButton(bot, SkipHandler, cls.EMOJIS.SKIP,
textChannel, guildID, MessagesCategory.PLAYER, "Skip"))
buttons.append(HandlerButton(bot, QueueHandler, cls.EMOJIS.QUEUE,
textChannel, guildID, MessagesCategory.QUEUE, "Songs"))
buttons.append(HandlerButton(bot, LoopHandler, cls.EMOJIS.LOOP_ONE,
textChannel, guildID, MessagesCategory.LOOP, "Loop One", 'One'))
buttons.append(HandlerButton(bot, LoopHandler, cls.EMOJIS.LOOP_OFF,
textChannel, guildID, MessagesCategory.LOOP, "Loop Off", 'Off'))
buttons.append(HandlerButton(bot, LoopHandler, cls.EMOJIS.LOOP_ALL,
textChannel, guildID, MessagesCategory.LOOP, "Loop All", 'All'))
return buttons
async def sendNowPlaying(self, playlist: Playlist, channel: TextChannel, song: Song) -> None:
# Get the lock of the playlist
if playlist.isLoopingOne():

View File

@@ -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, Coroutine
from typing import Callable
from discord import Guild, FFmpegPCMAudio, VoiceChannel
from Music.Playlist import Playlist
from Music.Song import Song
@@ -33,10 +33,11 @@ class ThreadPlayer(Thread):
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={})
print(f'Starting Player Thread for Guild {self.name}')
# Synchronization objects
self.__playlist: Playlist = playlist
self.__playlistLock: Lock = lock
self.__loop: AbstractEventLoop = None
self.__loop: AbstractEventLoop = bot.loop
self.__playerLock: RLock = RLock()
# Discord context ID
self.__voiceChannelID = voiceID
@@ -48,30 +49,13 @@ class ThreadPlayer(Thread):
self.__callback = callbackToSendCommand
self.__exitCB = exitCB
self.__bot = bot
self.__timer = TimeoutClock(self.__timeoutHandler, self.__loop)
self.__playing = False
self.__forceStop = False
self.FFMPEG_OPTIONS = {'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5',
'options': '-vn'}
def run(self) -> None:
"""This method is called automatically when the Thread starts"""
try:
print(f'Starting Player Thread for Guild {self.name}')
self.__loop = self.__bot.loop
self.__loop.run_until_complete(self._run())
except Exception as e:
print(f'[Error in Process {self.name}] -> {e}')
async def _run(self) -> None:
# Connect to voice Channel
await self.__connectToVoiceChannel()
# Start the timeout function
self.__timer = TimeoutClock(self.__timeoutHandler, self.__loop)
# Start a Task to play songs
self.__loop.create_task(self.__playPlaylistSongs())
def __verifyIfIsPlaying(self) -> bool:
if self.__voiceClient is None:
return False
@@ -89,8 +73,7 @@ class ThreadPlayer(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}')
await self.__playSong(song)
self.__playing = True
async def __playSong(self, song: Song) -> None:
@@ -132,7 +115,7 @@ class ThreadPlayer(Thread):
self.__timer = TimeoutClock(self.__timeoutHandler, self.__loop)
nowPlayingCommand = VCommands(VCommandsType.NOW_PLAYING, song)
await self.__callback(nowPlayingCommand, self.__guild.id, song)
await self.__callback(nowPlayingCommand, self.__guild, song)
except Exception as e:
print(f'[ERROR IN PLAY SONG FUNCTION] -> {e}, {type(e)}')
self.__playNext(None)
@@ -157,7 +140,8 @@ class ThreadPlayer(Thread):
self.__songPlaying = None
self.__playing = False
# Send a command to the main process to kill this thread
self.__exitCB(self.__guild.id)
self.__exitCB(self.__guild)
def __verifyIfSongAvailable(self, song: Song) -> bool:
"""Verify the song source to see if it's already expired"""
@@ -214,12 +198,12 @@ class ThreadPlayer(Thread):
self.__loop.create_task(self.__playSong(song), name=f'Song {song.identifier}')
async def receiveCommand(self, command: VCommands) -> None:
type = command.getType()
args = command.getArgs()
print(f'Player Thread {self.__guild.name} received command {type}')
try:
self.__playerLock.acquire()
type = command.getType()
args = command.getArgs()
# print(f'Player Thread {self.__guild.name} received command {type}')
if type == VCommandsType.PAUSE:
self.__pause()
elif type == VCommandsType.RESUME:

View File

@@ -1,4 +1,4 @@
from multiprocessing import Lock
from threading import RLock
from typing import Any, Dict, Union
from Config.Singleton import Singleton
from discord import Guild, Interaction, TextChannel
@@ -8,6 +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.ProcessExecutor import ProcessCommandsExecutor
from Parallelism.ThreadPlayer import ThreadPlayer
@@ -16,7 +17,7 @@ class ThreadPlayerInfo:
Class to store the reference to all structures to maintain a player thread
"""
def __init__(self, thread: ThreadPlayer, playlist: Playlist, lock: Lock, textChannel: TextChannel) -> None:
def __init__(self, thread: ThreadPlayer, playlist: Playlist, lock: RLock, textChannel: TextChannel) -> None:
self.__thread = thread
self.__playlist = playlist
self.__lock = lock
@@ -28,7 +29,7 @@ class ThreadPlayerInfo:
def getPlaylist(self) -> Playlist:
return self.__playlist
def getLock(self) -> Lock:
def getLock(self) -> RLock:
return self.__lock
def getTextChannel(self) -> TextChannel:
@@ -55,20 +56,20 @@ class ThreadPlayerManager(Singleton, AbstractPlayersManager):
await player.receiveCommand(command)
async def __receiveCommand(self, command: VCommands, guildID: int, args: Any) -> None:
async def __receiveCommand(self, command: VCommands, guild: Guild, args: Any) -> None:
commandType = command.getType()
if commandType == VCommandsType.NOW_PLAYING:
await self.showNowPlaying(guildID, args)
await self.showNowPlaying(guild, args)
else:
print(
f'[ERROR] -> Command not processable received from Thread {guildID}: {commandType}')
f'[ERROR] -> Command not processable received from Thread {guild.name}: {commandType}')
def getPlayerPlaylist(self, guild: Guild) -> Playlist:
playerInfo = self.__getRunningPlayerInfo(guild)
if playerInfo:
return playerInfo.getPlaylist()
def getPlayerLock(self, guild: Guild) -> Lock:
def getPlayerLock(self, guild: Guild) -> RLock:
playerInfo = self.__getRunningPlayerInfo(guild)
if playerInfo:
return playerInfo.getLock()
@@ -118,7 +119,7 @@ class ThreadPlayerManager(Singleton, AbstractPlayersManager):
voiceChannel = self.__bot.get_channel(voiceID)
playlist = Playlist()
lock = Lock()
lock = RLock()
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)
@@ -126,13 +127,14 @@ class ThreadPlayerManager(Singleton, AbstractPlayersManager):
return playerInfo
def __deleteThread(self, guildID: int) -> None:
def __deleteThread(self, guild: Guild) -> None:
"""Tries to delete the thread and removes all the references to it"""
playerInfo = self.__playersThreads[guildID]
print(f'[THREAD MANAGER] -> Deleting Thread for guild {guild.name}')
playerInfo = self.__playersThreads[guild.id]
if playerInfo:
thread = playerInfo.getPlayer()
self.__playersThreads.pop(guild.id)
del thread
self.__playersThreads.popitem(thread)
def __recreateThread(self, guild: Guild, context: Union[Context, Interaction]) -> ThreadPlayerInfo:
self.__stopPossiblyRunningProcess(guild)
@@ -145,7 +147,7 @@ class ThreadPlayerManager(Singleton, AbstractPlayersManager):
voiceChannel = self.__bot.get_channel(voiceID)
playlist = self.__playersThreads[guildID].getPlaylist()
lock = Lock()
lock = RLock()
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)
@@ -153,7 +155,9 @@ class ThreadPlayerManager(Singleton, AbstractPlayersManager):
return playerInfo
async def showNowPlaying(self, guildID: int, song: Song) -> None:
commandExecutor = self.__playersCommandsExecutor[guildID]
processInfo = self.__playersThreads[guildID]
await commandExecutor.sendNowPlaying(processInfo, song)
async def showNowPlaying(self, guild: Guild, song: Song) -> None:
processInfo = self.__playersThreads[guild.id]
playlist = processInfo.getPlaylist()
txtChannel = processInfo.getTextChannel()
await ProcessCommandsExecutor.sendNowPlayingToGuild(self.__bot, playlist, txtChannel, song, guild)