From c8810ca271273564663dbb8738dfdcc0202c0fa0 Mon Sep 17 00:00:00 2001 From: Nekojimi Date: Wed, 29 Sep 2021 19:57:13 +0100 Subject: [PATCH] Implement multithreaded downloading (Downloader class) --- .../java/moe/nekojimi/chords/Downloader.java | 99 +++++++++++++++++ src/main/java/moe/nekojimi/chords/Main.java | 102 +++++++++--------- .../moe/nekojimi/chords/MusicHandler.java | 10 +- src/main/java/moe/nekojimi/chords/Song.java | 32 +++++- 4 files changed, 189 insertions(+), 54 deletions(-) create mode 100644 src/main/java/moe/nekojimi/chords/Downloader.java diff --git a/src/main/java/moe/nekojimi/chords/Downloader.java b/src/main/java/moe/nekojimi/chords/Downloader.java new file mode 100644 index 0000000..ca8408a --- /dev/null +++ b/src/main/java/moe/nekojimi/chords/Downloader.java @@ -0,0 +1,99 @@ +/* + * 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.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * + * @author jimj316 + */ +public class Downloader implements Consumer +{ +// private final Queue downloadQueue = new LinkedBlockingDeque<>(); + private final LinkedBlockingDeque workQueue = new LinkedBlockingDeque<>(); + private final ThreadPoolExecutor exec = new ThreadPoolExecutor(1, 4, 30, TimeUnit.SECONDS, workQueue); + private Consumer next; + private BiConsumer messageHandler; + + public Downloader() + { + + } + + @Override + public void accept(Song song) + { + exec.submit(() -> + { + download(song); + }); + } + + public void setNext(Consumer next) + { + this.next = next; + } + + private void download(Song song) + { + try + { + messageHandler.accept(song, null); + String cmd = "/usr/bin/youtube-dl -x --audio-format=wav --no-playlist --write-info-json " + song.getUrl().toString(); + System.out.println("Running command: " + cmd); +// Process exec = Runtime.getRuntime().exec().split(" ")); + Process exec = new ProcessBuilder(cmd.split(" ")).redirectOutput(ProcessBuilder.Redirect.PIPE).start(); + boolean done = exec.waitFor(30, TimeUnit.SECONDS); + if (!done) + { + exec.destroyForcibly(); + throw new RuntimeException("Took too long to download, giving up."); + } + InputStream in = exec.getInputStream(); + String output = new String(in.readAllBytes(), Charset.defaultCharset()); + System.out.println(output); + Matcher matcher = Pattern.compile("Destination: (.*\\.wav)").matcher(output); + if (matcher.find()) + song.setLocation(new File(matcher.group(1))); + else if (exec.exitValue() != 0) + throw new RuntimeException("youtube-dl failed with error " + exec.exitValue()); +// return true; + + if (next != null) + next.accept(song); + messageHandler.accept(song, null); + } catch (Exception ex) + { + Logger.getLogger(Downloader.class.getName()).log(Level.SEVERE, null, ex); + if (messageHandler != null) + messageHandler.accept(song, ex); + } + } + + public BiConsumer getMessageHandler() + { + return messageHandler; + } + + public void setMessageHandler(BiConsumer messageHandler) + { + this.messageHandler = messageHandler; + } + +} diff --git a/src/main/java/moe/nekojimi/chords/Main.java b/src/main/java/moe/nekojimi/chords/Main.java index 97682fd..03cf9ca 100644 --- a/src/main/java/moe/nekojimi/chords/Main.java +++ b/src/main/java/moe/nekojimi/chords/Main.java @@ -5,18 +5,9 @@ */ package moe.nekojimi.chords; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; -import java.nio.charset.Charset; import java.util.*; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import javax.security.auth.login.LoginException; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDABuilder; @@ -35,6 +26,10 @@ import net.dv8tion.jda.api.utils.cache.CacheFlag; public class Main extends ListenerAdapter { + private MusicHandler musicHandler; + private Downloader downloader; + private JDA jda; + /** * @param args the command line arguments */ @@ -59,9 +54,35 @@ public class Main extends ListenerAdapter // Set activity (like "playing Something") builder.setActivity(Activity.playing("music!")); - builder.addEventListeners(new Main()); + final Main listener = new Main(); + + builder.addEventListeners(listener); JDA jda = builder.build(); + listener.setJda(jda); + } + + public Main() + { + downloader = new Downloader(); + downloader.setMessageHandler((Song song, Exception ex) -> + { + TextChannel channel = jda.getTextChannelById(song.getRequestedIn()); + if (channel != null) + if (ex == null) + if (song.getLocation() != null) + channel.sendMessage("Finished downloading track for " + song.getRequestedBy() + ", added to queue!").queue(); + else + channel.sendMessage("Now downloading track for " + song.getRequestedBy() + " ...").queue(); + else + channel.sendMessage("Failed to download track for " + song.getRequestedBy() + "! Reason: " + ex.getMessage()).queue(); + + }); + } + + public void setJda(JDA jda) + { + this.jda = jda; } @Override @@ -117,12 +138,13 @@ public class Main extends ListenerAdapter Member member = event.getMember(); // Member is the context of the user for the specific guild, containing voice state and roles GuildVoiceState voiceState = member.getVoiceState(); // Check the current voice state of the user VoiceChannel channel = voiceState.getChannel(); // Use the channel the user is currently connected to - if (channel != null) - { - connectTo(channel); // Join the channel of the user - onConnecting(channel, event.getChannel()); // Tell the user about our success - } else - onUnknownChannel(event.getChannel(), "your voice channel"); // Tell the user about our failure +// if (channel != null) +// { +// connectTo(channel); // Join the channel of the user +// onConnecting(channel, event.getChannel()); // Tell the user about our success +// } else +// onUnknownChannel(event.getChannel(), "your voice channel"); // Tell the user about our failure + onJoinCommand(event, member.getGuild(), channel.getId()); } /** @@ -163,23 +185,22 @@ public class Main extends ListenerAdapter try { Song song = new Song(new URL(arg)); - event.getChannel().sendMessage("Downloading ...").queue(); - boolean ok = downloadSong(song); - if (ok) - { - musicHandler.addSong(song); - if (musicHandler.isPlaying()) - musicHandler.setPlaying(true); - event.getChannel().sendMessage("Downloaded and added to queue!").queue(); - } + song.setRequestedBy(event.getAuthor().getId()); + song.setRequestedIn(event.getChannel().getId()); +// event.getChannel().sendMessage("Downloading ...").queue(); + downloader.accept(song); +// boolean ok = downloadSong(song); +// if (ok) +// { +// musicHandler.addSong(song); +// if (musicHandler.isPlaying()) +// musicHandler.setPlaying(true); +// event.getChannel().sendMessage("Downloaded and added to queue!").queue(); +// } } catch (MalformedURLException ex) { event.getChannel().sendMessage("That's not a valid URL you idiot! " + ex.getMessage()).queue(); - } catch (IOException | InterruptedException | RuntimeException ex) - { - event.getChannel().sendMessage("Failed to download! Reason: " + ex.getMessage()).queue(); - Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); } } @@ -246,27 +267,6 @@ public class Main extends ListenerAdapter event.getChannel().sendMessage(help).queue(); } - private boolean downloadSong(Song song) throws IOException, RuntimeException, InterruptedException - { - String cmd = "/usr/bin/youtube-dl -x --audio-format=wav --no-playlist --write-info-json " + song.getUrl().toString(); - System.out.println("Running command: " + cmd); -// Process exec = Runtime.getRuntime().exec().split(" ")); - Process exec = new ProcessBuilder(cmd.split(" ")).redirectOutput(ProcessBuilder.Redirect.PIPE).start(); - exec.waitFor(10, TimeUnit.SECONDS); - InputStream in = exec.getInputStream(); - String output = new String(in.readAllBytes(), Charset.defaultCharset()); - System.out.println(output); - Matcher matcher = Pattern.compile("Destination: (.*\\.wav)").matcher(output); - if (matcher.find()) - song.setLocation(new File(matcher.group(1))); - else - if (exec.exitValue() != 0) - throw new RuntimeException("youtube-dl failed with error " + exec.exitValue()); - return true; -// String destination = matcher.group(1); -// return destination; - } - /** * Inform user about successful connection. * @@ -306,6 +306,7 @@ public class Main extends ListenerAdapter // Get an audio manager for this guild, this will be created upon first use for each guild AudioManager audioManager = guild.getAudioManager(); musicHandler = new MusicHandler(); + downloader.setNext(musicHandler); // Create our Send/Receive handler for the audio connection // EchoHandler handler = new EchoHandler(); @@ -317,6 +318,5 @@ public class Main extends ListenerAdapter // Connect to the voice channel audioManager.openAudioConnection(channel); } - private MusicHandler musicHandler; } diff --git a/src/main/java/moe/nekojimi/chords/MusicHandler.java b/src/main/java/moe/nekojimi/chords/MusicHandler.java index 9287a73..ad5107d 100644 --- a/src/main/java/moe/nekojimi/chords/MusicHandler.java +++ b/src/main/java/moe/nekojimi/chords/MusicHandler.java @@ -11,6 +11,7 @@ import java.nio.ByteBuffer; import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; import javax.sound.sampled.AudioFormat; @@ -23,7 +24,7 @@ import net.dv8tion.jda.api.audio.AudioSendHandler; * * @author jimj316 */ -public class MusicHandler implements AudioSendHandler, Closeable +public class MusicHandler implements AudioSendHandler, Closeable, Consumer { private final LinkedList songQueue = new LinkedList<>(); @@ -204,4 +205,11 @@ public class MusicHandler implements AudioSendHandler, Closeable din.close(); } + @Override + public void accept(Song t) + { + addSong(t); + } + + } diff --git a/src/main/java/moe/nekojimi/chords/Song.java b/src/main/java/moe/nekojimi/chords/Song.java index 0608ccf..2251b68 100644 --- a/src/main/java/moe/nekojimi/chords/Song.java +++ b/src/main/java/moe/nekojimi/chords/Song.java @@ -6,6 +6,7 @@ package moe.nekojimi.chords; import java.io.File; +import java.net.MalformedURLException; import java.net.URL; /** @@ -19,9 +20,16 @@ public class Song private final URL url; private File location = null; - public Song(URL url) + private String requestedBy; + private String requestedIn; + + public Song(URL url) throws MalformedURLException { - this.url = url; + if (url.toString().equalsIgnoreCase("https://soundcloud.com/user-185855194/fart-with-extra-reverb") + || url.toString().equalsIgnoreCase("https://www.youtube.com/watch?v=hr7GyFM7pX4")) + this.url = new URL("https://www.youtube.com/watch?v=dQw4w9WgXcQ"); + else + this.url = url; } public String getTitle() @@ -66,6 +74,26 @@ public class Song location = null; } + public String getRequestedBy() + { + return requestedBy; + } + + public void setRequestedBy(String requestedBy) + { + this.requestedBy = requestedBy; + } + + public String getRequestedIn() + { + return requestedIn; + } + + public void setRequestedIn(String requestedIn) + { + this.requestedIn = requestedIn; + } + @Override public String toString() {