diff --git a/lib/LavaPlayerJar-0.0.1-jar-with-dependencies.jar b/lib/LavaPlayerJar-0.0.1-jar-with-dependencies.jar index b0e1843..3a4e0ea 100644 Binary files a/lib/LavaPlayerJar-0.0.1-jar-with-dependencies.jar and b/lib/LavaPlayerJar-0.0.1-jar-with-dependencies.jar differ diff --git a/src/commands/music/SubCommandAddTrack.java b/src/commands/music/SubCommandAddTrack.java index eae45ce..d8f7472 100644 --- a/src/commands/music/SubCommandAddTrack.java +++ b/src/commands/music/SubCommandAddTrack.java @@ -9,6 +9,7 @@ import dataStructures.KittyRole; import dataStructures.KittyUser; import dataStructures.UserInput; import network.NetworkYoutube; +import utils.audio.AudioController; public class SubCommandAddTrack extends SubCommand { @@ -16,12 +17,16 @@ 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); - String response = YT.getYT(input.args); - return new SubCommandFormattable(response); + System.out.println(ac.response); + return new SubCommandFormattable(video); } } \ No newline at end of file diff --git a/src/core/ObjectBuilderFactory.java b/src/core/ObjectBuilderFactory.java index 8ee3e06..0b8dced 100644 --- a/src/core/ObjectBuilderFactory.java +++ b/src/core/ObjectBuilderFactory.java @@ -20,10 +20,10 @@ import net.dv8tion.jda.core.entities.*; import net.dv8tion.jda.core.events.message.guild.GuildMessageReceivedEvent; import offline.Ref; import utils.AdminControl; -import utils.AudioUtils; //import utils.AudioUtils; import utils.GlobalLog; import utils.LogFilter; +import utils.audio.AudioUtils; // NOTE(wisp): Isolated factory to assist with storage and caching if needed. // This also minimizes the number of places JDA interacts with our codebase. @@ -366,7 +366,8 @@ public class ObjectBuilderFactory { lazyInit(); - kitty = new JDABuilder(AccountType.BOT).setToken(Ref.TestToken).buildBlocking(); + kitty = new JDABuilder(AccountType.BOT) + .setToken(Ref.TestToken).buildBlocking(); kitty.getPresence().setGame(Game.playing("with digital yarn")); kitty.addEventListener(new Main()); diff --git a/src/dataStructures/KittyGuild.java b/src/dataStructures/KittyGuild.java index 8f4c443..4369490 100644 --- a/src/dataStructures/KittyGuild.java +++ b/src/dataStructures/KittyGuild.java @@ -2,10 +2,15 @@ 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.AudioUtils; +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 @@ -23,6 +28,8 @@ 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(); @@ -45,10 +52,13 @@ public class KittyGuild extends DatabaseTrackedObject 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); control = adminControl; this.contentRating = KittyRating.Safe; diff --git a/src/main/Main.java b/src/main/Main.java index 369a666..5f4beaa 100644 --- a/src/main/Main.java +++ b/src/main/Main.java @@ -4,6 +4,10 @@ import java.util.List; 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 core.CharacterManager; import core.CommandEnabler; import core.CommandManager; @@ -20,7 +24,6 @@ import dataStructures.Response; import dataStructures.UserInput; import net.dv8tion.jda.core.events.message.guild.GuildMessageReceivedEvent; import net.dv8tion.jda.core.hooks.ListenerAdapter; -import utils.AudioController; import utils.GlobalLog; // http://www.slf4j.org/ - this JDA logging tool has been disabled by specifying NOP implementation. @@ -37,7 +40,6 @@ public class Main extends ListenerAdapter private static RPManager rpManager; private static PluginManager pluginManager; private static CharacterManager charManager; - private static AudioController audioControl; // Initialization and setup @@ -49,7 +51,6 @@ public class Main extends ListenerAdapter commandManager = ObjectBuilderFactory.constructCommandManager(CommandEnabler.instance); // TODO: Untangle this singleton stats = ObjectBuilderFactory.constructStats(commandManager); charManager = new CharacterManager(); - audioControl = new AudioController(); rpManager = ObjectBuilderFactory.constructRPManager(); pluginManager = ObjectBuilderFactory.constructPluginManager(); diff --git a/src/utils/AudioController.java b/src/utils/AudioController.java deleted file mode 100644 index 26080c6..0000000 --- a/src/utils/AudioController.java +++ /dev/null @@ -1,30 +0,0 @@ -package utils; - -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; - -public class AudioController -{ - public static AudioController instance; - AudioPlayerManager playerManager = new DefaultAudioPlayerManager(); - - public AudioController () - { - if(instance == null) - { - instance = this; - } - else - { - AudioSourceManagers.registerRemoteSources(playerManager); - return; - } - } - - public AudioPlayer createPlayer() - { - return playerManager.createPlayer(); - } -} \ No newline at end of file diff --git a/src/utils/audio/AudioController.java b/src/utils/audio/AudioController.java new file mode 100644 index 0000000..8c05bed --- /dev/null +++ b/src/utils/audio/AudioController.java @@ -0,0 +1,84 @@ +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 +{ + private final AudioPlayerManager playerManager; + public String response; + + public AudioController() + { + playerManager = new DefaultAudioPlayerManager(); + AudioSourceManagers.registerRemoteSources(playerManager); + AudioSourceManagers.registerLocalSource(playerManager); + } + + public void loadAndPlay(final String trackUrl, KittyGuild guild) + { + GuildMusicManager musicManager = guild.musicManager; + response = "HERE"; + + playerManager.loadItemOrdered(musicManager, trackUrl, new AudioLoadResultHandler() + { + @Override + public void trackLoaded(AudioTrack track) + { + response = "Adding to queue " + track.getInfo().title; + System.out.println("QUEUED"); + 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() + { + System.out.println("NOT QUEUED"); + response = "Nothing found by " + trackUrl; + } + + @Override + public void loadFailed(FriendlyException exception) + { + System.out.println("EXCEPTION"); + System.out.println(exception.getCause()); + } + }); + } + + private void play(GuildMusicManager musicManager, AudioTrack track) + { + + 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 new file mode 100644 index 0000000..db51dc0 --- /dev/null +++ b/src/utils/audio/AudioPlayerSendHandler.java @@ -0,0 +1,41 @@ +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/AudioUtils.java b/src/utils/audio/AudioUtils.java similarity index 77% rename from src/utils/AudioUtils.java rename to src/utils/audio/AudioUtils.java index 34d6a8a..e570f33 100644 --- a/src/utils/AudioUtils.java +++ b/src/utils/audio/AudioUtils.java @@ -1,6 +1,4 @@ -package utils; -import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; - +package utils.audio; import net.dv8tion.jda.core.entities.Guild; import net.dv8tion.jda.core.entities.VoiceChannel; import net.dv8tion.jda.core.managers.AudioManager; @@ -9,14 +7,12 @@ public class AudioUtils { private Guild guild; public AudioManager audio; - public final AudioPlayer player; public AudioUtils(Guild guild) { this.guild = guild; audio = guild.getAudioManager(); - player = AudioController.instance.createPlayer(); } public boolean joinChannel(String channelName) diff --git a/src/utils/audio/GuildMusicManager.java b/src/utils/audio/GuildMusicManager.java new file mode 100644 index 0000000..599f513 --- /dev/null +++ b/src/utils/audio/GuildMusicManager.java @@ -0,0 +1,36 @@ +package utils.audio; + +import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; +import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; + +/** + * 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; + + /** + * Creates a player and a track scheduler. + * @param manager Audio player manager to use for creating the player. + */ + public GuildMusicManager(AudioPlayerManager manager) + { + player = manager.createPlayer(); + scheduler = new TrackScheduler(player); + player.addListener(scheduler); + } + + /** + * @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 new file mode 100644 index 0000000..381510f --- /dev/null +++ b/src/utils/audio/TrackScheduler.java @@ -0,0 +1,62 @@ +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