Stabilized playback, added playlist class, among other improvements.

This commit is contained in:
Andrew Lalis 2017-06-20 15:18:37 +02:00
parent 3bc2404b44
commit 4f97ce6e87
5 changed files with 265 additions and 32 deletions

View File

@ -21,7 +21,7 @@ public class HandieBot {
private static final String TOKEN = "MjgzNjUyOTg5MjEyNjg4Mzg0.C45A_Q.506b0G6my1FEFa7_YY39lxLBHUY";
private static IDiscordClient client;
public static IDiscordClient client;
private CommandHandler commandHandler;
private MusicPlayer musicPlayer;

View File

@ -35,10 +35,14 @@ public class CommandHandler {
String command = extractCommand(message);
String[] args = extractArgs(message);
if (guild != null && command != null){
DisappearingMessage.deleteMessageAfter(2000, message);
if (command.equals("play") && args.length == 1){
DisappearingMessage.deleteMessageAfter(1000, message);
if (command.equals("play")){
//Play or queue a song.
this.bot.getMusicPlayer().loadToQueue(guild, args[0]);
if (args.length == 1) {
this.bot.getMusicPlayer().loadToQueue(guild, args[0]);
} else if (args.length == 0){
this.bot.getMusicPlayer().playQueue(guild);
}
} else if (command.equals("skip") && args.length == 0){
//Skip the current song.
this.bot.getMusicPlayer().skipTrack(guild);
@ -53,6 +57,12 @@ public class CommandHandler {
//TODO implement repeat command.
} else if (command.equals("clear")){
//TODO clear command.
} else if (command.equals("quit")){
//Quit the application.
channel.sendMessage("Quitting HandieBot functions.");
this.bot.getMusicPlayer().quit(guild);
} else if (command.equals("playlist")){
//Do playlist actions.
}
}
}

View File

@ -100,12 +100,7 @@ public class MusicPlayer {
this.voiceChannels.put(guild, channels.get(0));
}
}
IVoiceChannel vc = this.voiceChannels.get(guild);
if (!vc.isConnected()){
System.out.println("Joined voice channel.");
vc.join();
}
return vc;
return this.voiceChannels.get(guild);
}
/**
@ -136,7 +131,8 @@ public class MusicPlayer {
}
builder.withTimestamp(System.currentTimeMillis());
builder.appendField("Showing " + (tracks.size() <= 10 ? tracks.size() : "the first 10") + " track"+(tracks.size() > 1 ? "s" : "")+".", sb.toString(), false);
getChatChannel(guild).sendMessage(builder.build());
IMessage message = getChatChannel(guild).sendMessage(builder.build());
DisappearingMessage.deleteMessageAfter(6000, message);
}
}
@ -182,11 +178,8 @@ public class MusicPlayer {
public void addToQueue(IGuild guild, AudioTrack track){
IVoiceChannel voiceChannel = getVoiceChannel(guild);
if (voiceChannel != null){
//Check to make sure sound can be played.
if (guild.getAudioManager().getAudioProvider() == null){
new DisappearingMessage(getChatChannel(guild), "Audio provider not set. Please try again.", 3000);
guild.getAudioManager().setAudioProvider(getMusicManager(guild).getAudioProvider());
return;
if (!voiceChannel.isConnected()) {
voiceChannel.join();
}
long timeUntilPlay = getMusicManager(guild).scheduler.getTimeUntilDone();
getMusicManager(guild).scheduler.queue(track);
@ -206,6 +199,17 @@ public class MusicPlayer {
}
/**
* If possible, try to begin playing from the track scheduler's queue.
*/
public void playQueue(IGuild guild){
IVoiceChannel vc = this.getVoiceChannel(guild);
if (!vc.isConnected()){
vc.join();
}
getMusicManager(guild).scheduler.nextTrack();
}
/**
* Skips the current track.
*/
@ -214,4 +218,16 @@ public class MusicPlayer {
new DisappearingMessage(getChatChannel(guild), "Skipping the current track.", 3000);
}
/**
* Stops playback and disconnects from the voice channel, to cease music actions.
* @param guild The guild to quit from.
*/
public void quit(IGuild guild){
getMusicManager(guild).scheduler.quit();
IVoiceChannel vc = this.getVoiceChannel(guild);
if (vc.isConnected()){
vc.leave();
}
}
}

