/* * 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.*; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; import javax.sound.sampled.*; import net.dv8tion.jda.api.audio.AudioSendHandler; /** * * @author jimj316 */ public class MusicHandler implements AudioSendHandler, Closeable { private QueueManager queueManager; // private final LinkedList trackQueue = new LinkedList<>(); // private final Queue queue = new ConcurrentLinkedQueue<>(); // private final CircularByteBuffer audioBuffer = new CircularByteBuffer(3840 * 1024); private boolean shouldPlay = true; private int byteCount; private boolean arrayErr = false; private Consumer nowPlayingConsumer; public void setNowPlayingConsumer(Consumer nowPlayingConsumer) { this.nowPlayingConsumer = nowPlayingConsumer; } private Track currentTrack; // private TrackPlayer player; private final List playingTracks = new ArrayList<>(); private File debugOutFile; private BufferedOutputStream debugOut; public MusicHandler() { try { debugOutFile = new File("debug.wav"); if (debugOutFile.exists()) debugOutFile.delete(); debugOutFile.createNewFile(); debugOut = new BufferedOutputStream(new FileOutputStream(debugOutFile)); } catch (IOException ex) { Logger.getLogger(MusicHandler.class.getName()).log(Level.SEVERE, null, ex); } } void setQueueManager(QueueManager manager) { queueManager = manager; } public void playNext() { nextTrack(true); } public void playOver(Track track) { } private boolean nextTrack() { return nextTrack(false); } public boolean nextTrack(boolean immediate) { if (immediate) { System.out.println("Immediate next - clearing buffer"); playingTracks.clear(); } try { if (currentTrack != null) { if (!currentTrack.isKept()) currentTrack.delete(); currentTrack = null; } currentTrack = queueManager.nextTrackNeeded(); if (nowPlayingConsumer != null) nowPlayingConsumer.accept(currentTrack); if (currentTrack == null) { System.out.println("End of queue."); debugOut.flush(); return false; } System.out.println("Playing track " + currentTrack.getLocation().getAbsolutePath()); arrayErr = false; byteCount = 3840; TrackPlayer player = new TrackPlayer(currentTrack); playingTracks.add(player); // System.out.println("Queue filled to " + audioBuffer.getCurrentNumberOfBytes()); return true; } catch (UnsupportedAudioFileException | IOException ex) { Logger.getLogger(Chords.class.getName()).log(Level.SEVERE, null, ex); } finally { } return false; } public boolean isPlaying() { return !playingTracks.isEmpty(); } public boolean isShouldPlay() { return shouldPlay; } public void setShouldPlay(boolean shouldPlay) { if (!this.shouldPlay && shouldPlay) nextTrack(); this.shouldPlay = shouldPlay; } @Override public boolean canProvide() { if (playingTracks.isEmpty()) return false; for (TrackPlayer player : playingTracks) if (player.has(1)) return true; return false; // If we have something in our buffer we can provide it to the send system // return audioBuffer.getCurrentNumberOfBytes() > byteCount && shouldPlay; } @Override public ByteBuffer provide20MsAudio() { ByteBuffer ret = ByteBuffer.allocate(byteCount); while (ret.position() < byteCount && !playingTracks.isEmpty()) { boolean outOfInput = true; List mixes = new ArrayList<>(); List emptyPlayers = new ArrayList<>(); for (TrackPlayer player : playingTracks) { try { ByteBuffer read = player.read(ret.remaining()); if (ret.limit() + read.position() >= byteCount) outOfInput = false; mixes.add(read); // ret.put(read); } catch (TrackPlayer.OutOfInputException | IOException ex) { // System.out.println("Track player " + player + " stopped giving input: " + ex.getMessage()); emptyPlayers.add(player); // System.out.println("Track ended, starting next."); // outOfInput = true; } } playingTracks.removeAll(emptyPlayers); ret.put(mixBuffers(mixes)); if (outOfInput) { boolean foundNext = nextTrack(); if (!foundNext) break; } } // System.out.println("Buffer filled, submitting."); ret.rewind(); // required apparently, if returned buf has pos > 0 you get silence assert ret.hasArray(); // output MUST be array backed return ret; } public Track getCurrentTrack() { return currentTrack; } @Override public boolean isOpus() { return false; //To change body of generated methods, choose Tools | Templates. } @Override public void close() throws IOException { } private ByteBuffer mixBuffers(List mixes) { // System.out.println("Mixing " + mixes.size() + " buffers"); if (mixes.size() == 1) return mixes.get(0); int maxSize = 0; for (ByteBuffer buf : mixes) { if (buf.limit() > maxSize) maxSize = buf.position(); } ByteBuffer ret = ByteBuffer.allocate(maxSize); for (int i = 0; i < ret.limit(); i++) { int byteTotal = 0; int mixCount = 0; for (ByteBuffer buf : mixes) { if (i < buf.limit()) { byteTotal += buf.get(i); mixCount++; } } double avg = ((double) byteTotal) / mixCount; byte byteVal = (byte) Math.round(avg); ret.put(byteVal); } ret.rewind(); return ret; } }