mirror of
https://github.com/RafaelSolVargas/Vulkan.git
synced 2025-10-29 16:57:23 +00:00
Finishing to add the ThreadPlayer to the Bot, allowing the user to switch between the versions
This commit is contained in:
parent
1f45b64a62
commit
a34a6a78d7
@ -11,7 +11,11 @@ class VConfigs(Singleton):
|
||||
self.SHOULD_AUTO_DISCONNECT_WHEN_ALONE = False
|
||||
# Recommended to be True, except in cases when your Bot is present in thousands servers, in that case
|
||||
# the delay to start a new Python process for the playback is too much, and to avoid that you set as False
|
||||
self.SONG_PLAYBACK_IN_SEPARATE_PROCESS = False
|
||||
# This feature is for now in testing period, for a more stable version, keep this boolean = True
|
||||
self.SONG_PLAYBACK_IN_SEPARATE_PROCESS = True
|
||||
# Maximum of songs that will be downloaded at once, the higher this number is, the faster the songs will be all available
|
||||
# but the slower will be the others commands of the Bot during the downloading time, for example, the playback quality
|
||||
self.MAX_DOWNLOAD_SONGS_AT_A_TIME = 5
|
||||
|
||||
self.BOT_PREFIX = '!'
|
||||
try:
|
||||
|
||||
@ -79,14 +79,8 @@ class PlayHandler(AbstractHandler):
|
||||
|
||||
return response
|
||||
else: # If multiple songs added
|
||||
# If more than 10 songs, download and load the first 5 to start the play right away
|
||||
if len(songs) > 10:
|
||||
fiveFirstSongs = songs[0:5]
|
||||
songs = songs[5:]
|
||||
await self.__downloadSongsAndStore(fiveFirstSongs, playersManager)
|
||||
|
||||
# Trigger a task to download all songs and then store them in the playlist
|
||||
asyncio.create_task(self.__downloadSongsAndStore(songs, playersManager))
|
||||
asyncio.create_task(self.__downloadSongsInLots(songs, playersManager))
|
||||
|
||||
embed = self.embeds.SONGS_ADDED(len(songs))
|
||||
return HandlerResponse(self.ctx, embed)
|
||||
@ -95,7 +89,7 @@ class PlayHandler(AbstractHandler):
|
||||
embed = self.embeds.DOWNLOADING_ERROR()
|
||||
return HandlerResponse(self.ctx, embed, error)
|
||||
except Exception as error:
|
||||
print(f'ERROR IN PLAYHANDLER -> {traceback.format_exc()}', {type(error)})
|
||||
print(f'[ERROR IN PLAYHANDLER] -> {traceback.format_exc()}', {type(error)})
|
||||
if isinstance(error, VulkanError):
|
||||
embed = self.embeds.CUSTOM_ERROR(error)
|
||||
else:
|
||||
@ -104,24 +98,31 @@ class PlayHandler(AbstractHandler):
|
||||
|
||||
return HandlerResponse(self.ctx, embed, error)
|
||||
|
||||
async def __downloadSongsAndStore(self, songs: List[Song], playersManager: AbstractPlayersManager) -> None:
|
||||
async def __downloadSongsInLots(self, songs: List[Song], playersManager: AbstractPlayersManager) -> None:
|
||||
"""
|
||||
To avoid having a lot of tasks delaying the song playback we will lock the maximum songs downloading at a time
|
||||
"""
|
||||
playlist = playersManager.getPlayerPlaylist(self.guild)
|
||||
playCommand = VCommands(VCommandsType.PLAY, None)
|
||||
tooManySongs = len(songs) > 100
|
||||
maxDownloads = self.config.MAX_DOWNLOAD_SONGS_AT_A_TIME
|
||||
|
||||
# Trigger a task for each song to be downloaded
|
||||
while len(songs) > 0:
|
||||
# Verify how many songs will be downloaded in this lot and extract from the songs list
|
||||
songsQuant = min(maxDownloads, len(songs))
|
||||
# Get the first quantInLot songs
|
||||
songsInLot = songs[:songsQuant]
|
||||
# Remove the first quantInLot songs from the songs
|
||||
songs = songs[songsQuant:]
|
||||
|
||||
# Create task to download the songs in the lot
|
||||
tasks: List[asyncio.Task] = []
|
||||
for index, song in enumerate(songs):
|
||||
# If there is a lot of songs being downloaded, force a sleep to try resolve the Http Error 429 "To Many Requests"
|
||||
# Trying to fix the issue https://github.com/RafaelSolVargas/Vulkan/issues/32
|
||||
if tooManySongs and index % 3 == 0:
|
||||
await asyncio.sleep(0.5)
|
||||
for index, song in enumerate(songsInLot):
|
||||
task = asyncio.create_task(self.__down.download_song(song))
|
||||
tasks.append(task)
|
||||
|
||||
for index, task in enumerate(tasks):
|
||||
for index, task, in enumerate(tasks):
|
||||
await task
|
||||
song = songs[index]
|
||||
song = songsInLot[index]
|
||||
if not song.problematic: # If downloaded add to the playlist and send play command
|
||||
playerLock = playersManager.getPlayerLock(self.guild)
|
||||
acquired = playerLock.acquire(timeout=self.config.ACQUIRE_LOCK_TIMEOUT)
|
||||
|
||||
@ -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():
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user