View File

@ -0,0 +1,189 @@
package handiebot.lavaplayer;
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* @author Andrew Lalis
* A Playlist is a list of AudioTracks which a track scheduler can pull from to create a queue filled with songs. The
* playlist is persistent, i.e. it is saved into a file.
*/
public class Playlist {
private String name;
private long creatorUID;
List<AudioTrack> tracks;
/**
* Creates an empty playlist template.
* @param name The name of the playlist.
* @param creatorUID The ID of the user who created it.
*/
public Playlist(String name, long creatorUID){
this.name = name;
this.creatorUID = creatorUID;
this.tracks = new ArrayList<>();
}
/**
* Creates a playlist from a file with the given name.
* @param name The name of the file.
*/
public Playlist(String name){
this.name = name;
this.load();
}
public String getName(){
return this.name;
}
public long getCreatorUID(){
return this.creatorUID;
}
public List<AudioTrack> getTracks(){
return this.tracks;
}
/**
* Adds a track to the end of the playlist.
* @param track The track to add.
*/
public void addTrack(AudioTrack track){
this.tracks.add(track);
}
/**
* Removes a track from the playlist.
* @param track The track to remove.
*/
public void removeTrack(AudioTrack track){
this.tracks.remove(track);
}
/**
* Returns the next track, i.e. the first one in the list, and removes it from the internal list.
* @return The AudioTrack that should be played next.
*/
public AudioTrack getNextTrackAndRemove(boolean shouldShuffle){
return this.tracks.remove((shouldShuffle ? getShuffledIndex(this.tracks.size()) : 0));
}
/**
* Returns the next track to be played, and re-adds it to the end of the playlist, as it would do in a loop.
* @return
*/
public AudioTrack getNextTrackAndRequeue(boolean shouldShuffle){
AudioTrack track = this.tracks.remove((shouldShuffle ? getShuffledIndex(this.tracks.size()) : 0));
this.tracks.add(track);
return track;
}
/**
* Gets a 'shuffled index' from a given list length. That means:
* - A random number from 0 to (listLength-1) - threshold*(listLength), where threshold is some percentage of
* recent songs that should be ignored; for example, the most recent 20% of the playlist can be ignored.
* - A greater likelihood for numbers closer to 0 (those which have not been played in a while).
* @param listLength The number of items in a potential list to choose from.
* @return A pseudo-random choice as to which item to pick from the list.
*/
public static int getShuffledIndex(int listLength){
float threshold = 0.2f;
int trueLength = listLength - (int)threshold*listLength;
Random rand = new Random();
//TODO Add in a small gradient in chance for a song to be picked.
return rand.nextInt(trueLength);
}
/**
* Saves the playlist to a file in its name. The playlists are saved into a file in the user's home directory.
*/
public void save(){
String homeDir = System.getProperty("user.home");
File playlistDir = new File(homeDir+"/.handiebot/playlist");
if (!playlistDir.exists()){
if (!playlistDir.mkdirs()){
System.out.println("Unable to make directory: "+playlistDir.getPath());
return;
}
}
File playlistFile = new File(playlistDir.getPath()+"/"+this.name.replace(" ", "_")+".txt");
System.out.println("Saving playlist to: "+playlistFile.getAbsolutePath());
try(Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(playlistFile)))){
writer.write(this.name+'\n');
writer.write(Long.toString(this.creatorUID)+'\n');
writer.write(Integer.toString(this.tracks.size())+'\n');
for (AudioTrack track : this.tracks){
writer.write(track.getInfo().uri);
writer.write('\n');
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Loads the playlist from a file with the playlist's name.
*/
public void load(){//TODO Make load work!!!
String path = System.getProperty("user.home")+"/.handiebot/playlist/"+this.name.replace(" ", "_")+".txt";
File playlistFile = new File(path);
if (playlistFile.exists()){
try {
List<String> lines = Files.readAllLines(Paths.get(playlistFile.toURI()));
this.name = lines.remove(0);
this.creatorUID = Long.parseLong(lines.remove(0));
int trackCount = Integer.parseInt(lines.remove(0));
this.tracks = new ArrayList<>(trackCount);
AudioPlayerManager pm = new DefaultAudioPlayerManager();
for (int i = 0; i < trackCount; i++){
System.out.println("Loading item "+i);
String url = lines.remove(0);
pm.loadItem(url, new AudioLoadResultHandler() {
@Override
public void trackLoaded(AudioTrack audioTrack) {
System.out.println("Added track");
tracks.add(audioTrack);
}
@Override
public void playlistLoaded(AudioPlaylist audioPlaylist) {
System.out.println("Playlist loaded.");
//Do nothing. This should not happen.
}
@Override
public void noMatches() {
System.out.println("No matches for: "+url);
//Do nothing. This should not happen.
}
@Override
public void loadFailed(FriendlyException e) {
System.out.println("Load failed: "+e.getMessage());
//Do nothing. This should not happen.
}
});
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

View File

@ -8,10 +8,7 @@ import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason;
import sx.blah.discord.handle.obj.IChannel;
import sx.blah.discord.handle.obj.IGuild;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* @author Andrew Lalis
@ -19,9 +16,11 @@ import java.util.concurrent.LinkedBlockingQueue;
public class TrackScheduler extends AudioEventAdapter {
private final AudioPlayer player;
private final BlockingQueue<AudioTrack> queue;
private Playlist activePlaylist;
private boolean repeat = false;
private boolean shuffle = false;
private IGuild guild;
@ -32,7 +31,8 @@ public class TrackScheduler extends AudioEventAdapter {
public TrackScheduler(AudioPlayer player, IGuild guild){
this.player = player;
this.guild = guild;
this.queue = new LinkedBlockingQueue<>();
this.activePlaylist = new Playlist("HandieBot Active Playlist", 283652989212688384L);
//this.activePlaylist = new Playlist("HandieBot Active Playlist");
}
/**
@ -51,6 +51,22 @@ public class TrackScheduler extends AudioEventAdapter {
return this.repeat;
}
/**
* Sets whether or not to randomize the next track to be played.
* @param value True if shuffled should become active.
*/
public void setShuffle(boolean value){
this.shuffle = value;
}
/**
* Returns whether or not shuffling is active.
* @return True if shuffling is active, false otherwise.
*/
public boolean isShuffling(){
return this.shuffle;
}
/**
* Returns the time until the bot is done playing sound, at the current rate.
* @return The milliseconds until music stops.
@ -72,7 +88,7 @@ public class TrackScheduler extends AudioEventAdapter {
* @return A list of tracks in the queue.
*/
public List<AudioTrack> queueList(){
return new ArrayList<>(this.queue);
return this.activePlaylist.getTracks();
}
/**
@ -83,7 +99,8 @@ public class TrackScheduler extends AudioEventAdapter {
if (player.getPlayingTrack() == null){
player.startTrack(track, false);
} else {
queue.offer(track);
this.activePlaylist.addTrack(track);
this.activePlaylist.save();
}
}
@ -91,11 +108,16 @@ public class TrackScheduler extends AudioEventAdapter {
* Starts the next track, stopping the current one if it's playing.
*/
public void nextTrack(){
AudioTrack track = queue.poll();
AudioTrack track = (this.repeat ? this.activePlaylist.getNextTrackAndRequeue(this.shuffle) : this.activePlaylist.getNextTrackAndRemove(this.shuffle));
this.activePlaylist.save();
player.startTrack(track, false);
if (this.repeat){
this.queue.add(track);
}
}
/**
* If the user wishes to quit, stop the currently played track.
*/
public void quit(){
this.player.stopTrack();
}
@Override
@ -111,6 +133,7 @@ public class TrackScheduler extends AudioEventAdapter {
public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason) {
System.out.println("Track ended.");
if (endReason.mayStartNext){
System.out.println("Moving to next track.");
nextTrack();
} else {
System.out.println(endReason.toString());
@ -122,9 +145,4 @@ public class TrackScheduler extends AudioEventAdapter {
exception.printStackTrace();
}
@Override
public void onTrackStuck(AudioPlayer player, AudioTrack track, long thresholdMs) {
super.onTrackStuck(player, track, thresholdMs);
}
}