Compare commits

..

No commits in common. "7d3795b10a00400930f57bb4741c65641eb576ba" and "b41f5c97a3589d98e718bde9f70214b3996e5a60" have entirely different histories.

12 changed files with 144 additions and 547 deletions

View File

@ -1,11 +1,2 @@
# Chords - Yet Another Discord Music Bot # Chords
![The Chords logo - a white guitar with the word "Chords" in bubble font, on a blue background.](chords.png)
Basically everyone has written one at this point, but our little community needed one, so I made one!
Here's some stuff about it:
- Can play from any site that youtube-dl supports ([that's over 1800!](https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md))
- Built-in multi-site searching via my own [MusicSearcher](https://fluff.nekojimi.moe/gitea/Nekojimi/MusicSearcher) project
- More cool stuff coming soon!

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

View File

@ -14,6 +14,7 @@ import java.nio.file.Files;
import java.util.*; import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer;
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;
@ -23,13 +24,14 @@ import javax.json.Json;
import javax.json.JsonArray; import javax.json.JsonArray;
import javax.json.JsonObject; import javax.json.JsonObject;
import javax.json.JsonReader; import javax.json.JsonReader;
import moe.nekojimi.chords.Downloader.DownloadTask;
import net.dv8tion.jda.api.audio.AudioSendHandler; import net.dv8tion.jda.api.audio.AudioSendHandler;
/** /**
* *
* @author jimj316 * @author jimj316
*/ */
public class Downloader extends QueueThing<TrackRequest, Track> public class Downloader implements Consumer<DownloadTask>
{ {
private static final int DOWNLOAD_TIMEOUT = 300; private static final int DOWNLOAD_TIMEOUT = 300;
@ -43,16 +45,7 @@ public class Downloader extends QueueThing<TrackRequest, Track>
private static final Pattern ETA_PATTERN = Pattern.compile("\\[download\\].*?ETA\\s+(\\d{1,2}:\\d{2})"); private static final Pattern ETA_PATTERN = Pattern.compile("\\[download\\].*?ETA\\s+(\\d{1,2}:\\d{2})");
private static final Pattern DOWNLOAD_ITEM_PATTERN = Pattern.compile("\\[download\\] Downloading item (\\d+) of (\\d+)"); private static final Pattern DOWNLOAD_ITEM_PATTERN = Pattern.compile("\\[download\\] Downloading item (\\d+) of (\\d+)");
public static final String[] INFO_TITLE_KEYS = private final List<DownloadTask> downloadQueue = new LinkedList<>();
{
"track", "title", "fulltitle"
};
public static final String[] INFO_ARTIST_KEYS =
{
"artist", "channel", "uploader"
};
private final List<TrackRequest> downloadingTracks = new LinkedList<>();
private final LinkedBlockingDeque<Runnable> workQueue = new LinkedBlockingDeque<>(); private final LinkedBlockingDeque<Runnable> workQueue = new LinkedBlockingDeque<>();
private final ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 30, TimeUnit.SECONDS, workQueue); private final ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 30, TimeUnit.SECONDS, workQueue);
private final ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(1); private final ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(1);
@ -65,45 +58,34 @@ public class Downloader extends QueueThing<TrackRequest, Track>
public Downloader() public Downloader()
{ {
super(new LinkedList<>());
} }
//
// @Override
// public void accept(TrackRequest request)
// {
// if all tracks of the request are already downloaded, just skip
// if (!request.getTracks().isEmpty() && request.getTracks().stream().allMatch((t) -> t.isDownloaded()))
// {
// for (Track track : request.getTracks())
// getDestination().accept(track);
// return;
// }
// inputQueue.add(task);
// }
@Override @Override
protected boolean completePromise(Promise<TrackRequest, Track> promise) public void accept(DownloadTask task)
{ {
TrackRequest request = promise.getInput(); // if all tracks of the request are already downloaded, just skip
if (!task.request.getTracks().isEmpty() && task.request.getTracks().stream().allMatch((t) -> t.isDownloaded()))
{
for (Track track : task.request.getTracks())
task.getDestination().accept(track);
return;
}
downloadQueue.add(task);
getInfo(task.request);
// TODO: get info should also use the thread pool
executor.submit(() -> executor.submit(() ->
{ {
downloadingTracks.add(request);
try try
{ {
// getFormats(track);
getInfo(request); download(task);
download(promise);
} catch (Exception ex) } catch (Exception ex)
{ {
ex.printStackTrace(); ex.printStackTrace();
} }
downloadingTracks.remove(request);
}); });
return true;
} }
private List<Format> sortFormats(Collection<Format> input) private List<Format> sortFormats(Collection<Format> input)
@ -235,7 +217,7 @@ public class Downloader extends QueueThing<TrackRequest, Track>
Scanner sc = new Scanner(input); Scanner sc = new Scanner(input);
while (sc.hasNextLine()) while (sc.hasNextLine())
{ {
Track track = new Track(request); Track track = new Track(request.getUrl());
track.setNumber(trackNumber); track.setNumber(trackNumber);
trackNumber++; trackNumber++;
request.addTrack(track); request.addTrack(track);
@ -243,40 +225,10 @@ public class Downloader extends QueueThing<TrackRequest, Track>
String line = sc.nextLine(); String line = sc.nextLine();
JsonReader reader = Json.createReader(new StringReader(line)); JsonReader reader = Json.createReader(new StringReader(line));
JsonObject object = reader.readObject(); JsonObject object = reader.readObject();
// look for metadata
if (track.getTitle() == null) if (track.getTitle() == null)
{ track.setTitle(object.getString("title", null));
for (String key : INFO_TITLE_KEYS)
{
if (object.containsKey(key) && !object.getString(key).isBlank())
{
track.setTitle(object.getString(key));
break;
}
}
}
if (track.getArtist() == null) if (track.getArtist() == null)
{ track.setArtist(object.getString("uploader", null));
for (String key : INFO_ARTIST_KEYS)
{
if (object.containsKey(key) && !object.getString(key).isBlank())
{
track.setArtist(object.getString(key));
break;
}
}
}
if (track.getTitle().contains("-"))
{
String[] split = track.getTitle().split("-", 2);
track.setArtist(split[0]);
track.setTitle(split[1]);
}
if (track.getArtist().contains(" - Topic"))
{
track.setArtist(track.getArtist().replace(" - Topic", ""));
}
JsonArray formatsJSON = object.getJsonArray("formats"); JsonArray formatsJSON = object.getJsonArray("formats");
if (formatsJSON != null) if (formatsJSON != null)
@ -312,7 +264,7 @@ public class Downloader extends QueueThing<TrackRequest, Track>
// if there's less tracks in the request than expected, fill the array // if there's less tracks in the request than expected, fill the array
while (idx >= request.getTracks().size()) while (idx >= request.getTracks().size())
{ {
Track track = new Track(request); Track track = new Track(request.getUrl());
track.setNumber(trackNumber); track.setNumber(trackNumber);
trackNumber++; trackNumber++;
request.addTrack(track); request.addTrack(track);
@ -320,12 +272,10 @@ public class Downloader extends QueueThing<TrackRequest, Track>
return request.getTracks().get(idx); return request.getTracks().get(idx);
} }
private void download(Promise<TrackRequest, Track> promise) throws InterruptedException, ExecutionException private void download(DownloadTask task)
{ {
TrackRequest request = promise.getInput();
Set<Format> uniqueFormats = new HashSet<>(); Set<Format> uniqueFormats = new HashSet<>();
for (Track track : task.request.getTracks())
for (Track track : request.getTracks())
{ {
uniqueFormats.addAll(track.getFormats()); uniqueFormats.addAll(track.getFormats());
} }
@ -340,7 +290,7 @@ public class Downloader extends QueueThing<TrackRequest, Track>
try try
{ {
messageHandler.accept(request, null); messageHandler.accept(task.request, null);
String cmd = Chords.getSettings().getYtdlCommand() String cmd = Chords.getSettings().getYtdlCommand()
+ " -x" + " -x"
+ " -f " + formatCodes + "worstaudio/bestaudio/worst/best" + " -f " + formatCodes + "worstaudio/bestaudio/worst/best"
@ -348,7 +298,7 @@ public class Downloader extends QueueThing<TrackRequest, Track>
+ " --no-playlist" + " --no-playlist"
+ " --extractor-args youtube:player_client=android" + " --extractor-args youtube:player_client=android"
+ " -o " + getDownloadDir().getAbsolutePath() + "/%(title)s.%(ext)s " + " -o " + getDownloadDir().getAbsolutePath() + "/%(title)s.%(ext)s "
+ request.getUrl().toString(); + task.request.getUrl().toString();
Process exec = runCommand(cmd, DOWNLOAD_TIMEOUT); Process exec = runCommand(cmd, DOWNLOAD_TIMEOUT);
InputStream in = exec.getInputStream(); InputStream in = exec.getInputStream();
@ -371,40 +321,47 @@ public class Downloader extends QueueThing<TrackRequest, Track>
Matcher progMatcher = PROGRESS_PATTERN.matcher(line); Matcher progMatcher = PROGRESS_PATTERN.matcher(line);
if (progMatcher.find()) if (progMatcher.find())
{ {
getTrackFromRequest(request, downloadIdx).setProgress(Double.parseDouble(progMatcher.group(1))); getTrackFromRequest(task.request, downloadIdx).setProgress(Double.parseDouble(progMatcher.group(1)));
messageHandler.accept(request, null); messageHandler.accept(task.request, null);
} }
Matcher destMatcher = DESTINATION_PATTERN.matcher(line); Matcher destMatcher = DESTINATION_PATTERN.matcher(line);
if (destMatcher.find()) if (destMatcher.find())
{ {
Track track = getTrackFromRequest(request, downloadIdx); Track track = getTrackFromRequest(task.request, downloadIdx);
track.setLocation(new File(destMatcher.group(1))); track.setLocation(new File(destMatcher.group(1)));
// this is currently our criteria for completion; submit the track and move on // this is currently our criteria for completion; submit the track and move on
promise.complete(track); if (task.getDestination() != null)
task.getDestination().accept(track);
track.setProgress(100.0); track.setProgress(100.0);
messageHandler.accept(request, null); messageHandler.accept(task.request, null);
downloadIdx++; downloadIdx++;
} }
} }
// String output = new String(in.readAllBytes(), Charset.defaultCharset());
String error = new String(exec.getErrorStream().readAllBytes(), Charset.defaultCharset()); String error = new String(exec.getErrorStream().readAllBytes(), Charset.defaultCharset());
// System.out.println(output);
if (exec.exitValue() != 0) if (exec.exitValue() != 0)
throw new RuntimeException("youtube-dl failed with error " + exec.exitValue() + ", output:\n" + error); throw new RuntimeException("youtube-dl failed with error " + exec.exitValue() + ", output:\n" + error);
messageHandler.accept(request, null); // task.request.setProgress(100);
// return true;
downloadQueue.remove(task);
messageHandler.accept(task.request, null);
} catch (Exception ex) } catch (Exception ex)
{ {
Logger.getLogger(Downloader.class.getName()).log(Level.SEVERE, null, ex); Logger.getLogger(Downloader.class.getName()).log(Level.SEVERE, null, ex);
if (messageHandler != null) if (messageHandler != null)
messageHandler.accept(request, ex); messageHandler.accept(task.request, ex);
//downloadQueue.remove(task); downloadQueue.remove(task);
} }
} }
@ -440,39 +397,33 @@ public class Downloader extends QueueThing<TrackRequest, Track>
this.messageHandler = messageHandler; this.messageHandler = messageHandler;
} }
public List<TrackRequest> getDownloadQueue() public List<DownloadTask> getDownloadQueue()
{ {
return new ArrayList<>(inputQueue); return downloadQueue;
} }
public static class DownloadTask
// public static class DownloadTask
// {
//
// private final TrackRequest request;
// private final Consumer<Track> destination;
//
// public DownloadTask(TrackRequest request, Consumer<Track> destination)
// {
// this.request = request;
// this.destination = destination;
// }
//
// public TrackRequest getTrack()
// {
// return request;
// }
//
// public Consumer<Track> getDestination()
// {
// return destination;
// }
//
// }
public List<TrackRequest> getDownloadingTracks()
{ {
return downloadingTracks;
private final TrackRequest request;
private final Consumer<Track> destination;
public DownloadTask(TrackRequest request, Consumer<Track> destination)
{
this.request = request;
this.destination = destination;
}
public TrackRequest getTrack()
{
return request;
}
public Consumer<Track> getDestination()
{
return destination;
}
} }
} }

