diff --git a/src/main/java/moe/nekojimi/chords/MusicHandler.java b/src/main/java/moe/nekojimi/chords/MusicHandler.java index b0b6dc4..9931c6b 100644 --- a/src/main/java/moe/nekojimi/chords/MusicHandler.java +++ b/src/main/java/moe/nekojimi/chords/MusicHandler.java @@ -13,9 +13,6 @@ import java.util.Queue; import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; -import javax.sound.sampled.AudioFormat; -import javax.sound.sampled.AudioInputStream; -import javax.sound.sampled.AudioSystem; import javax.sound.sampled.UnsupportedAudioFileException; import net.dv8tion.jda.api.audio.AudioSendHandler; import org.apache.commons.io.input.buffer.CircularByteBuffer; @@ -27,11 +24,7 @@ import org.apache.commons.io.input.buffer.CircularByteBuffer; public class MusicHandler implements AudioSendHandler, Closeable, Consumer { - private static final int DESIRED_BUFFER_SIZE = 3840 * 500; - private final LinkedList songQueue = new LinkedList<>(); - private Song currentSong; - private AudioInputStream din = null; // private final Queue queue = new ConcurrentLinkedQueue<>(); private final CircularByteBuffer audioBuffer = new CircularByteBuffer(3840 * 1024); private boolean playing = true; @@ -39,6 +32,9 @@ public class MusicHandler implements AudioSendHandler, Closeable, Consumer private boolean arrayErr = false; + private Song currentSong; + private TrackPlayer player; + public MusicHandler() { } @@ -83,16 +79,13 @@ public class MusicHandler implements AudioSendHandler, Closeable, Consumer public boolean nextSong(boolean immediate) { if (immediate) + { + System.out.println("Immediate next - clearing buffer"); audioBuffer.clear(); + } - AudioInputStream in = null; try { - if (din != null) - { - din.close(); - din = null; - } if (currentSong != null) { if (!currentSong.isKept()) @@ -104,12 +97,9 @@ public class MusicHandler implements AudioSendHandler, Closeable, Consumer 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); byteCount = 3840; - fillBuffer(false); - System.out.println("Queue filled to " + audioBuffer.getCurrentNumberOfBytes()); + player = new TrackPlayer(currentSong); +// System.out.println("Queue filled to " + audioBuffer.getCurrentNumberOfBytes()); return true; } catch (UnsupportedAudioFileException | IOException ex) { @@ -135,70 +125,88 @@ public class MusicHandler implements AudioSendHandler, Closeable, Consumer @Override public boolean canProvide() { + return player != null && player.has(byteCount); // If we have something in our buffer we can provide it to the send system - return audioBuffer.getCurrentNumberOfBytes() > byteCount && playing; +// return audioBuffer.getCurrentNumberOfBytes() > byteCount && playing; } @Override public ByteBuffer provide20MsAudio() { - fillBuffer(true); - byte[] data = new byte[byteCount]; - audioBuffer.read(data, 0, data.length); -// byte[] data = queue.poll(); - return ByteBuffer.wrap(data); // Wrap this in a java.nio.ByteBuffer - } - - private void fillBuffer(boolean canSkip) - { - // use what we have in our buffer to send audio as PCM - while (audioBuffer.getCurrentNumberOfBytes() < DESIRED_BUFFER_SIZE) - if (!readData()) - if (!canSkip || !nextSong()) - break; - } - - private boolean readData() - { - if (din == null) - return false; - try + ByteBuffer ret = ByteBuffer.allocate(byteCount); + while (ret.position() < byteCount && player != null) { - // if (din.available() == 0) - // return false; - int bytesToRead = DESIRED_BUFFER_SIZE - audioBuffer.getCurrentNumberOfBytes(); - int space = audioBuffer.getSpace(); - if (din.available() > 0 && din.available() < bytesToRead) - bytesToRead = din.available(); - if (bytesToRead > space) - bytesToRead = space; - if (bytesToRead == 0) - return false; - byte[] bytes = new byte[bytesToRead]; - // byte[] bytes = din.readNBytes(bytesToRead); - int read = din.read(bytes); - System.out.println("Wanted: " + byteCount + " Space:" + space + " Available: " + din.available() + " To read: " + bytesToRead + " Read: " + read); - if (read < 0) - return false; -// queue.add(bytes); + System.out.println("Position: " + ret.position() + " Remaining: " + ret.remaining()); + ByteBuffer read = player.read(ret.remaining()); - audioBuffer.add(bytes, 0, read); - } catch (IOException ex) - { - Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); - return false; - } catch (ArrayIndexOutOfBoundsException ex) - { - if (!arrayErr) - arrayErr = true; - else + if (read != null) { - Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); - return false; + System.out.println("Read: " + read.remaining()); + ret.put(read); + } else if (!nextSong()) + { + System.out.println("Out of songs!"); + break; } } - return true; - } + return ret; +// fillBuffer(true); +// byte[] data = new byte[byteCount]; +// audioBuffer.read(data, 0, data.length); +//// byte[] data = queue.poll(); +// return ByteBuffer.wrap(data); // Wrap this in a java.nio.ByteBuffer + } + +// private void fillBuffer(boolean canSkip) +// { +// // use what we have in our buffer to send audio as PCM +// while (audioBuffer.getCurrentNumberOfBytes() < DESIRED_BUFFER_SIZE) +// if (!readData()) +// if (!canSkip || !nextSong()) +// break; +// } +// +// private boolean readData() +// { +// if (din == null) +// return false; +// try +// { +// // if (din.available() == 0) +// // return false; +// int bytesToRead = DESIRED_BUFFER_SIZE - audioBuffer.getCurrentNumberOfBytes(); +// int space = audioBuffer.getSpace(); +// if (din.available() > 0 && din.available() < bytesToRead) +// bytesToRead = din.available(); +// if (bytesToRead > space) +// bytesToRead = space; +// if (bytesToRead == 0) +// return false; +// byte[] bytes = new byte[bytesToRead]; +// // byte[] bytes = din.readNBytes(bytesToRead); +// int read = din.read(bytes); +//// System.out.println("Wanted: " + byteCount + " Space:" + space + " Available: " + din.available() + " To read: " + bytesToRead + " Read: " + read); +// if (read < 0) +// return false; +//// queue.add(bytes); +// +// audioBuffer.add(bytes, 0, read); +// } catch (IOException ex) +// { +// 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() { @@ -219,7 +227,6 @@ public class MusicHandler implements AudioSendHandler, Closeable, Consumer @Override public void close() throws IOException { - din.close(); } @Override @@ -227,6 +234,4 @@ public class MusicHandler implements AudioSendHandler, Closeable, Consumer { addSong(t); } - - } diff --git a/src/main/java/moe/nekojimi/chords/TrackPlayer.java b/src/main/java/moe/nekojimi/chords/TrackPlayer.java new file mode 100644 index 0000000..340efab --- /dev/null +++ b/src/main/java/moe/nekojimi/chords/TrackPlayer.java @@ -0,0 +1,138 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package moe.nekojimi.chords; + +import java.io.Closeable; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.UnsupportedAudioFileException; +import net.dv8tion.jda.api.audio.AudioSendHandler; +import org.apache.commons.io.input.buffer.CircularByteBuffer; + +/** + * + * @author jimj316 + */ +public class TrackPlayer implements Closeable +{ + + private static final int DESIRED_BUFFER_SIZE = 3840 * 500; + private static final int MAX_READ_FAILS = 100; + + private final CircularByteBuffer audioBuffer = new CircularByteBuffer(3840 * 1024); + + private final AudioInputStream input; + + private boolean arrayErr = false; // supresses ArrayIndexOutOfBoundsException after the first time, to prevent spam + + public TrackPlayer(Song song) throws UnsupportedAudioFileException, IOException + { + AudioInputStream in = AudioSystem.getAudioInputStream(song.getLocation()); + AudioFormat decodedFormat = AudioSendHandler.INPUT_FORMAT; + input = AudioSystem.getAudioInputStream(decodedFormat, in); + fillBuffer(false); + } + + public TrackPlayer(AudioInputStream input) + { + this.input = input; + fillBuffer(false); + } + + boolean has(int byteCount) + { + return audioBuffer.getCurrentNumberOfBytes() > byteCount; + } + + public ByteBuffer read(int length) + { + boolean filled = fillBuffer(true); + if (!filled) + throw new OutOfInputException(); + + byte[] data = new byte[length]; + audioBuffer.read(data, 0, data.length); +// byte[] data = queue.poll(); + return ByteBuffer.wrap(data); // Wrap this in a java.nio.ByteBuffer + } + + private boolean fillBuffer(boolean canSkip) + { + int fails = 0; + // use what we have in our buffer to send audio as PCM + while (audioBuffer.getCurrentNumberOfBytes() < DESIRED_BUFFER_SIZE) + { + boolean read = readData(); + if (!read && !canSkip) + return false; + else if (fails < MAX_READ_FAILS) + fails++; + else + return false; + } + return true; + } + + private boolean readData() + { + if (input == null) + return false; + try + { + // if (din.available() == 0) + // return false; + int bytesToRead = DESIRED_BUFFER_SIZE - audioBuffer.getCurrentNumberOfBytes(); + int space = audioBuffer.getSpace(); + if (input.available() > 0 && input.available() < bytesToRead) + bytesToRead = input.available(); + if (bytesToRead > space) + bytesToRead = space; + if (bytesToRead == 0) + return false; + byte[] bytes = new byte[bytesToRead]; + // byte[] bytes = din.readNBytes(bytesToRead); + int read = input.read(bytes); +// System.out.println("Wanted: " + byteCount + " Space:" + space + " Available: " + din.available() + " To read: " + bytesToRead + " Read: " + read); + if (read < 0) + return false; +// queue.add(bytes); + + audioBuffer.add(bytes, 0, read); + } catch (IOException ex) + { + 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; + } + + @Override + public void close() throws IOException + { + input.close(); + } + + + public static class OutOfInputException extends RuntimeException + { + + } + +}