package handiebot.lavaplayer; import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager; import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers; import handiebot.command.Commands; import handiebot.lavaplayer.playlist.Playlist; import handiebot.lavaplayer.playlist.UnloadedTrack; import handiebot.utils.MessageUtils; import handiebot.utils.Pastebin; import handiebot.view.BotLog; import sx.blah.discord.handle.obj.*; import sx.blah.discord.util.EmbedBuilder; import java.text.MessageFormat; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import static handiebot.HandieBot.log; import static handiebot.HandieBot.resourceBundle; import static handiebot.utils.MessageUtils.sendMessage; /** * @author Andrew Lalis * This class is a container for all the music related functions, and contains methods for easy playback and queue * management. * The goal is to abstract all functions to this layer, rather than have the bot interact directly with any schedulers. */ public class MusicPlayer { //Name for the message and voice channels dedicated to this bot. static String CHANNEL_NAME = "HandieBotMusic"; private final AudioPlayerManager playerManager; /* Mappings of music managers, channels and voice channels for each guild. */ private Map musicManagers; private Map chatChannels; private Map voiceChannels; public MusicPlayer(){ //Initialize player manager. this.playerManager = new DefaultAudioPlayerManager(); AudioSourceManagers.registerLocalSource(playerManager); AudioSourceManagers.registerRemoteSources(playerManager); //Initialize all maps. this.musicManagers = new HashMap<>(); this.chatChannels = new HashMap<>(); this.voiceChannels = new HashMap<>(); } public AudioPlayerManager getPlayerManager(){ return this.playerManager; } /** * Gets the music manager specific to a particular guild. * @param guild The guild to get the music manager for. * @return The music manager for a guild. */ public GuildMusicManager getMusicManager(IGuild guild){ if (!this.musicManagers.containsKey(guild)){ this.musicManagers.put(guild, new GuildMusicManager(this.playerManager, guild)); guild.getAudioManager().setAudioProvider(this.musicManagers.get(guild).getAudioProvider()); } return this.musicManagers.get(guild); } /** * Gets the chat channel specific to a particular guild. This channel is used send updates about playback and * responses to people's commands. If none exists, the bot will attempt to make a channel. * @param guild The guild to get the channel from. * @return A message channel on a particular guild that is specifically for music. */ public IChannel getChatChannel(IGuild guild){ if (!this.chatChannels.containsKey(guild)){ List channels = guild.getChannelsByName(CHANNEL_NAME.toLowerCase()); if (channels.isEmpty()){ log.log(BotLog.TYPE.MUSIC, guild, resourceBundle.getString("log.creatingChatChannel")); this.chatChannels.put(guild, guild.createChannel(CHANNEL_NAME.toLowerCase())); } else { this.chatChannels.put(guild, channels.get(0)); } } return this.chatChannels.get(guild); } /** * Gets the voice channel associated with a particular guild. This channel is used for audio playback. If none * exists, the bot will attempt to make a voice channel. * @param guild The guild to get the channel from. * @return The voice channel on a guild that is for this bot. */ public IVoiceChannel getVoiceChannel(IGuild guild){ if (!this.voiceChannels.containsKey(guild)){ List channels = guild.getVoiceChannelsByName(CHANNEL_NAME); if (channels.isEmpty()){ log.log(BotLog.TYPE.MUSIC, guild, resourceBundle.getString("log.newVoiceChannel")); this.voiceChannels.put(guild, guild.createVoiceChannel(CHANNEL_NAME)); } else { this.voiceChannels.put(guild, channels.get(0)); } } return this.voiceChannels.get(guild); } /** * Toggles the repeating of songs for a particular guild. * @param guild The guild to repeat for. */ public void toggleRepeat(IGuild guild){ setRepeat(guild, !getMusicManager(guild).scheduler.isRepeating()); } /** * Sets the repeating of songs for a particular guild. * @param guild The guild to set repeat for. * @param value True to repeat, false otherwise. */ public void setRepeat(IGuild guild, boolean value){ getMusicManager(guild).scheduler.setRepeat(value); String message = MessageFormat.format(resourceBundle.getString("player.setRepeat"), getMusicManager(guild).scheduler.isRepeating()); log.log(BotLog.TYPE.MUSIC, guild, message); sendMessage(":repeat: "+message, getChatChannel(guild)); } /** * Returns whether or not repeat is set for a guild. * @param guild The guild to check for. * @return True if repeating is enabled, false otherwise. */ public boolean isRepeating(IGuild guild){ return getMusicManager(guild).scheduler.isRepeating(); } /** * Toggles shuffling for a specific guild. * @param guild The guild to toggle shuffling for. */ public void toggleShuffle(IGuild guild){ setShuffle(guild, !getMusicManager(guild).scheduler.isShuffling()); } /** * Sets shuffling for a specific guild. * @param guild The guild to set shuffling for. * @param value The value to set. True for shuffling, false for linear play. */ public void setShuffle(IGuild guild, boolean value){ getMusicManager(guild).scheduler.setShuffle(value); String message = MessageFormat.format(resourceBundle.getString("player.setShuffle"), getMusicManager(guild).scheduler.isShuffling()); log.log(BotLog.TYPE.MUSIC, guild, message); sendMessage(":twisted_rightwards_arrows: "+message, getChatChannel(guild)); } /** * Returns whether or not shuffle is set for a guild. * @param guild The guild to check for. * @return True if shuffling is enabled, false otherwise. */ public boolean isShuffling(IGuild guild){ return getMusicManager(guild).scheduler.isShuffling(); } /** * Sends a formatted message to the guild about the first few items in a queue. */ public void showQueueList(IGuild guild, boolean showAll) { List tracks = getMusicManager(guild).scheduler.queueList(); if (tracks.size() == 0) { //noinspection ConstantConditions sendMessage(MessageFormat.format(resourceBundle.getString("player.queueEmpty"), Commands.get("play").getUsage()), getChatChannel(guild)); } else { if (tracks.size() > 10 && showAll) { String result = Pastebin.paste("Current queue for discord server: "+guild.getName()+".", getMusicManager(guild).scheduler.getActivePlaylist().toString()); if (result != null && result.startsWith("https://pastebin.com/")){ log.log(BotLog.TYPE.INFO, guild, MessageFormat.format(resourceBundle.getString("player.queueUploaded"), result)); //Only display the pastebin link for 10 minutes. IMessage message = sendMessage(MessageFormat.format(resourceBundle.getString("player.pastebinLink"), result), getChatChannel(guild)); MessageUtils.deleteMessageAfter(600000, message); } else { log.log(BotLog.TYPE.ERROR, guild, MessageFormat.format(resourceBundle.getString("player.pastebinError"), result)); } } else { EmbedBuilder builder = new EmbedBuilder(); builder.withColor(255, 0, 0); StringBuilder sb = new StringBuilder(); for (int i = 0; i < (tracks.size() <= 10 ? tracks.size() : 10); i++) { sb.append(i + 1).append(". [").append(tracks.get(i).getTitle()).append("]("); sb.append(tracks.get(i).getURL()).append(")"); sb.append(tracks.get(i).getFormattedDuration()).append('\n'); } builder.appendField(MessageFormat.format(resourceBundle.getString("player.queueHeader"), tracks.size() <= 10 ? tracks.size() : "the first 10", tracks.size() > 1 ? "s" : "", tracks.size()), sb.toString(), false); getChatChannel(guild).sendMessage(builder.build()); } } } /** * Adds a track to the queue and sends a message to the appropriate channel notifying users. * @param guild The guild to add the song to. * @param track The track to queue. * @param user the user who added the song. */ public void addToQueue(IGuild guild, UnloadedTrack track, IUser user){ IVoiceChannel voiceChannel = getVoiceChannel(guild); if (voiceChannel != null){ if (!voiceChannel.isConnected()) { voiceChannel.join(); } long timeUntilPlay = getMusicManager(guild).scheduler.getTimeUntilDone(); getMusicManager(guild).scheduler.queue(track); //Build message. StringBuilder sb = new StringBuilder(); if (timeUntilPlay > 0) { sb.append(MessageFormat.format(resourceBundle.getString("player.addedToQueue"), user.getName(), track.getTitle())); sb.append(String.format("\nTime until play: %d min, %02d sec", TimeUnit.MILLISECONDS.toMinutes(timeUntilPlay), TimeUnit.MILLISECONDS.toSeconds(timeUntilPlay) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(timeUntilPlay)) )); } if (sb.length() > 0) { sendMessage(sb.toString(), getChatChannel(guild)); } } } /** * If possible, try to begin playing from the track scheduler's queue. * @param guild The guild to play music on. */ public void playQueue(IGuild guild){ if (getMusicManager(guild).scheduler.getActivePlaylist().getTrackCount() == 0){ sendMessage(resourceBundle.getString("player.playQueueEmpty"), getChatChannel(guild)); return; } IVoiceChannel vc = this.getVoiceChannel(guild); if (!vc.isConnected()){ vc.join(); } getMusicManager(guild).scheduler.nextTrack(); } /** * Clears the queue for a specified guild. * @param guild The guild to clear the queue for. */ public void clearQueue(IGuild guild){ getMusicManager(guild).scheduler.clearQueue(); sendMessage(resourceBundle.getString("player.queueCleared"), getChatChannel(guild)); } /** * Skips the current track. * @param guild The guild to skip the track for. */ public void skipTrack(IGuild guild){ String message = resourceBundle.getString("player.skippingCurrent"); log.log(BotLog.TYPE.MUSIC, guild, message); sendMessage(":track_next: "+message, getChatChannel(guild)); getMusicManager(guild).scheduler.nextTrack(); } /** * Stops playback and disconnects from the voice channel, to cease music actions. * @param guild The guild to stop from. */ public void stop(IGuild guild){ getMusicManager(guild).scheduler.stop(); String message = resourceBundle.getString("player.musicStopped"); sendMessage(":stop_button: "+message, getChatChannel(guild)); log.log(BotLog.TYPE.MUSIC, guild, message); } /** * Returns a playlist of all songs either in the queue or being played now. * @param guild The guild to get songs from. * @return A list of songs in the form of a playlist. */ public Playlist getAllSongsInQueue(IGuild guild){ GuildMusicManager musicManager = getMusicManager(guild); Playlist p = new Playlist("Active Queue"); p.copy(musicManager.scheduler.getActivePlaylist()); UnloadedTrack track = musicManager.scheduler.getPlayingTrack(); if (track != null){ p.addTrack(track); } return p; } /** * Performs the same functions as stop, but with every guild. */ public void quitAll(){ this.musicManagers.forEach((guild, musicManager) -> musicManager.scheduler.stop()); this.playerManager.shutdown(); } }