View File

@ -19,10 +19,9 @@ import net.dv8tion.jda.api.audio.AudioSendHandler;
* *
* @author jimj316 * @author jimj316
*/ */
public class MusicHandler implements AudioSendHandler, Closeable, Consumer<Track> public class MusicHandler implements AudioSendHandler, Closeable
{ {
// private QueueThing<?, Track> queueManager;
private QueueManager queueManager; private QueueManager queueManager;
// private final LinkedList<Track> trackQueue = new LinkedList<>(); // private final LinkedList<Track> trackQueue = new LinkedList<>();
// private final Queue<byte[]> queue = new ConcurrentLinkedQueue<>(); // private final Queue<byte[]> queue = new ConcurrentLinkedQueue<>();
@ -42,15 +41,22 @@ public class MusicHandler implements AudioSendHandler, Closeable, Consumer<Track
// private TrackPlayer player; // private TrackPlayer player;
private final List<TrackPlayer> playingTracks = new ArrayList<>(); private final List<TrackPlayer> playingTracks = new ArrayList<>();
private File debugOutFile;
private BufferedOutputStream debugOut;
public MusicHandler() public MusicHandler()
{ {
try
} {
debugOutFile = new File("debug.wav");
@Override if (debugOutFile.exists())
public void accept(Track t) debugOutFile.delete();
{ debugOutFile.createNewFile();
play(t); debugOut = new BufferedOutputStream(new FileOutputStream(debugOutFile));
} catch (IOException ex)
{
Logger.getLogger(MusicHandler.class.getName()).log(Level.SEVERE, null, ex);
}
} }
void setQueueManager(QueueManager manager) void setQueueManager(QueueManager manager)
@ -58,22 +64,24 @@ public class MusicHandler implements AudioSendHandler, Closeable, Consumer<Track
queueManager = manager; queueManager = manager;
} }
public void playNext()
{
nextTrack(true);
}
public void playOver(Track track) public void playOver(Track track)
{ {
} }
public boolean play(Track track)
private boolean nextTrack()
{ {
return play(track, false); return nextTrack(false);
} }
public boolean play(Track track, boolean immediate) public boolean nextTrack(boolean immediate)
{ {
if (track == currentTrack)
return false;
if (immediate) if (immediate)
{ {
System.out.println("Immediate next - clearing buffer"); System.out.println("Immediate next - clearing buffer");
@ -88,11 +96,13 @@ public class MusicHandler implements AudioSendHandler, Closeable, Consumer<Track
currentTrack.delete(); currentTrack.delete();
currentTrack = null; currentTrack = null;
} }
currentTrack = track; currentTrack = queueManager.nextTrackNeeded();
if (nowPlayingConsumer != null) if (nowPlayingConsumer != null)
nowPlayingConsumer.accept(currentTrack); nowPlayingConsumer.accept(currentTrack);
if (currentTrack == null) if (currentTrack == null)
{ {
System.out.println("End of queue.");
debugOut.flush();
return false; return false;
} }
System.out.println("Playing track " + currentTrack.getLocation().getAbsolutePath()); System.out.println("Playing track " + currentTrack.getLocation().getAbsolutePath());
@ -111,16 +121,6 @@ public class MusicHandler implements AudioSendHandler, Closeable, Consumer<Track
return false; return false;
} }
public boolean requestTrack()
{
if (isPlaying())
System.out.println("How did we get here?");
System.out.println("MusicHandler requesting track...");
List<QueueThing.Promise<Track, Track>> request = queueManager.request(1, this);
// Queuemanager will syncronously attempt to call play()
return !playingTracks.isEmpty();
}
public boolean isPlaying() public boolean isPlaying()
{ {
return !playingTracks.isEmpty(); return !playingTracks.isEmpty();
@ -134,7 +134,7 @@ public class MusicHandler implements AudioSendHandler, Closeable, Consumer<Track
public void setShouldPlay(boolean shouldPlay) public void setShouldPlay(boolean shouldPlay)
{ {
if (!this.shouldPlay && shouldPlay) if (!this.shouldPlay && shouldPlay)
requestTrack(); nextTrack();
this.shouldPlay = shouldPlay; this.shouldPlay = shouldPlay;
} }
@ -181,7 +181,7 @@ public class MusicHandler implements AudioSendHandler, Closeable, Consumer<Track
ret.put(mixBuffers(mixes)); ret.put(mixBuffers(mixes));
if (outOfInput) if (outOfInput)
{ {
boolean foundNext = requestTrack(); boolean foundNext = nextTrack();
if (!foundNext) if (!foundNext)
break; break;
} }
@ -244,13 +244,4 @@ public class MusicHandler implements AudioSendHandler, Closeable, Consumer<Track
return ret; return ret;
} }
public boolean skipTrack()
{
if (!isPlaying())
return false;
playingTracks.clear();
requestTrack();
return true;
}
} }

