Implement multithreaded downloading (Downloader class)

crossfading
Nekojimi 3 years ago
parent 2f7bb13dc6
commit c8810ca271
  1. 99
      src/main/java/moe/nekojimi/chords/Downloader.java
  2. 102
      src/main/java/moe/nekojimi/chords/Main.java
  3. 10
      src/main/java/moe/nekojimi/chords/MusicHandler.java
  4. 30
      src/main/java/moe/nekojimi/chords/Song.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<Song>
{
// private final Queue<Song> downloadQueue = new LinkedBlockingDeque<>();
private final LinkedBlockingDeque<Runnable> workQueue = new LinkedBlockingDeque<>();
private final ThreadPoolExecutor exec = new ThreadPoolExecutor(1, 4, 30, TimeUnit.SECONDS, workQueue);
private Consumer<Song> next;
private BiConsumer<Song, Exception> messageHandler;
public Downloader()
{
}
@Override
public void accept(Song song)
{
exec.submit(() ->
{
download(song);
});
}
public void setNext(Consumer<Song> 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<Song, Exception> getMessageHandler()
{
return messageHandler;
}
public void setMessageHandler(BiConsumer<Song, Exception> messageHandler)
{
this.messageHandler = messageHandler;
}
}

@ -5,18 +5,9 @@
*/ */
package moe.nekojimi.chords; package moe.nekojimi.chords;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.nio.charset.Charset;
import java.util.*; 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 javax.security.auth.login.LoginException;
import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder; import net.dv8tion.jda.api.JDABuilder;
@ -35,6 +26,10 @@ import net.dv8tion.jda.api.utils.cache.CacheFlag;
public class Main extends ListenerAdapter public class Main extends ListenerAdapter
{ {
private MusicHandler musicHandler;
private Downloader downloader;
private JDA jda;
/** /**
* @param args the command line arguments * @param args the command line arguments
*/ */
@ -59,9 +54,35 @@ public class Main extends ListenerAdapter
// Set activity (like "playing Something") // Set activity (like "playing Something")
builder.setActivity(Activity.playing("music!")); builder.setActivity(Activity.playing("music!"));
builder.addEventListeners(new Main()); final Main listener = new Main();
builder.addEventListeners(listener);
JDA jda = builder.build(); 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 @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 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 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 VoiceChannel channel = voiceState.getChannel(); // Use the channel the user is currently connected to
if (channel != null) // if (channel != null)
{ // {
connectTo(channel); // Join the channel of the user // connectTo(channel); // Join the channel of the user
onConnecting(channel, event.getChannel()); // Tell the user about our success // onConnecting(channel, event.getChannel()); // Tell the user about our success
} else // } else
onUnknownChannel(event.getChannel(), "your voice channel"); // Tell the user about our failure // 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 try
{ {
Song song = new Song(new URL(arg)); Song song = new Song(new URL(arg));
event.getChannel().sendMessage("Downloading ...").queue(); song.setRequestedBy(event.getAuthor().getId());
boolean ok = downloadSong(song); song.setRequestedIn(event.getChannel().getId());
if (ok) // event.getChannel().sendMessage("Downloading ...").queue();
{ downloader.accept(song);
musicHandler.addSong(song); // boolean ok = downloadSong(song);
if (musicHandler.isPlaying()) // if (ok)
musicHandler.setPlaying(true); // {
event.getChannel().sendMessage("Downloaded and added to queue!").queue(); // musicHandler.addSong(song);
} // if (musicHandler.isPlaying())
// musicHandler.setPlaying(true);
// event.getChannel().sendMessage("Downloaded and added to queue!").queue();
// }
} catch (MalformedURLException ex) } catch (MalformedURLException ex)
{ {
event.getChannel().sendMessage("That's not a valid URL you idiot! " + ex.getMessage()).queue(); 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(); 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. * 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 // Get an audio manager for this guild, this will be created upon first use for each guild
AudioManager audioManager = guild.getAudioManager(); AudioManager audioManager = guild.getAudioManager();
musicHandler = new MusicHandler(); musicHandler = new MusicHandler();
downloader.setNext(musicHandler);
// Create our Send/Receive handler for the audio connection // Create our Send/Receive handler for the audio connection
// EchoHandler handler = new EchoHandler(); // EchoHandler handler = new EchoHandler();
@ -317,6 +318,5 @@ public class Main extends ListenerAdapter
// Connect to the voice channel // Connect to the voice channel
audioManager.openAudioConnection(channel); audioManager.openAudioConnection(channel);
} }
private MusicHandler musicHandler;
} }

@ -11,6 +11,7 @@ import java.nio.ByteBuffer;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
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 javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioFormat;
@ -23,7 +24,7 @@ import net.dv8tion.jda.api.audio.AudioSendHandler;
* *
* @author jimj316 * @author jimj316
*/ */
public class MusicHandler implements AudioSendHandler, Closeable public class MusicHandler implements AudioSendHandler, Closeable, Consumer<Song>
{ {
private final LinkedList<Song> songQueue = new LinkedList<>(); private final LinkedList<Song> songQueue = new LinkedList<>();
@ -204,4 +205,11 @@ public class MusicHandler implements AudioSendHandler, Closeable
din.close(); din.close();
} }
@Override
public void accept(Song t)
{
addSong(t);
}
} }

@ -6,6 +6,7 @@
package moe.nekojimi.chords; package moe.nekojimi.chords;
import java.io.File; import java.io.File;
import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
/** /**
@ -19,8 +20,15 @@ public class Song
private final URL url; private final URL url;
private File location = null; private File location = null;
public Song(URL url) private String requestedBy;
private String requestedIn;
public Song(URL url) throws MalformedURLException
{ {
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; this.url = url;
} }
@ -66,6 +74,26 @@ public class Song
location = null; 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 @Override
public String toString() public String toString()
{ {

Loading…
Cancel
Save