Implement multithreaded downloading (Downloader class)
This commit is contained in:
parent
2f7bb13dc6
commit
c8810ca271
|
@ -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…
Reference in New Issue