View File

@ -16,7 +16,8 @@
*/ */
package moe.nekojimi.chords; package moe.nekojimi.chords;
import java.util.List; import java.util.Comparator;
import java.util.LinkedList;
import java.util.PriorityQueue; import java.util.PriorityQueue;
import java.util.Queue; import java.util.Queue;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -25,62 +26,29 @@ import java.util.function.Consumer;
* *
* @author jimj316 * @author jimj316
*/ */
public class QueueManager extends QueueThing<Track, Track> public class QueueManager implements Consumer<Track>
{ {
private final int QUEUE_TARGET_SIZE = 5;
private Track restartingTrack = null; private Track restartingTrack = null;
// private final PriorityQueue<Track> jukeboxQueue = new PriorityQueue<>(); private final PriorityQueue<Track> jukeboxQueue = new PriorityQueue<>();
private QueueThing<?, Track> trackSource;
private Playlist playlist; private Playlist playlist;
private MusicHandler handler; private MusicHandler handler;
public QueueManager() public QueueManager()
{ {
super(new PriorityQueue<Track>());
queueTargetSize = QUEUE_TARGET_SIZE;
// jukeboxQueue = new LinkedList<>(); // jukeboxQueue = new LinkedList<>();
} }
@Override @Override
protected void notifyNewInput() public void accept(Track t)
{ {
super.notifyNewInput(); jukeboxQueue.add(t);
// if (inputQueue.size() < QUEUE_TARGET_SIZE)
// demandInput(QUEUE_TARGET_SIZE - inputQueue.size());
if (!handler.isPlaying() || handler.getCurrentTrack() == null) if (!handler.isPlaying() || handler.getCurrentTrack() == null)
handler.requestTrack(); handler.playNext();
}
@Override if (handler.isPlaying() != (handler.getCurrentTrack() == null))
protected boolean completePromise(Promise<Track, Track> request) System.err.println("WARNING: handler isPlaying violates contract! Something dumb going on!");
{
final Track input = request.getInput();
if (input != null)
{
request.complete(input);
return true;
}
return false;
}
@Override
public List<Promise<Track, Track>> request(int count, Consumer<Track> destination)
{
if (restartingTrack != null)
{
Promise ret = new Promise<>(restartingTrack, destination);
restartingTrack = null;
return List.of(ret);
}
List<Promise<Track, Track>> ret = super.request(count, destination);
if (inputQueue.size() < QUEUE_TARGET_SIZE)
demandInput(QUEUE_TARGET_SIZE - inputQueue.size());
return ret;
} }
/** /**
@ -89,31 +57,29 @@ public class QueueManager extends QueueThing<Track, Track>
* *
* @return the next track to play, or null to stop playing. * @return the next track to play, or null to stop playing.
*/ */
// public Track nextTrackNeeded() public Track nextTrackNeeded()
// { {
// Track ret; // if we're restarting the current track: store, clear, and return it
// // if we're restarting the current track: store, clear, and return it if (restartingTrack != null)
// if (restartingTrack != null) {
// { Track ret = restartingTrack;
// ret = restartingTrack; restartingTrack = null;
// restartingTrack = null; return ret;
// } }
// // if there's anything in the queue, play that first // if there's anything in the queue, play that first
// else if (!jukeboxQueue.isEmpty()) if (!jukeboxQueue.isEmpty())
// { {
// ret = jukeboxQueue.poll(); return jukeboxQueue.poll();
// } }
// // otherwise if there's a playlist, shuffle from that // otherwise if there's a playlist, shuffle from that
// else if (playlist != null) else if (playlist != null)
// { {
// ret = playlist.getNextTrack(); return playlist.getNextTrack();
// } }
// // otherwise stop playing // otherwise stop playing
// else else
// ret = null; return null;
// }
// return ret;
// }
public MusicHandler getHandler() public MusicHandler getHandler()
{ {
@ -123,7 +89,7 @@ public class QueueManager extends QueueThing<Track, Track>
public void addTrack(Track track) public void addTrack(Track track)
{ {
System.out.println("Track added to queue: " + track.getLocation().getAbsolutePath()); System.out.println("Track added to queue: " + track.getLocation().getAbsolutePath());
inputQueue.add(track); jukeboxQueue.add(track);
} }
@ -131,7 +97,7 @@ public class QueueManager extends QueueThing<Track, Track>
{ {
try try
{ {
return inputQueue.remove((Track) inputQueue.toArray()[i]); return jukeboxQueue.remove((Track) jukeboxQueue.toArray()[i]);
} catch (ArrayIndexOutOfBoundsException ex) } catch (ArrayIndexOutOfBoundsException ex)
{ {
return false; return false;
@ -140,7 +106,7 @@ public class QueueManager extends QueueThing<Track, Track>
public boolean removeTrack(Track track) public boolean removeTrack(Track track)
{ {
return inputQueue.remove(track); return jukeboxQueue.remove(track);
} }
public void setHandler(MusicHandler handler) public void setHandler(MusicHandler handler)
@ -151,7 +117,7 @@ public class QueueManager extends QueueThing<Track, Track>
public Queue<Track> getJukeboxQueue() public Queue<Track> getJukeboxQueue()
{ {
return inputQueue; return jukeboxQueue;
} }
public Playlist getPlaylist() public Playlist getPlaylist()
@ -169,19 +135,9 @@ public class QueueManager extends QueueThing<Track, Track>
restartingTrack = handler.getCurrentTrack(); restartingTrack = handler.getCurrentTrack();
if (restartingTrack != null) if (restartingTrack != null)
{ {
handler.requestTrack(); handler.playNext();
return true; return true;
} else } else
return false; return false;
} }
public QueueThing<?, Track> getTrackSource()
{
return trackSource;
}
public void setTrackSource(QueueThing<?, Track> trackSource)
{
this.trackSource = trackSource;
}
} }

View File

@ -1,208 +0,0 @@
/*
* Copyright (C) 2023 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 <http://www.gnu.org/licenses/>.
*/
package moe.nekojimi.chords;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.function.Consumer;
/**
*
* @author jimj316
*/
public abstract class QueueThing<I, O> implements Consumer<I>
{
// TODO: better name
protected final Queue<I> inputQueue;
protected final List<QueueThing<?, I>> sources = new ArrayList<>();
protected final List<QueueThing<O, ?>> sinks = new ArrayList<>();
protected final Queue<Promise<I, O>> pendingPromises = new LinkedList<>();
protected int queueTargetSize = 0;
protected QueueThing(Queue<I> inputQueue)
{
this.inputQueue = inputQueue;
}
@Override
public void accept(I t)
{
// if we've got pending promises, fullfill them, otherwise just put it in the queue and notify the sinks
if (pendingPromises.isEmpty())
{
inputQueue.add(t);
notifyNewInput();
} else
{
Promise<I, O> promise = pendingPromises.poll();
promise.setInput(t);
handlePromise(promise);
}
}
/**
* Called to notify downstream modules that there's input upstream -
* consider requesting it to pull it through
*/
protected void notifyNewInput()
{
if (inputQueue.size() < queueTargetSize)
demandInput(queueTargetSize - inputQueue.size());
for (QueueThing<O, ?> sink : sinks)
{
sink.notifyNewInput();
}
}
public List<Promise<I, O>> request(int count, Consumer<O> destination)
{
List<Promise<I, O>> ret = new ArrayList<>();
int demands = 0;
for (int i = 0; i < count; i++)
{
I input = null;
if (!inputQueue.isEmpty())
{
input = inputQueue.poll();
}
Promise<I, O> promise = new Promise<>(input, destination);
boolean ok = handlePromise(promise);
if (ok)
{
// we got a promise of output so we can tell the client
ret.add(promise);
} else
{
// we need to get more input from sources
demands++;
}
}
demands += queueTargetSize - inputQueue.size();
if (demands > 0)
{
// try to get more input from sources
int sourcePromises = demandInput(demands);
// each promise of input we get represents a promise of output we can give
for (int i = 0; i < sourcePromises; i++)
{
Promise<I, O> myPromise = new Promise<>(destination);
pendingPromises.add(myPromise);
ret.add(myPromise);
}
}
return ret;
}
private boolean handlePromise(Promise<I, O> promise)
{
boolean ok;
ok = completePromise(promise);
return ok;
}
protected abstract boolean completePromise(Promise<I, O> request);
/**
* Requests from sources a certain about of input, to be provided later.
*
* @param count the number of input items to request.
* @return a number Promises of input items. May be more or less than count.
*/
protected int demandInput(int count)
{
int ret = 0;
for (QueueThing<?, I> source : sources)
{
List<Promise<?, I>> promises = (List<Promise<?, I>>) source.request(count - ret, this);
ret += promises.size();
if (ret >= count)
break;
}
return ret;
}
public void addSource(QueueThing<?, I> source)
{
sources.add(source);
source.addSink(this);
}
public void removeSource(QueueThing<?, I> source)
{
sources.remove(source);
source.removeSink(this);
}
private void addSink(QueueThing<O, ?> sink)
{
sinks.add(sink);
}
private void removeSink(QueueThing<O, ?> sink)
{
sinks.remove(sink);
}
public static class Promise<I, O>
{
private I input;
private final Consumer<O> output;
public Promise(I input, Consumer<O> output)
{
this.input = input;
this.output = output;
}
public Promise(Consumer<O> output)
{
this.output = output;
}
public void setInput(I input)
{
this.input = input;
}
public void complete(O out)
{
output.accept(out);
}
public I getInput()
{
return input;
}
}
// protected void submit(O output)
// {
// if (nextStage != null)
// nextStage.accept(output);
// }
}

View File

@ -33,18 +33,9 @@ public class Track implements Comparable<Track>
private double progress = -1; private double progress = -1;
private double eta = -1; private double eta = -1;
private final TrackRequest request; public Track(URL url)
public Track(URL url, TrackRequest request)
{ {
this.url = url; this.url = url;
this.request = request;
}
public Track(TrackRequest request)
{
this.request = request;
this.url = request.getUrl();
} }
public YamlMapping toYaml() public YamlMapping toYaml()
@ -68,7 +59,7 @@ public class Track implements Comparable<Track>
public static Track fromYaml(YamlMapping map) throws MalformedURLException public static Track fromYaml(YamlMapping map) throws MalformedURLException
{ {
Track track = new Track(new URL(map.string("url")), null); Track track = new Track(new URL(map.string("url")));
track.setArtist(map.string("artist")); track.setArtist(map.string("artist"));
track.setTitle(map.string("title")); track.setTitle(map.string("title"));
track.setLocation(new File(map.string("location"))); track.setLocation(new File(map.string("location")));
@ -153,11 +144,6 @@ public class Track implements Comparable<Track>
this.kept = kept; this.kept = kept;
} }
public TrackRequest getRequest()
{
return request;
}
@Override @Override
public String toString() public String toString()
{ {

View File

@ -16,11 +16,9 @@
*/ */
package moe.nekojimi.chords; package moe.nekojimi.chords;
import com.amihaiemil.eoyaml.YamlMapping;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
import moe.nekojimi.chords.commands.Invocation; import moe.nekojimi.chords.commands.Invocation;
import moe.nekojimi.musicsearcher.Result; import moe.nekojimi.musicsearcher.Result;
@ -28,7 +26,7 @@ import moe.nekojimi.musicsearcher.Result;
* *
* @author jimj316 * @author jimj316
*/ */
public class TrackRequest implements Comparable<TrackRequest> public class TrackRequest
{ {
private Invocation invocation; private Invocation invocation;
@ -44,19 +42,6 @@ public class TrackRequest implements Comparable<TrackRequest>
private String requestedBy; private String requestedBy;
private String requestedIn; private String requestedIn;
private double priority = 1.0;
public YamlMapping toYAML()
{
throw new UnsupportedOperationException("Not supported yet.");
}
public static TrackRequest fromYAML(YamlMapping yaml)
{
TrackRequest ret = new TrackRequest();
return ret;
}
public List<Result> getSearchResults() public List<Result> getSearchResults()
{ {
@ -174,44 +159,4 @@ public class TrackRequest implements Comparable<TrackRequest>
return (trackName + " " + requestName).trim(); return (trackName + " " + requestName).trim();
} }
@Override
public int hashCode()
{
int hash = 7;
hash = 83 * hash + Objects.hashCode(this.url);
return hash;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final TrackRequest other = (TrackRequest) obj;
if (!Objects.equals(this.url, other.url))
return false;
return true;
}
@Override
public int compareTo(TrackRequest o)
{
return -Double.compare(priority, o.priority); // backwards so higher priority comes first
}
public double getPriority()
{
return priority;
}
public void setPriority(double priority)
{
this.priority = priority;
}
} }

View File

@ -16,8 +16,6 @@
*/ */
package moe.nekojimi.chords.commands; package moe.nekojimi.chords.commands;
import java.util.Map;
import java.util.Map.Entry;
import moe.nekojimi.chords.Chords; import moe.nekojimi.chords.Chords;
public class HelpCommand extends Command public class HelpCommand extends Command
@ -40,11 +38,10 @@ public class HelpCommand extends Command
+ "!remove <Index> - Remove the track at position <Index> from the queue.\n" + "!remove <Index> - Remove the track at position <Index> from the queue.\n"
+ "!skip - Skip the current track and play the next one.\n" + "!skip - Skip the current track and play the next one.\n"
+ "!restart - Try playing the current track again in case it goes wrong.\n"; + "!restart - Try playing the current track again in case it goes wrong.\n";
final Map<String, Command> commands = bot.getCommands(); // for (String key: commands.keySet())
for (Entry<String, Command> e : commands.entrySet()) // {
{ // help += "!" + key + ":"
help += "!" + e.getKey() + " ".repeat(10 - e.getKey().length()) + "- " + e.getValue().synopsis(); // }
}
invocation.respond(help); invocation.respond(help);
} }

View File

@ -21,7 +21,6 @@ import java.util.Queue;
import moe.nekojimi.chords.Downloader; import moe.nekojimi.chords.Downloader;
import moe.nekojimi.chords.Chords; import moe.nekojimi.chords.Chords;
import moe.nekojimi.chords.Track; import moe.nekojimi.chords.Track;
import moe.nekojimi.chords.TrackRequest;
public class QueueCommand extends Command public class QueueCommand extends Command
{ {
@ -49,34 +48,23 @@ public class QueueCommand extends Command
message += "__Ready to play:__\n"; message += "__Ready to play:__\n";
for (Track track : trackQueue) for (Track track : trackQueue)
{ {
message += ":bread: **" + i + ":** " + track + "\n"; message += ":bread: **" + (i) + ":** " + track + "\n";
i++; i++;
} }
} }
final List<TrackRequest> downloading = bot.getDownloader().getDownloadingTracks(); final List<Downloader.DownloadTask> downloadQueue = bot.getDownloader().getDownloadQueue();
if (!downloading.isEmpty())
{
message += "__Downloading:__\n";
for (TrackRequest request : downloading)
{
message += ":satellite: **" + (i) + ":** " + request + "\n";
i++;
}
}
final List<TrackRequest> downloadQueue = bot.getDownloader().getDownloadQueue();
if (!downloadQueue.isEmpty()) if (!downloadQueue.isEmpty())
{ {
message += "__In queue for download:__\n"; message += "__Downloading:__\n";
for (TrackRequest request : downloadQueue) for (Downloader.DownloadTask task : downloadQueue)
{ {
message += ":inbox_tray: **" + (i) + ":** " + request + "\n"; message += ":inbox_tray: **" + (i) + ":** " + task.getTrack() + "\n";
i++; i++;
} }
} }
if (downloading.isEmpty() && trackQueue.isEmpty() && downloadQueue.isEmpty()) if (downloadQueue.isEmpty() && trackQueue.isEmpty())
message += ":mailbox_with_no_mail: The track queue is empty."; message += ":mailbox_with_no_mail: The track queue is empty.";
// :inbox_tray: // :inbox_tray:
invocation.respond(message); invocation.respond(message);

View File

@ -29,7 +29,7 @@ public class SkipCommand extends Command
@Override @Override
public void call(Invocation invocation) public void call(Invocation invocation)
{ {
boolean ok = bot.getMusicHandler().skipTrack(); boolean ok = bot.getMusicHandler().nextTrack(true);
if (ok) if (ok)
invocation.respond("Skipped to next track!"); invocation.respond("Skipped to next track!");
else else