diff --git a/.classpath b/.classpath index 571ccd1..6cf8f5a 100644 --- a/.classpath +++ b/.classpath @@ -24,8 +24,8 @@ - - - + + + diff --git a/src/commands/music/CommandMusicMain.java b/src/commands/music/CommandMusicMain.java index 7176498..6581271 100644 --- a/src/commands/music/CommandMusicMain.java +++ b/src/commands/music/CommandMusicMain.java @@ -2,6 +2,7 @@ package commands.music; import core.Command; import core.LocStrings; +import core.SubCommandFormattable; import core.SubCommandFramework; import dataStructures.*; @@ -9,11 +10,14 @@ public class CommandMusicMain extends Command { SubCommandFramework framework = new SubCommandFramework(); public CommandMusicMain(KittyRole level, KittyRating rating) - { + { super(level, rating); framework.addCommand("join", new SubCommandJoin(KittyRole.General, KittyRating.Safe)); framework.addCommand("leave", new SubCommandLeave(KittyRole.General, KittyRating.Safe)); framework.addCommand("add", new SubCommandAddTrack(KittyRole.General, KittyRating.Safe)); + framework.addCommand("skip", new SubCommandSkip(KittyRole.General, KittyRating.Safe)); + framework.addCommand("stop", new SubCommandStop(KittyRole.General, KittyRating.Safe)); + framework.addCommand("playlist", new SubCommandPlaylist(KittyRole.General, KittyRating.Safe)); } @Override @@ -22,6 +26,8 @@ public class CommandMusicMain extends Command @Override public void onRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res) { - framework.run(guild, channel, user, input).Call(res); + SubCommandFormattable scf = framework.run(guild, channel, user, input); + System.out.println("SCF: " + scf.resString); + scf.Call(res); } } \ No newline at end of file diff --git a/src/commands/music/SubCommandAddTrack.java b/src/commands/music/SubCommandAddTrack.java index 575bd00..39febae 100644 --- a/src/commands/music/SubCommandAddTrack.java +++ b/src/commands/music/SubCommandAddTrack.java @@ -9,7 +9,6 @@ import dataStructures.KittyRole; import dataStructures.KittyUser; import dataStructures.UserInput; import network.NetworkYoutube; -import utils.audio.AudioController; public class SubCommandAddTrack extends SubCommand { @@ -17,19 +16,17 @@ public class SubCommandAddTrack extends SubCommand public SubCommandAddTrack(KittyRole level, KittyRating rating) { super(level, rating); } NetworkYoutube YT = new NetworkYoutube(); - AudioController ac = new AudioController(); @Override public SubCommandFormattable OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input) { //Get track from Youtube - String video = YT.getYT(input.args); - ac.loadAndPlay(video, guild); - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - return new SubCommandFormattable(ac.response); + String video = input.args; + if(!(video.startsWith("https://www.youtube") || video.startsWith("https://www.youtu.be"))) + { + video = YT.getYT(video); + } + guild.audio.playVideo(video); + return new SubCommandFormattable(video); } } \ No newline at end of file diff --git a/src/commands/music/SubCommandPlaylist.java b/src/commands/music/SubCommandPlaylist.java new file mode 100644 index 0000000..4ab8c88 --- /dev/null +++ b/src/commands/music/SubCommandPlaylist.java @@ -0,0 +1,24 @@ +package commands.music; + +import java.util.ArrayList; + +import com.sedmelluq.discord.lavaplayer.track.AudioTrack; + +import core.SubCommand; +import core.SubCommandFormattable; +import dataStructures.*; + +public class SubCommandPlaylist extends SubCommand +{ + + public SubCommandPlaylist(KittyRole roleLevel, KittyRating contentRating) {super(roleLevel, contentRating); } + + @Override + public SubCommandFormattable OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input) + { +// ArrayList playlist = guild.audio.getPlaylist(); + String stringPlaylist = guild.audio.getPlaylist(guild.audio.player); + return new SubCommandFormattable(stringPlaylist); + } + +} diff --git a/src/commands/music/SubCommandSkip.java b/src/commands/music/SubCommandSkip.java new file mode 100644 index 0000000..b57ee52 --- /dev/null +++ b/src/commands/music/SubCommandSkip.java @@ -0,0 +1,23 @@ +package commands.music; + +import core.SubCommand; +import core.SubCommandFormattable; +import dataStructures.KittyChannel; +import dataStructures.KittyGuild; +import dataStructures.KittyRating; +import dataStructures.KittyRole; +import dataStructures.KittyUser; +import dataStructures.UserInput; + +public class SubCommandSkip extends SubCommand +{ + public SubCommandSkip(KittyRole level, KittyRating rating) { super(level, rating); } + { + + } + @Override + public SubCommandFormattable OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input) + { + return new SubCommandFormattable(guild.audio.skipVideo(guild.audio.player)); + } +} diff --git a/src/commands/music/SubCommandStop.java b/src/commands/music/SubCommandStop.java new file mode 100644 index 0000000..2aacffd --- /dev/null +++ b/src/commands/music/SubCommandStop.java @@ -0,0 +1,24 @@ +package commands.music; + +import core.SubCommand; +import core.SubCommandFormattable; +import dataStructures.KittyChannel; +import dataStructures.KittyGuild; +import dataStructures.KittyRating; +import dataStructures.KittyRole; +import dataStructures.KittyUser; +import dataStructures.UserInput; + +public class SubCommandStop extends SubCommand +{ + + public SubCommandStop(KittyRole roleLevel, KittyRating contentRating) {super(roleLevel, contentRating);} + + @Override + public SubCommandFormattable OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input) + { + + return new SubCommandFormattable(guild.audio.stopPlayer(guild.audio.player)); + } + +} diff --git a/src/core/ObjectBuilderFactory.java b/src/core/ObjectBuilderFactory.java index 0b8dced..fd5d592 100644 --- a/src/core/ObjectBuilderFactory.java +++ b/src/core/ObjectBuilderFactory.java @@ -7,16 +7,29 @@ import java.util.concurrent.Semaphore; import javax.security.auth.login.LoginException; +import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; +import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager; +import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers; + //import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; //import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager; import core.lua.PluginManager; -import dataStructures.*; +import dataStructures.KittyChannel; +import dataStructures.KittyCore; +import dataStructures.KittyGuild; +import dataStructures.KittyRating; +import dataStructures.KittyRole; +import dataStructures.KittyUser; import main.Main; import net.dv8tion.jda.core.AccountType; import net.dv8tion.jda.core.JDA; import net.dv8tion.jda.core.JDABuilder; -import net.dv8tion.jda.core.entities.*; +import net.dv8tion.jda.core.entities.Emote; +import net.dv8tion.jda.core.entities.Game; +import net.dv8tion.jda.core.entities.Guild; +import net.dv8tion.jda.core.entities.Member; +import net.dv8tion.jda.core.entities.User; import net.dv8tion.jda.core.events.message.guild.GuildMessageReceivedEvent; import offline.Ref; import utils.AdminControl; @@ -56,7 +69,7 @@ public class ObjectBuilderFactory private static CommandManager commandManager; //Audio track manager -// private static AudioPlayerManager audioPlayer = new DefaultAudioPlayerManager(); + private static AudioPlayerManager playerManager = new DefaultAudioPlayerManager(); // Localization classes - these are singletons, but should be initialized before almost all other // things so their inclusion in the factory is to ensure they're started at the correct time. @@ -91,6 +104,9 @@ public class ObjectBuilderFactory database = null; stats = null; + AudioSourceManagers.registerRemoteSources(playerManager); + AudioSourceManagers.registerLocalSource(playerManager); + // Start by reading from things that are external. Because // we require these things to be resolved before the rest of the application, // we place them here. @@ -148,7 +164,7 @@ public class ObjectBuilderFactory else { // Construct a new guild with defaults - guild = new KittyGuild(uid, new AdminControl(event.getGuild()), emotesString, new AudioUtils(event.getGuild())); + guild = new KittyGuild(uid, new AdminControl(event.getGuild()), emotesString, new AudioUtils(event.getGuild(), playerManager)); DatabaseManager.instance.globalRegister(guild); guildCache.put(uid, guild); } diff --git a/src/dataStructures/KittyGuild.java b/src/dataStructures/KittyGuild.java index d989348..ceb056d 100644 --- a/src/dataStructures/KittyGuild.java +++ b/src/dataStructures/KittyGuild.java @@ -2,15 +2,10 @@ package dataStructures; import java.util.ArrayList; -import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; -import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager; -import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers; - import core.DatabaseManager; import core.DatabaseTrackedObject; import utils.AdminControl; import utils.audio.AudioUtils; -import utils.audio.GuildMusicManager; // Context for a given guild for kittybot. Primarily designed to hold guild-specific settings. public class KittyGuild extends DatabaseTrackedObject @@ -28,8 +23,6 @@ public class KittyGuild extends DatabaseTrackedObject public ArrayList choices = new ArrayList(); public AdminControl control; public AudioUtils audio; - public GuildMusicManager musicManager; - public AudioPlayerManager playerManager = new DefaultAudioPlayerManager(); public ArrayList raffleUsersUnchosen = new ArrayList(); public ArrayList raffleUsersChosen = new ArrayList(); @@ -51,14 +44,12 @@ public class KittyGuild extends DatabaseTrackedObject // Default content for a guild public KittyGuild(String uniqueID, AdminControl adminControl, ArrayList emoji, AudioUtils audio) { + super(uniqueID); - AudioSourceManagers.registerRemoteSources(playerManager); - AudioSourceManagers.registerLocalSource(playerManager); this.uniqueID = uniqueID; roleList = new KittyTrackedVector(roleListName, uniqueID); beans = new KittyTrackedLong(beansName, uniqueID); registerTrackedObjects(); - musicManager = new GuildMusicManager(playerManager, this); control = adminControl; this.contentRating = KittyRating.Safe; diff --git a/src/main/Main.java b/src/main/Main.java index 5f4beaa..23e3b7d 100644 --- a/src/main/Main.java +++ b/src/main/Main.java @@ -1,12 +1,20 @@ package main; +import java.util.HashMap; import java.util.List; +import java.util.Map; import javax.security.auth.login.LoginException; +import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler; +import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager; import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers; +import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; +import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist; +import com.sedmelluq.discord.lavaplayer.track.AudioTrack; +import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame; import core.CharacterManager; import core.CommandEnabler; @@ -22,8 +30,17 @@ import dataStructures.KittyGuild; import dataStructures.KittyUser; import dataStructures.Response; import dataStructures.UserInput; +import net.dv8tion.jda.core.AccountType; +import net.dv8tion.jda.core.JDA; +import net.dv8tion.jda.core.JDABuilder; +import net.dv8tion.jda.core.audio.AudioSendHandler; +import net.dv8tion.jda.core.entities.Guild; +import net.dv8tion.jda.core.entities.TextChannel; +import net.dv8tion.jda.core.entities.VoiceChannel; import net.dv8tion.jda.core.events.message.guild.GuildMessageReceivedEvent; import net.dv8tion.jda.core.hooks.ListenerAdapter; +import net.dv8tion.jda.core.managers.AudioManager; +import offline.Ref; import utils.GlobalLog; // http://www.slf4j.org/ - this JDA logging tool has been disabled by specifying NOP implementation. diff --git a/src/utils/audio/AudioController.java b/src/utils/audio/AudioController.java deleted file mode 100644 index 0754180..0000000 --- a/src/utils/audio/AudioController.java +++ /dev/null @@ -1,81 +0,0 @@ -package utils.audio; - -import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler; -import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; -import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager; -import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers; -import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; -import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist; -import com.sedmelluq.discord.lavaplayer.track.AudioTrack; - -import dataStructures.KittyGuild; - -public class AudioController -{ - public String response; - private final AudioPlayerManager playerManager; - - - public AudioController() - { - playerManager = new DefaultAudioPlayerManager(); - AudioSourceManagers.registerRemoteSources(playerManager); - AudioSourceManagers.registerLocalSource(playerManager); - } - - public void loadAndPlay(final String trackUrl, KittyGuild guild) - { - GuildMusicManager musicManager = guild.musicManager; - - playerManager.loadItemOrdered(musicManager, trackUrl, new AudioLoadResultHandler() - { - @Override - public void trackLoaded(AudioTrack track) - { - response = "Adding to queue " + track.getInfo().title; - play(musicManager, track); - } - - @Override - public void playlistLoaded(AudioPlaylist playlist) - { - AudioTrack firstTrack = playlist.getSelectedTrack(); - - if (firstTrack == null) - { - firstTrack = playlist.getTracks().get(0); - } - - response = "Adding to queue " + firstTrack.getInfo().title + " (first track of playlist " + playlist.getName() + ")"; - - play(musicManager, firstTrack); - } - - @Override - public void noMatches() - { - response = "Nothing found by " + trackUrl; - } - - @Override - public void loadFailed(FriendlyException exception) - { - System.out.println(exception.getCause()); - } - }); - } - - private void play(GuildMusicManager musicManager, AudioTrack track) - { - System.out.println(musicManager.guildName); - musicManager.scheduler.queue(track); - } - - private void skipTrack(KittyGuild guild) - { - GuildMusicManager musicManager = guild.musicManager; - musicManager.scheduler.nextTrack(); - - response = "Skipped to next track."; - } -} diff --git a/src/utils/audio/AudioPlayerSendHandler.java b/src/utils/audio/AudioPlayerSendHandler.java deleted file mode 100644 index db51dc0..0000000 --- a/src/utils/audio/AudioPlayerSendHandler.java +++ /dev/null @@ -1,41 +0,0 @@ -package utils.audio; - -import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; -import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame; - -import net.dv8tion.jda.core.audio.AudioSendHandler; - -/** - * This is a wrapper around AudioPlayer which makes it behave as an AudioSendHandler for JDA. As JDA calls canProvide - * before every call to provide20MsAudio(), we pull the frame in canProvide() and use the frame we already pulled in - * provide20MsAudio(). - */ -public class AudioPlayerSendHandler implements AudioSendHandler -{ - private final AudioPlayer audioPlayer; - private AudioFrame lastFrame; - - public AudioPlayerSendHandler(AudioPlayer audioPlayer) - { - this.audioPlayer = audioPlayer; - } - - @Override - public boolean canProvide() - { - lastFrame = audioPlayer.provide(); - return lastFrame != null; - } - - @Override - public byte[] provide20MsAudio() - { - return lastFrame.getData(); - } - - @Override - public boolean isOpus() - { - return true; - } -} \ No newline at end of file diff --git a/src/utils/audio/AudioUtils.java b/src/utils/audio/AudioUtils.java index e570f33..c320651 100644 --- a/src/utils/audio/AudioUtils.java +++ b/src/utils/audio/AudioUtils.java @@ -1,4 +1,17 @@ package utils.audio; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.Queue; + +import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler; +import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; +import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; +import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; +import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist; +import com.sedmelluq.discord.lavaplayer.track.AudioTrack; +import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame; + +import net.dv8tion.jda.core.audio.AudioSendHandler; import net.dv8tion.jda.core.entities.Guild; import net.dv8tion.jda.core.entities.VoiceChannel; import net.dv8tion.jda.core.managers.AudioManager; @@ -6,13 +19,19 @@ import net.dv8tion.jda.core.managers.AudioManager; public class AudioUtils { private Guild guild; - public AudioManager audio; + public AudioManager am; + public final AudioPlayer player; + public final AudioPlayerManager playerManager; + private Queue playlist = new LinkedList<>(); - public AudioUtils(Guild guild) + public AudioUtils(Guild guild, AudioPlayerManager playerManager) { + player = playerManager.createPlayer(); + this.playerManager = playerManager; this.guild = guild; - audio = guild.getAudioManager(); + am = guild.getAudioManager(); + } public boolean joinChannel(String channelName) @@ -21,7 +40,7 @@ public class AudioUtils channel = guild.getVoiceChannelsByName(channelName, true).get(0); if(channel != null) { - audio.openAudioConnection(channel); + am.openAudioConnection(channel); return true; } return false; @@ -29,11 +48,97 @@ public class AudioUtils public boolean leaveChannel() { - if(audio.isConnected()) + if(am.isConnected()) { - audio.closeAudioConnection(); + am.closeAudioConnection(); return true; } return false; } + + + public class AudioPlayerSendHandler implements AudioSendHandler + { + private final AudioPlayer audioPlayer; + private AudioFrame lastFrame; + + public AudioPlayerSendHandler(AudioPlayer audioPlayer) + { + this.audioPlayer = audioPlayer; + } + + @Override + public boolean canProvide() + { + lastFrame = audioPlayer.provide(); + return lastFrame != null; + } + + @Override public byte[] provide20MsAudio() + { + return lastFrame.getData(); + } + + @Override public boolean isOpus() + { + return true; + } + } + + public class SmallAudio implements AudioLoadResultHandler + { + private AudioPlayer player; + + private SmallAudio(AudioPlayer player) + { + this.player = player; + } + + @Override public void loadFailed(FriendlyException arg0) { } + @Override public void noMatches() { } + @Override public void playlistLoaded(AudioPlaylist arg0) { } + + @Override + public void trackLoaded(AudioTrack track) + { + synchronized(playlist) + { + playlist.add(track); + } + do + { + if(player.getPlayingTrack() == null) + { + player.startTrack(playlist.poll(), false); + } + } + while(!playlist.isEmpty()); + } + } + + + public String playVideo(String videoURL) + { + guild.getAudioManager().setSendingHandler(new AudioPlayerSendHandler(player)); + playerManager.loadItemOrdered(player, videoURL, new SmallAudio(player)); + + return null; + } + + public String skipVideo(AudioPlayer player) + { + return null; + } + + public String stopPlayer(AudioPlayer player) + { + playlist.clear(); + player.stopTrack(); + return "Stopped"; + } + + public String getPlaylist(AudioPlayer player) + { + return player.getPlayingTrack().getInfo().title; + } } diff --git a/src/utils/audio/GuildMusicManager.java b/src/utils/audio/GuildMusicManager.java deleted file mode 100644 index b499517..0000000 --- a/src/utils/audio/GuildMusicManager.java +++ /dev/null @@ -1,40 +0,0 @@ -package utils.audio; - -import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; -import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; - -import dataStructures.KittyGuild; - -/** - * Holder for both the player and a track scheduler for one guild. - */ -public class GuildMusicManager { - /** - * Audio player for the guild. - */ - public final AudioPlayer player; - /** - * Track scheduler for the player. - */ - public final TrackScheduler scheduler; - public final String guildName; - - /** - * Creates a player and a track scheduler. - * @param manager Audio player manager to use for creating the player. - */ - public GuildMusicManager(AudioPlayerManager manager, KittyGuild guild) - { - player = manager.createPlayer(); - scheduler = new TrackScheduler(player); - player.addListener(scheduler); - guildName = guild.uniqueID; - } - - /** - * @return Wrapper around AudioPlayer to use it as an AudioSendHandler. - */ - public AudioPlayerSendHandler getSendHandler() { - return new AudioPlayerSendHandler(player); - } -} \ No newline at end of file diff --git a/src/utils/audio/TrackScheduler.java b/src/utils/audio/TrackScheduler.java deleted file mode 100644 index 381510f..0000000 --- a/src/utils/audio/TrackScheduler.java +++ /dev/null @@ -1,62 +0,0 @@ -package utils.audio; - -import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; -import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter; -import com.sedmelluq.discord.lavaplayer.track.AudioTrack; -import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason; - -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; - -/** - * This class schedules tracks for the audio player. It contains the queue of tracks. - */ -public class TrackScheduler extends AudioEventAdapter { - private final AudioPlayer player; - private final BlockingQueue queue; - - /** - * @param player The audio player this scheduler uses - */ - public TrackScheduler(AudioPlayer player) - { - this.player = player; - this.queue = new LinkedBlockingQueue<>(); - } - - /** - * Add the next track to queue or play right away if nothing is in the queue. - * - * @param track The track to play or add to queue. - */ - public void queue(AudioTrack track) - { - // Calling startTrack with the noInterrupt set to true will start the track only if nothing is currently playing. If - // something is playing, it returns false and does nothing. In that case the player was already playing so this - // track goes to the queue instead. - if (!player.startTrack(track, true)) - { - queue.offer(track); - } - } - - /** - * Start the next track, stopping the current one if it is playing. - */ - public void nextTrack() - { - // Start the next track, regardless of if something is already playing or not. In case queue was empty, we are - // giving null to startTrack, which is a valid argument and will simply stop the player. - player.startTrack(queue.poll(), false); - } - - @Override - public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason) - { - // Only start the next track if the end reason is suitable for it (FINISHED or LOAD_FAILED) - if (endReason.mayStartNext) - { - nextTrack(); - } - } -} \ No newline at end of file