From f64ed119a77cf25f3be4197e85557a2d3e3f3b71 Mon Sep 17 00:00:00 2001 From: Nekojimi Date: Tue, 19 Apr 2022 23:44:35 +0100 Subject: [PATCH 1/4] WIP: work on new track player system. --- .../moe/nekojimi/chords/MusicHandler.java | 153 +++++++++--------- .../java/moe/nekojimi/chords/TrackPlayer.java | 138 ++++++++++++++++ 2 files changed, 217 insertions(+), 74 deletions(-) create mode 100644 src/main/java/moe/nekojimi/chords/TrackPlayer.java 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 + { + + } + +} From 795e31aa626e257bcfeb0443ba0afa2ac3919f6a Mon Sep 17 00:00:00 2001 From: Nekojimi Date: Sun, 19 Jun 2022 00:00:15 +0100 Subject: [PATCH 2/4] Fix playback issues caused by new track player. Submitted ByteBuffer needed to be rewound, and provide method should return true until there are zero bytes left in the buffer. --- .../moe/nekojimi/chords/MusicHandler.java | 59 +++++++++++++------ .../java/moe/nekojimi/chords/TrackPlayer.java | 41 ++++++++----- 2 files changed, 67 insertions(+), 33 deletions(-) diff --git a/src/main/java/moe/nekojimi/chords/MusicHandler.java b/src/main/java/moe/nekojimi/chords/MusicHandler.java index 9931c6b..6f84a50 100644 --- a/src/main/java/moe/nekojimi/chords/MusicHandler.java +++ b/src/main/java/moe/nekojimi/chords/MusicHandler.java @@ -5,15 +5,15 @@ */ package moe.nekojimi.chords; -import java.io.Closeable; -import java.io.IOException; +import java.io.*; +import moe.nekojimi.chords.Util; import java.nio.ByteBuffer; import java.util.LinkedList; import java.util.Queue; import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; -import javax.sound.sampled.UnsupportedAudioFileException; +import javax.sound.sampled.*; import net.dv8tion.jda.api.audio.AudioSendHandler; import org.apache.commons.io.input.buffer.CircularByteBuffer; @@ -27,7 +27,7 @@ public class MusicHandler implements AudioSendHandler, Closeable, Consumer private final LinkedList songQueue = new LinkedList<>(); // private final Queue queue = new ConcurrentLinkedQueue<>(); private final CircularByteBuffer audioBuffer = new CircularByteBuffer(3840 * 1024); - private boolean playing = true; + private boolean playing = true; private int byteCount; private boolean arrayErr = false; @@ -35,8 +35,22 @@ public class MusicHandler implements AudioSendHandler, Closeable, Consumer private Song currentSong; private TrackPlayer player; + 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); + } } public void addSong(Song song) @@ -94,7 +108,11 @@ public class MusicHandler implements AudioSendHandler, Closeable, Consumer } currentSong = songQueue.poll(); if (currentSong == null) + { + System.out.println("End of queue."); + debugOut.flush(); return false; + } System.out.println("Playing song " + currentSong.getLocation().getAbsolutePath()); arrayErr = false; byteCount = 3840; @@ -125,7 +143,7 @@ public class MusicHandler implements AudioSendHandler, Closeable, Consumer @Override public boolean canProvide() { - return player != null && player.has(byteCount); + return player != null && player.has(1); // If we have something in our buffer we can provide it to the send system // return audioBuffer.getCurrentNumberOfBytes() > byteCount && playing; } @@ -136,25 +154,32 @@ public class MusicHandler implements AudioSendHandler, Closeable, Consumer ByteBuffer ret = ByteBuffer.allocate(byteCount); while (ret.position() < byteCount && player != null) { - System.out.println("Position: " + ret.position() + " Remaining: " + ret.remaining()); - ByteBuffer read = player.read(ret.remaining()); - - if (read != null) +// System.out.println("Position: " + ret.position() + " Remaining: " + ret.remaining()); + try { + ByteBuffer read = player.read(ret.remaining()); + // System.out.println("SAMPLES from player: " + Util.printSamples(read)); + System.out.println("Read: " + read.remaining()); ret.put(read); - } else if (!nextSong()) + } catch (TrackPlayer.OutOfInputException | IOException ex) { - System.out.println("Out of songs!"); - break; + System.out.println("Track ended, starting next."); + boolean foundNext = nextSong(); + + if (!foundNext) + { + System.out.println("Out of tracks!"); + 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; -// 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) diff --git a/src/main/java/moe/nekojimi/chords/TrackPlayer.java b/src/main/java/moe/nekojimi/chords/TrackPlayer.java index 340efab..a1aa680 100644 --- a/src/main/java/moe/nekojimi/chords/TrackPlayer.java +++ b/src/main/java/moe/nekojimi/chords/TrackPlayer.java @@ -25,7 +25,7 @@ public class TrackPlayer implements Closeable { private static final int DESIRED_BUFFER_SIZE = 3840 * 500; - private static final int MAX_READ_FAILS = 100; + private static final int MAX_READ_FAILS = 3; private final CircularByteBuffer audioBuffer = new CircularByteBuffer(3840 * 1024); @@ -41,7 +41,7 @@ public class TrackPlayer implements Closeable fillBuffer(false); } - public TrackPlayer(AudioInputStream input) + public TrackPlayer(AudioInputStream input) throws IOException { this.input = input; fillBuffer(false); @@ -49,22 +49,33 @@ public class TrackPlayer implements Closeable boolean has(int byteCount) { - return audioBuffer.getCurrentNumberOfBytes() > byteCount; +// return true; + return audioBuffer.getCurrentNumberOfBytes() >= byteCount; } - public ByteBuffer read(int length) + public ByteBuffer read(int length) throws IOException { boolean filled = fillBuffer(true); - if (!filled) +// 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[length]; + 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 } - private boolean fillBuffer(boolean canSkip) + /** + * + * @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 @@ -81,7 +92,11 @@ public class TrackPlayer implements Closeable return true; } - private boolean readData() + /** + * + * @return true if any data was read; false otherwise + */ + private boolean readData() throws IOException { if (input == null) return false; @@ -106,19 +121,13 @@ public class TrackPlayer implements Closeable // queue.add(bytes); audioBuffer.add(bytes, 0, read); - } catch (IOException ex) - { - Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); - return false; +// System.out.println("SAMPLES player buff: " + Util.printSamples(audioBuffer)); } catch (ArrayIndexOutOfBoundsException ex) { if (!arrayErr) arrayErr = true; else - { - Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); - return false; - } + throw ex; } return true; } From d7c48b38fa1aad6c97e310aedeed83d229b4477d Mon Sep 17 00:00:00 2001 From: Nekojimi Date: Sun, 19 Jun 2022 00:00:45 +0100 Subject: [PATCH 3/4] Add debug method. --- src/main/java/moe/nekojimi/chords/Util.java | 37 +++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/main/java/moe/nekojimi/chords/Util.java diff --git a/src/main/java/moe/nekojimi/chords/Util.java b/src/main/java/moe/nekojimi/chords/Util.java new file mode 100644 index 0000000..2c181ba --- /dev/null +++ b/src/main/java/moe/nekojimi/chords/Util.java @@ -0,0 +1,37 @@ +package moe.nekojimi.chords; + +import java.nio.ByteBuffer; + +/* + * Copyright (C) 2022 jimj316 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * + * @author jimj316 + */ +public class Util +{ + public static String printSamples(ByteBuffer buf) + { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < buf.limit(); i += 128) + { + sb.append(String.format("%x ", buf.get(i))); + } + return sb.toString(); + } +} From 594ffa9ba9bb36e2505a6cf25423817a2f3f0bf3 Mon Sep 17 00:00:00 2001 From: Nekojimi Date: Sun, 19 Jun 2022 19:40:43 +0100 Subject: [PATCH 4/4] Move song queue to new QueueManager class. --- src/main/java/moe/nekojimi/chords/Main.java | 15 +- .../moe/nekojimi/chords/MusicHandler.java | 57 ++----- .../java/moe/nekojimi/chords/Playlist.java | 5 + .../moe/nekojimi/chords/QueueManager.java | 140 ++++++++++++++++++ .../java/moe/nekojimi/chords/TrackPlayer.java | 5 +- .../chords/commands/QueueCommand.java | 2 +- .../chords/commands/RemoveCommand.java | 4 +- .../chords/commands/RestartCommand.java | 2 +- 8 files changed, 178 insertions(+), 52 deletions(-) create mode 100644 src/main/java/moe/nekojimi/chords/QueueManager.java diff --git a/src/main/java/moe/nekojimi/chords/Main.java b/src/main/java/moe/nekojimi/chords/Main.java index a47d741..13f93fc 100644 --- a/src/main/java/moe/nekojimi/chords/Main.java +++ b/src/main/java/moe/nekojimi/chords/Main.java @@ -37,6 +37,7 @@ public class Main extends ListenerAdapter private MusicHandler musicHandler; private final Downloader downloader; private final Searcher searcher; + private final QueueManager queueManager; private JDA jda; private final Map commands = new HashMap<>(); @@ -81,6 +82,8 @@ public class Main extends ListenerAdapter public Main() { log("INFO", "Starting up..."); + + // init downloader downloader = new Downloader(); downloader.setMessageHandler((Song song, Exception ex) -> { @@ -110,8 +113,14 @@ public class Main extends ListenerAdapter } }); + + // init searcher searcher = MetaSearcher.loadYAML(new File("searchproviders.yml")); + // init queue manager + queueManager = new QueueManager(); + + // init commands addCommand(new JoinCommand(this)); addCommand(new LeaveCommand(this)); addCommand(new PlayCommand(this)); @@ -200,7 +209,7 @@ public class Main extends ListenerAdapter song.setRequestedIn(event.getChannel().getId()); song.setNumber(trackNumber); trackNumber++; - downloader.accept(new Downloader.DownloadTask(song, musicHandler)); + downloader.accept(new Downloader.DownloadTask(song, queueManager)); return song; } @@ -281,6 +290,10 @@ public class Main extends ListenerAdapter return currentVoiceChannel; } + public QueueManager getQueueManager() + { + return queueManager; + } public int getTrackNumber() { diff --git a/src/main/java/moe/nekojimi/chords/MusicHandler.java b/src/main/java/moe/nekojimi/chords/MusicHandler.java index 6f84a50..a754b26 100644 --- a/src/main/java/moe/nekojimi/chords/MusicHandler.java +++ b/src/main/java/moe/nekojimi/chords/MusicHandler.java @@ -6,11 +6,7 @@ package moe.nekojimi.chords; import java.io.*; -import moe.nekojimi.chords.Util; import java.nio.ByteBuffer; -import java.util.LinkedList; -import java.util.Queue; -import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; import javax.sound.sampled.*; @@ -21,10 +17,11 @@ import org.apache.commons.io.input.buffer.CircularByteBuffer; * * @author jimj316 */ -public class MusicHandler implements AudioSendHandler, Closeable, Consumer +public class MusicHandler implements AudioSendHandler, Closeable { - private final LinkedList songQueue = new LinkedList<>(); + private QueueManager queueManager; +// private final LinkedList songQueue = new LinkedList<>(); // private final Queue queue = new ConcurrentLinkedQueue<>(); private final CircularByteBuffer audioBuffer = new CircularByteBuffer(3840 * 1024); private boolean playing = true; @@ -53,37 +50,22 @@ public class MusicHandler implements AudioSendHandler, Closeable, Consumer } } - public void addSong(Song song) + void setQueueManager(QueueManager manager) { - System.out.println("Song added to queue: " + song.getLocation().getAbsolutePath()); - songQueue.add(song); - if (!canProvide() && playing) - nextSong(); - } - - public boolean removeSong(int i) - { - try - { - songQueue.remove(i); - return true; - } catch (ArrayIndexOutOfBoundsException ex) - { - return false; - } + queueManager = manager; } - public boolean removeSong(Song song) + public void playNext() { - return songQueue.remove(song); + nextSong(true); } - public boolean restartSong() - { - songQueue.addFirst(currentSong); - currentSong = null; - return nextSong(true); - } +// public boolean restartSong() +// { +//// songQueue.addFirst(currentSong); +// currentSong = null; +// return nextSong(true); +// } private boolean nextSong() { @@ -106,7 +88,7 @@ public class MusicHandler implements AudioSendHandler, Closeable, Consumer currentSong.delete(); currentSong = null; } - currentSong = songQueue.poll(); + currentSong = queueManager.nextSongNeeded(); if (currentSong == null) { System.out.println("End of queue."); @@ -232,12 +214,6 @@ public class MusicHandler implements AudioSendHandler, Closeable, Consumer // } // return true; // } - - public Queue getSongQueue() - { - return songQueue; - } - public Song getCurrentSong() { return currentSong; @@ -254,9 +230,4 @@ public class MusicHandler implements AudioSendHandler, Closeable, Consumer { } - @Override - public void accept(Song t) - { - addSong(t); - } } diff --git a/src/main/java/moe/nekojimi/chords/Playlist.java b/src/main/java/moe/nekojimi/chords/Playlist.java index 0e1f335..2ba65d0 100644 --- a/src/main/java/moe/nekojimi/chords/Playlist.java +++ b/src/main/java/moe/nekojimi/chords/Playlist.java @@ -61,4 +61,9 @@ public class Playlist return songs; } + Song getNextSong() + { + throw new UnsupportedOperationException("Not supported yet."); + } + } diff --git a/src/main/java/moe/nekojimi/chords/QueueManager.java b/src/main/java/moe/nekojimi/chords/QueueManager.java new file mode 100644 index 0000000..2382ca1 --- /dev/null +++ b/src/main/java/moe/nekojimi/chords/QueueManager.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2022 jimj316 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package moe.nekojimi.chords; + +import java.util.LinkedList; +import java.util.Queue; +import java.util.function.Consumer; + +/** + * + * @author jimj316 + */ +public class QueueManager implements Consumer +{ + + private Mode mode; + private final Queue jukeboxQueue; + private Playlist playlist; + private MusicHandler handler; + + public QueueManager() + { + jukeboxQueue = new LinkedList<>(); + } + + @Override + public void accept(Song t) + { + jukeboxQueue.add(t); + + handler.playNext(); + } + + /** + * Called by the music handler when the current song has ended, or if + * playNext is called with nothing playing. + * + * @return the next track to play, or null to stop playing. + */ + public Song nextSongNeeded() + { + // if there's anything in the queue, play that first + if (!jukeboxQueue.isEmpty()) + { + return jukeboxQueue.poll(); + } + // otherwise if there's a playlist, shuffle from that + else if (playlist != null) + { + return playlist.getNextSong(); + } + // otherwise stop playing + else + return null; + } + + public MusicHandler getHandler() + { + return handler; + } + + public void addSong(Song song) + { + System.out.println("Song added to queue: " + song.getLocation().getAbsolutePath()); + jukeboxQueue.add(song); + + } + + public boolean removeSong(int i) + { + try + { + return jukeboxQueue.remove((Song) jukeboxQueue.toArray()[i]); + } catch (ArrayIndexOutOfBoundsException ex) + { + return false; + } + } + + public boolean removeSong(Song song) + { + return jukeboxQueue.remove(song); + } + + public void setHandler(MusicHandler handler) + { + this.handler = handler; + handler.setQueueManager(this); + } + + public Queue getJukeboxQueue() + { + return jukeboxQueue; + } + + public Playlist getPlaylist() + { + return playlist; + } + + public void setPlaylist(Playlist playlist) + { + this.playlist = playlist; + } + + public Mode getMode() + { + return mode; + } + + public void setMode(Mode mode) + { + this.mode = mode; + } + + public boolean restartSong() + { + throw new UnsupportedOperationException("Not supported yet."); + } + + public enum Mode + { + JUKEBOX, + PLAYLIST; + } +} diff --git a/src/main/java/moe/nekojimi/chords/TrackPlayer.java b/src/main/java/moe/nekojimi/chords/TrackPlayer.java index a1aa680..4826ea6 100644 --- a/src/main/java/moe/nekojimi/chords/TrackPlayer.java +++ b/src/main/java/moe/nekojimi/chords/TrackPlayer.java @@ -8,8 +8,6 @@ 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; @@ -47,7 +45,7 @@ public class TrackPlayer implements Closeable fillBuffer(false); } - boolean has(int byteCount) + public boolean has(int byteCount) { // return true; return audioBuffer.getCurrentNumberOfBytes() >= byteCount; @@ -132,7 +130,6 @@ public class TrackPlayer implements Closeable return true; } - @Override public void close() throws IOException { input.close(); diff --git a/src/main/java/moe/nekojimi/chords/commands/QueueCommand.java b/src/main/java/moe/nekojimi/chords/commands/QueueCommand.java index e01587d..e20f8f8 100644 --- a/src/main/java/moe/nekojimi/chords/commands/QueueCommand.java +++ b/src/main/java/moe/nekojimi/chords/commands/QueueCommand.java @@ -43,7 +43,7 @@ public class QueueCommand extends Command else message += ":mute: **Not playing anything right now.**\n"; - final Queue songQueue = bot.getMusicHandler().getSongQueue(); + final Queue songQueue = bot.getQueueManager().getJukeboxQueue(); if (!songQueue.isEmpty()) { message += "__Ready to play:__\n"; diff --git a/src/main/java/moe/nekojimi/chords/commands/RemoveCommand.java b/src/main/java/moe/nekojimi/chords/commands/RemoveCommand.java index 40679a0..b68b682 100644 --- a/src/main/java/moe/nekojimi/chords/commands/RemoveCommand.java +++ b/src/main/java/moe/nekojimi/chords/commands/RemoveCommand.java @@ -34,8 +34,8 @@ public class RemoveCommand extends Command try { int i = Integer.parseInt(arg.get(0)); - boolean removed = bot.getMusicHandler().removeSong(i - 1); - final int size = bot.getMusicHandler().getSongQueue().size(); + boolean removed = bot.getQueueManager().removeSong(i - 1); + final int size = bot.getQueueManager().getJukeboxQueue().size(); if (removed) event.getChannel().sendMessage("Song removed.").queue(); else if (size > 1) diff --git a/src/main/java/moe/nekojimi/chords/commands/RestartCommand.java b/src/main/java/moe/nekojimi/chords/commands/RestartCommand.java index 5cdac33..ee17125 100644 --- a/src/main/java/moe/nekojimi/chords/commands/RestartCommand.java +++ b/src/main/java/moe/nekojimi/chords/commands/RestartCommand.java @@ -32,7 +32,7 @@ public class RestartCommand extends Command public void call(GuildMessageReceivedEvent event, List arg) { // TODO: this needs to clear the current data queue - boolean ok = bot.getMusicHandler().restartSong(); + boolean ok = bot.getQueueManager().restartSong(); if (ok) event.getChannel().sendMessage("Restarted current song!").queue(); else