Fix various problems.

crossfading
Nekojimi 3 years ago
parent 367f2b93de
commit 2f7bb13dc6
  1. 13
      .gitignore
  2. 108
      src/main/java/moe/nekojimi/chords/Main.java
  3. 75
      src/main/java/moe/nekojimi/chords/MusicHandler.java
  4. 12
      src/main/java/moe/nekojimi/chords/Song.java
  5. BIN
      target/classes/moe/nekojimi/chords/Main$EchoHandler.class
  6. BIN
      target/classes/moe/nekojimi/chords/Main$MusicHandler.class

13
.gitignore vendored

@ -24,9 +24,20 @@ target/
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid* hs_err_pid*
# Downloaded audio # Downloaded audio/info
*.wav *.wav
*.wav.tmp *.wav.tmp
*.info.json
# Syncthing being weird # Syncthing being weird
.syncthing.* .syncthing.*
# Netbeans
**/nbproject/private/
**/nbproject/Makefile-*.mk
**/nbproject/Package-*.bash
build/
nbbuild/
dist/
nbdist/
.nb-gradle/

@ -5,26 +5,21 @@
*/ */
package moe.nekojimi.chords; package moe.nekojimi.chords;
import java.io.Closeable;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.security.auth.login.LoginException; import javax.security.auth.login.LoginException;
import javax.sound.sampled.*;
import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder; import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.audio.AudioSendHandler;
import net.dv8tion.jda.api.entities.*; import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent; import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter; import net.dv8tion.jda.api.hooks.ListenerAdapter;
@ -81,16 +76,32 @@ public class Main extends ListenerAdapter
if (author.isBot()) if (author.isBot())
return; return;
if (content.startsWith("!join ")) try
{ {
String arg = content.substring("!join ".length());
String arg = "";
if (content.contains(" "))
arg = content.split(" ", 2)[1];
if (content.startsWith("!join "))
onJoinCommand(event, guild, arg); onJoinCommand(event, guild, arg);
} else if (content.equals("!join")) else if (content.equals("!join"))
onJoinCommand(event); onJoinCommand(event);
else if (content.startsWith("!play ")) else if (content.startsWith("!play "))
{
String arg = content.substring("!join ".length());
onPlayCommand(event, guild, arg); 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)
{
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 boolean isNumber = arg.matches("\\d+"); // This is a regular expression that ensures the input consists of digits
VoiceChannel channel = null; VoiceChannel channel = null;
if (isNumber) // The input is an id? if (isNumber) // The input is an id?
channel = guild.getVoiceChannelById(arg); channel = guild.getVoiceChannelById(arg);
if (channel == null) // Then the input must be a name? if (channel == null) // Then the input must be a name?
{ {
@ -158,6 +168,8 @@ public class Main extends ListenerAdapter
if (ok) if (ok)
{ {
musicHandler.addSong(song); musicHandler.addSong(song);
if (musicHandler.isPlaying())
musicHandler.setPlaying(true);
event.getChannel().sendMessage("Downloaded and added to queue!").queue(); 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<Song> 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 <Channel> - Joins a voice channel\n"
+ "!play <URL> - 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 <Index> - Remove the song at position <Index> 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 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); System.out.println("Running command: " + cmd);
// Process exec = Runtime.getRuntime().exec().split(" ")); // Process exec = Runtime.getRuntime().exec().split(" "));
Process exec = new ProcessBuilder(cmd.split(" ")).redirectOutput(ProcessBuilder.Redirect.PIPE).start(); 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(); InputStream in = exec.getInputStream();
String output = new String(in.readAllBytes(), Charset.defaultCharset()); String output = new String(in.readAllBytes(), Charset.defaultCharset());
System.out.println(output); System.out.println(output);
Matcher matcher = Pattern.compile("Destination: (.*\\.wav)").matcher(output);
if (matcher.find())
song.setLocation(new File(matcher.group(1)));
else
if (exec.exitValue() != 0) if (exec.exitValue() != 0)
throw new RuntimeException("youtube-dl failed with error " + exec.exitValue()); 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)));
return true; return true;
// String destination = matcher.group(1); // String destination = matcher.group(1);
// return destination; // return destination;

@ -6,9 +6,9 @@
package moe.nekojimi.chords; package moe.nekojimi.chords;
import java.io.Closeable; import java.io.Closeable;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level; import java.util.logging.Level;
@ -26,12 +26,15 @@ import net.dv8tion.jda.api.audio.AudioSendHandler;
public class MusicHandler implements AudioSendHandler, Closeable public class MusicHandler implements AudioSendHandler, Closeable
{ {
private final Queue<Song> songQueue = new ConcurrentLinkedQueue<>(); private final LinkedList<Song> songQueue = new LinkedList<>();
private Song currentSong; private Song currentSong;
private AudioInputStream din = null; private AudioInputStream din = null;
private final Queue<byte[]> queue = new ConcurrentLinkedQueue<>(); private final Queue<byte[]> queue = new ConcurrentLinkedQueue<>();
private boolean playing = true;
private int byteCount; private int byteCount;
private boolean arrayErr = false;
public MusicHandler() public MusicHandler()
{ {
} }
@ -40,12 +43,44 @@ public class MusicHandler implements AudioSendHandler, Closeable
{ {
System.out.println("Song added to queue: " + song.getLocation().getAbsolutePath()); System.out.println("Song added to queue: " + song.getLocation().getAbsolutePath());
songQueue.add(song); songQueue.add(song);
if (!canProvide()) if (!canProvide() && playing)
nextSong(); 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() private boolean nextSong()
{ {
return nextSong(false);
}
public boolean nextSong(boolean immediate)
{
if (immediate)
queue.clear();
AudioInputStream in = null; AudioInputStream in = null;
try try
{ {
@ -63,6 +98,7 @@ public class MusicHandler implements AudioSendHandler, Closeable
if (currentSong == null) if (currentSong == null)
return false; return false;
System.out.println("Playing song " + currentSong.getLocation().getAbsolutePath()); System.out.println("Playing song " + currentSong.getLocation().getAbsolutePath());
arrayErr = false;
in = AudioSystem.getAudioInputStream(currentSong.getLocation()); in = AudioSystem.getAudioInputStream(currentSong.getLocation());
AudioFormat decodedFormat = AudioSendHandler.INPUT_FORMAT; AudioFormat decodedFormat = AudioSendHandler.INPUT_FORMAT;
din = AudioSystem.getAudioInputStream(decodedFormat, in); din = AudioSystem.getAudioInputStream(decodedFormat, in);
@ -81,11 +117,23 @@ public class MusicHandler implements AudioSendHandler, Closeable
return false; return false;
} }
public boolean isPlaying()
{
return playing;
}
public void setPlaying(boolean playing)
{
if (!this.playing && playing)
nextSong();
this.playing = playing;
}
@Override @Override
public boolean canProvide() public boolean canProvide()
{ {
// If we have something in our buffer we can provide it to the send system // If we have something in our buffer we can provide it to the send system
return !queue.isEmpty(); return !queue.isEmpty() && playing;
} }
@Override @Override
@ -121,10 +169,29 @@ public class MusicHandler implements AudioSendHandler, Closeable
{ {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
return false; return false;
} catch (ArrayIndexOutOfBoundsException ex)
{
if (!arrayErr)
arrayErr = true;
else
{
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
return false;
}
} }
return true; return true;
} }
public Queue<Song> getSongQueue()
{
return songQueue;
}
public Song getCurrentSong()
{
return currentSong;
}
@Override @Override
public boolean isOpus() public boolean isOpus()
{ {

@ -61,7 +61,17 @@ public class Song
void delete() 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();
}
} }

Loading…
Cancel
Save