/* * 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.BufferedInputStream; 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 = 3; private static final int RETRY_COUNT = 8; private static final int RETRY_DELAY = 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(Track track) throws UnsupportedAudioFileException, IOException { AudioInputStream in = null; AudioFormat decodedFormat = null; int retry = 0; while (in == null) { try { in = AudioSystem.getAudioInputStream(new BufferedInputStream(track.getInputStream())); decodedFormat = AudioSendHandler.INPUT_FORMAT; break; // it worked! } catch (Exception ex) { retry++; if (retry < RETRY_COUNT) { System.err.println("Open file " + track.getLocation() + " failed because " + ex.getMessage() + " retry " + retry + "..."); try { Thread.sleep(((long) Math.pow(2, retry)) * RETRY_DELAY); } catch (InterruptedException ex1) { } } else { throw ex; } } } input = AudioSystem.getAudioInputStream(decodedFormat, in); fillBuffer(false); } public TrackPlayer(AudioInputStream input) throws IOException { this.input = input; fillBuffer(false); } public boolean has(int byteCount) { // return true; return audioBuffer.getCurrentNumberOfBytes() >= byteCount; } public ByteBuffer read(int length) throws IOException { boolean filled = fillBuffer(true); // if (!filled) // throw new OutOfInputException(); int toRead = Math.min(length, audioBuffer.getCurrentNumberOfBytes()); System.out.println("To read: " + toRead + " from " + audioBuffer.getCurrentNumberOfBytes()); if (toRead <= 0) throw new OutOfInputException(); byte[] data = new byte[toRead]; audioBuffer.read(data, 0, data.length); // byte[] data = queue.poll(); return ByteBuffer.wrap(data); // Wrap this in a java.nio.ByteBuffer } /** * * @param canSkip * @return true if the buffer is not empty; false otherwise */ private boolean fillBuffer(boolean canSkip) throws IOException { 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; } /** * * @return true if any data was read; false otherwise */ private boolean readData() throws IOException { 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); // System.out.println("SAMPLES player buff: " + Util.printSamples(audioBuffer)); } catch (ArrayIndexOutOfBoundsException ex) { if (!arrayErr) arrayErr = true; else throw ex; } return true; } public void close() throws IOException { input.close(); //q } public static class OutOfInputException extends RuntimeException { } }