diff --git a/.gitignore b/.gitignore index 6b28229..f141e2b 100644 --- a/.gitignore +++ b/.gitignore @@ -24,9 +24,20 @@ target/ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* -# Downloaded audio +# Downloaded audio/info *.wav *.wav.tmp +*.info.json # Syncthing being weird -.syncthing.* \ No newline at end of file +.syncthing.* + +# Netbeans +**/nbproject/private/ +**/nbproject/Makefile-*.mk +**/nbproject/Package-*.bash +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ diff --git a/src/main/java/moe/nekojimi/chords/Main.java b/src/main/java/moe/nekojimi/chords/Main.java index da95c82..97682fd 100644 --- a/src/main/java/moe/nekojimi/chords/Main.java +++ b/src/main/java/moe/nekojimi/chords/Main.java @@ -5,26 +5,21 @@ */ package moe.nekojimi.chords; -import java.io.Closeable; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; -import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.*; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.security.auth.login.LoginException; -import javax.sound.sampled.*; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDABuilder; -import net.dv8tion.jda.api.audio.AudioSendHandler; import net.dv8tion.jda.api.entities.*; import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; @@ -81,16 +76,32 @@ public class Main extends ListenerAdapter if (author.isBot()) return; - if (content.startsWith("!join ")) + try { - String arg = content.substring("!join ".length()); - onJoinCommand(event, guild, arg); - } else if (content.equals("!join")) - onJoinCommand(event); - else if (content.startsWith("!play ")) + + String arg = ""; + if (content.contains(" ")) + arg = content.split(" ", 2)[1]; + + if (content.startsWith("!join ")) + onJoinCommand(event, guild, arg); + else if (content.equals("!join")) + onJoinCommand(event); + else if (content.startsWith("!play ")) + onPlayCommand(event, guild, arg); + else if (content.startsWith("!queue")) + onQueueCommand(event, guild); + else if (content.startsWith("!remove ")) + onRemoveCommand(event, guild, arg); + else if (content.startsWith("!restart")) + onRestartCommand(event); + else if (content.startsWith("!skip")) + onSkipCommand(event); + else if (content.startsWith("!")) + onHelpCommand(event); + } catch (Exception ex) { - String arg = content.substring("!join ".length()); - onPlayCommand(event, guild, arg); + event.getChannel().sendMessage("Error in command! " + ex.getMessage()).queue(); } } @@ -129,7 +140,6 @@ public class Main extends ListenerAdapter boolean isNumber = arg.matches("\\d+"); // This is a regular expression that ensures the input consists of digits VoiceChannel channel = null; if (isNumber) // The input is an id? - channel = guild.getVoiceChannelById(arg); if (channel == null) // Then the input must be a name? { @@ -158,6 +168,8 @@ public class Main extends ListenerAdapter if (ok) { musicHandler.addSong(song); + if (musicHandler.isPlaying()) + musicHandler.setPlaying(true); event.getChannel().sendMessage("Downloaded and added to queue!").queue(); } @@ -171,21 +183,85 @@ public class Main extends ListenerAdapter } } + private void onRestartCommand(GuildMessageReceivedEvent event) + { + // TODO: this needs to clear the current data queue + boolean ok = musicHandler.restartSong(); + if (ok) + event.getChannel().sendMessage("Restarted current song!").queue(); + else + event.getChannel().sendMessage("Cannot restart!").queue(); + } + + private void onSkipCommand(GuildMessageReceivedEvent event) + { + boolean ok = musicHandler.nextSong(true); + if (ok) + event.getChannel().sendMessage("Skipped to next song!").queue(); + else + event.getChannel().sendMessage("There's no more songs!").queue(); + } + + private void onQueueCommand(GuildMessageReceivedEvent event, Guild guild) + { + Queue songQueue = musicHandler.getSongQueue(); + String message = new String(); + int i = 1; + message += "Now playing: " + musicHandler.getCurrentSong() + "\n"; + message += "---\n"; + for (Song song : songQueue) + message += (i) + ": " + song + "\n"; + event.getChannel().sendMessage(message).queue(); + } + + private void onRemoveCommand(GuildMessageReceivedEvent event, Guild guild, String arg) + { + try + { + int i = Integer.parseInt(arg); + boolean removed = musicHandler.removeSong(i - 1); + final int size = musicHandler.getSongQueue().size(); + if (removed) + event.getChannel().sendMessage("Song removed.").queue(); + else if (size > 1) + event.getChannel().sendMessage("That's not a number between 1 and " + size + "!").queue(); + else if (size == 1) + event.getChannel().sendMessage("There's only one song to remove and that's not one of them!").queue(); + + } catch (NumberFormatException ex) + { + event.getChannel().sendMessage(arg + " isn't a number!").queue(); + } + } + + private void onHelpCommand(GuildMessageReceivedEvent event) + { + String help = "Commands available:\n" + + "!join - Joins a voice channel\n" + + "!play - Downloads the track at that URL and adds it to the queue.\n" + + "!queue - Show the songs currently playing and the current queue.\n" + + "!remove - Remove the song at position from the queue.\n" + + "!skip - Skip the current song and play the next one.\n" + + "!restart - Try playing the current song again in case it goes wrong.\n"; + event.getChannel().sendMessage(help).queue(); + } + private boolean downloadSong(Song song) throws IOException, RuntimeException, InterruptedException { - String cmd = "/usr/bin/youtube-dl -x --audio-format=wav " + song.getUrl().toString(); + String cmd = "/usr/bin/youtube-dl -x --audio-format=wav --no-playlist --write-info-json " + song.getUrl().toString(); System.out.println("Running command: " + cmd); // Process exec = Runtime.getRuntime().exec().split(" ")); Process exec = new ProcessBuilder(cmd.split(" ")).redirectOutput(ProcessBuilder.Redirect.PIPE).start(); - exec.waitFor(10000, TimeUnit.MILLISECONDS); + exec.waitFor(10, TimeUnit.SECONDS); InputStream in = exec.getInputStream(); String output = new String(in.readAllBytes(), Charset.defaultCharset()); System.out.println(output); - if (exec.exitValue() != 0) - throw new RuntimeException("youtube-dl failed with error " + exec.exitValue()); Matcher matcher = Pattern.compile("Destination: (.*\\.wav)").matcher(output); - matcher.find(); - song.setLocation(new File(matcher.group(0))); + if (matcher.find()) + song.setLocation(new File(matcher.group(1))); + else + if (exec.exitValue() != 0) + throw new RuntimeException("youtube-dl failed with error " + exec.exitValue()); return true; // String destination = matcher.group(1); // return destination; diff --git a/src/main/java/moe/nekojimi/chords/MusicHandler.java b/src/main/java/moe/nekojimi/chords/MusicHandler.java index 0a5e2e7..9287a73 100644 --- a/src/main/java/moe/nekojimi/chords/MusicHandler.java +++ b/src/main/java/moe/nekojimi/chords/MusicHandler.java @@ -6,9 +6,9 @@ package moe.nekojimi.chords; import java.io.Closeable; -import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.logging.Level; @@ -26,12 +26,15 @@ import net.dv8tion.jda.api.audio.AudioSendHandler; public class MusicHandler implements AudioSendHandler, Closeable { - private final Queue songQueue = new ConcurrentLinkedQueue<>(); + private final LinkedList songQueue = new LinkedList<>(); private Song currentSong; private AudioInputStream din = null; private final Queue queue = new ConcurrentLinkedQueue<>(); + private boolean playing = true; private int byteCount; + private boolean arrayErr = false; + public MusicHandler() { } @@ -40,12 +43,44 @@ public class MusicHandler implements AudioSendHandler, Closeable { System.out.println("Song added to queue: " + song.getLocation().getAbsolutePath()); songQueue.add(song); - if (!canProvide()) + if (!canProvide() && playing) nextSong(); } + public boolean removeSong(int i) + { + try + { + songQueue.remove(i); + return true; + } catch (ArrayIndexOutOfBoundsException ex) + { + return false; + } + } + + public boolean removeSong(Song song) + { + return songQueue.remove(song); + } + + public boolean restartSong() + { + songQueue.addFirst(currentSong); + currentSong = null; + return nextSong(true); + } + private boolean nextSong() { + return nextSong(false); + } + + public boolean nextSong(boolean immediate) + { + if (immediate) + queue.clear(); + AudioInputStream in = null; try { @@ -63,6 +98,7 @@ public class MusicHandler implements AudioSendHandler, Closeable if (currentSong == null) return false; System.out.println("Playing song " + currentSong.getLocation().getAbsolutePath()); + arrayErr = false; in = AudioSystem.getAudioInputStream(currentSong.getLocation()); AudioFormat decodedFormat = AudioSendHandler.INPUT_FORMAT; din = AudioSystem.getAudioInputStream(decodedFormat, in); @@ -81,11 +117,23 @@ public class MusicHandler implements AudioSendHandler, Closeable return false; } + public boolean isPlaying() + { + return playing; + } + + public void setPlaying(boolean playing) + { + if (!this.playing && playing) + nextSong(); + this.playing = playing; + } + @Override public boolean canProvide() { // If we have something in our buffer we can provide it to the send system - return !queue.isEmpty(); + return !queue.isEmpty() && playing; } @Override @@ -121,10 +169,29 @@ public class MusicHandler implements AudioSendHandler, Closeable { Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); return false; + } catch (ArrayIndexOutOfBoundsException ex) + { + if (!arrayErr) + arrayErr = true; + else + { + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); + return false; + } } return true; } + public Queue getSongQueue() + { + return songQueue; + } + + public Song getCurrentSong() + { + return currentSong; + } + @Override public boolean isOpus() { diff --git a/src/main/java/moe/nekojimi/chords/Song.java b/src/main/java/moe/nekojimi/chords/Song.java index 6971d4c..0608ccf 100644 --- a/src/main/java/moe/nekojimi/chords/Song.java +++ b/src/main/java/moe/nekojimi/chords/Song.java @@ -61,7 +61,17 @@ public class Song void delete() { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + if (location != null) + location.delete(); + location = null; } + @Override + public String toString() + { + if (title != null && !title.isEmpty()) + return title; + else + return url.toExternalForm(); + } } diff --git a/target/classes/moe/nekojimi/chords/Main$EchoHandler.class b/target/classes/moe/nekojimi/chords/Main$EchoHandler.class deleted file mode 100644 index bc0bf05..0000000 Binary files a/target/classes/moe/nekojimi/chords/Main$EchoHandler.class and /dev/null differ diff --git a/target/classes/moe/nekojimi/chords/Main$MusicHandler.class b/target/classes/moe/nekojimi/chords/Main$MusicHandler.class deleted file mode 100644 index 5d1cace..0000000 Binary files a/target/classes/moe/nekojimi/chords/Main$MusicHandler.class and /dev/null differ