- final String GAPI = DiscordBot.tokens.getProperty("google"); //package final
+
+ final HttpInterfaceManager httpInterfaceManager; //package final since TrackScheduler will also access it
public ScheduledExecutorService ex = Executors.newScheduledThreadPool(2, new ThreadFactoryBuilder().setDaemon(true).setNameFormat("audio-scheduler-%d").build());
static class TrackData {
private String url;
private String uDis;
private String fDur;
private List<String> votes = new ArrayList<>();
public String getUrl() {
return url;
}
public String getUserWithDiscriminator() {
return uDis;
}
public String getFormattedDuration() {
return fDur;
}
public int voteSkip(User user, int req) {
if (votes.contains(user.getId()))
throw new UnsupportedOperationException("You have already voted!");
votes.add(user.getId());
if (votes.size() < req) {
return votes.size();
} else {
votes.clear();
return -1;
}
}
public TrackData(String url, User user, long durMillis) {
resFuture.complete(new CommandResult(CommandResultType.FAILURE, "There is already a pending playlist or livestream."));
return;
}
mm.vote(user.getUser(), "tracks", req); //self vote; should never return -1 (success) coz req > 1
channel.sendMessage("Due to the total duration of your requested tracks, it has been added to pending. It will be automatically removed if it has not been approved by the users in the channel for longer than 1 minute.\n" + "Others in the channel should use `!desp music approve` to vote.").queue();
}, (ex) -> resFuture.complete(ex.getStackTrace()[0].getMethodName().equals("readPlaylistName") ? new CommandResult(CommandResultType.FAILURE, "Cannot read the playlist specified. Is it private?") : new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(ex))));
exceptionally.accept(e); //so i dont lose my sanity over silenced errors
}
}
@Override
public void playlistLoaded(AudioPlaylist playlist) {
try {
if(playlist.getTracks().size() == 0) { //somehow its possible; do the same as noResult()
if(url.startsWith("ytsearch:")) load(user, url.replaceFirst("yt", "sc"), resultHandler, exceptionally); //searches in soundcloud, since apparently yt pretty frequently returns no result
else resultHandler.accept(null, new ArrayList<>());
tracks.add(0, tracks.remove(tracks.indexOf(playlist.getSelectedTrack()))); //shift selected track to first track
resultHandler.accept(playlist.getName(), tracks.stream().filter(t -> t != null).collect(Collectors.toList())); //only get first result if search
} catch(Exception e) {
exceptionally.accept(e); //so i dont lose my sanity over silenced errors
}
}
@Override
public void noMatches() {
if(url.startsWith("ytsearch:")) load(user, url.replaceFirst("yt", "sc"), resultHandler, exceptionally); //searches in soundcloud, since apparently yt pretty frequently returns no result
else resultHandler.accept(null, new ArrayList<>());
}
@Override
public void loadFailed(FriendlyException exception) {
public MessageBuilder queueCheck(Guild guild, int page) throws IllegalArgumentException {
GuildMusicManager mm = getGuildMusicManager(guild);
AudioTrack playing = mm.player.getPlayingTrack();
if(playing == null) return null;
MessageBuilder smsg = new MessageBuilder();
List<AudioTrack> tracks = mm.scheduler.findTracks(1, Integer.MAX_VALUE).stream().filter(a -> a != null).collect(Collectors.toList()); //get all tracks in queue
tracks.add(0, playing);
int maxPage = (int) Math.ceil(tracks.size() / 10f);
if(page > maxPage) throw new IllegalArgumentException("There is no such page.");
smsg.append("There is a total of `" + tracks.size() + "` tracks " + (mm.scheduler.loop.equals("loop") ? "looping" : "in autoplay") + ".\n\n");
} else {
long millis = 0;
for(AudioTrack track : tracks)
millis += track.getDuration();
smsg.append("There is a total of `" + tracks.size() + "` tracks queued" + ((millis == Long.MAX_VALUE || millis < 0) ? ".\n\n" : ", with a total duration of `" + MiscUtils.convertMillis(millis - playing.getPosition()) + "`.\n\n"));
} else { //remove autoplay queued track when disabling autoplay?
if (mm.scheduler.loop.equals("autoplay") && mm.scheduler.getQueueSize() == 1 && mm.scheduler.findTracks(1, 1).get(0).getUserData(TrackData.class).uDis.equals("Autoplay")) { //including autoplay, theres only 2 tracks; only remove tracks that is autoplayed
mm.scheduler.removeTrack(1);
}
mm.scheduler.loop = null;
return false;
}
}
public void stopAndClearQueue(Guild guild) {
GuildMusicManager mm = getGuildMusicManager(guild);
DiscordBot.lastMusicCmd.get(s).sendMessage("The music bot is going into maintenance and it will now disconnect. Sorry for the inconvenience.").queue();
}
});
musicManagers = null; //so further operations wont be possible even if i forgot to set this instance to null
* This class schedules tracks for the audio player. It contains the queue of
* tracks.
*/
public class TrackScheduler extends AudioEventAdapter {
private final AudioPlayer player;
private final BlockingQueue<AudioTrack> queue;
private AudioTrackHandler ap;
private Guild guild;
public String loop = null; //way too lazy to use getter setters lmao
/**
* @param player
* The audio player this scheduler uses
*/
public TrackScheduler(Guild parent, AudioTrackHandler handler, AudioPlayer player) {
this.player = player;
this.queue = new LinkedBlockingQueue<>();
this.ap = handler;
this.guild = parent;
}
/**
* Add the next track to queue or play right away if nothing is in the
* queue.
*
* @param track
* The track to play or add to queue.
*/
public void queue(AudioTrack track) {
// Calling startTrack with the noInterrupt set to true will start the track only if nothing is currently playing. If
// something is playing, it returns false and does nothing. In that case the player was already playing so this
// track goes to the queue instead.
if (!player.startTrack(track, true)) {
queue.offer(track);
} else if (loop != null && loop.equals("autoplay")) { //i dont think this is needed as people need to play something before autoplay can be toggled anyways
queueAutoplay(track);
}
}
/**
* Start the next track, stopping the current one if it is playing.
*/
public void nextTrack() { //DONE rewrite to not include q.remove here so that stuff like interrupted wont break the queue?
// Start the next track, regardless of if something is already playing or not. In case queue was empty, we are
// giving null to startTrack, which is a valid argument and will simply stop the player.
AudioTrack track = queue.poll();
player.startTrack(track, false);
if(track == null) {
//System.out.println("finished"); //debug
loop = null;
delayCloseConnection(player); //required because if not it will throw InterruptedException
}
} //seems to be called internally somehow; even when mayStartNext is false (REPLACED, STOPPED etc) this still fires
//NVM ITS CALLED FROM AudioTrackHandler.skipTrack() LOL
public void queueAutoplay(AudioTrack track) { //check duplicate please, some can get into a dead loop like cosMo@暴走P - WalpurgisNacht and Ice - 絶 //well randoming it works
ap.ex.submit(() -> { //async so it can free up the event
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (!HttpClientTools.isSuccessWithContent(statusCode)) {
+ throw new IOException("Invalid status code for search response: " + statusCode);
+ }
+
+ //start parsing
+ String data = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
+ data = data.substring(data.indexOf("window[\"ytInitialData\"] = ") + 26);
+
+ for (int i = 0; i < 5 && !data.startsWith("{"); i++) { //reconnect in case it doesnt fetch correctly; give up after 5 tries (should be more than enough)
+ Member user = guild.getMemberById(DiscordBot.BotID); //no need to use original track user as it would introduce performance overhead and other complications when leaving channels
+ ap.load(user, url, (n, l) -> {
+ AudioTrack auto = l.get(0);
+ auto.setUserData(new TrackData(auto.getInfo().uri, "Autoplay", auto.getDuration())); //change name to autoplay
+ ap.queueTracks(l, user); //no need sublist coz l is always a single video
+ }, ex -> {
+ ex.printStackTrace();
+ DiscordBot.lastMusicCmd.get(guild.getId()).sendMessage("Something went wrong when loading next autoplay track: " + ex.getMessage()).queue();
+ loop = null;
+ });
}
- final Member user = temp;
- int t = ThreadLocalRandom.current().nextInt(2);
- String url = "https://youtube.com/watch?v=" + new JSONObject(result).getJSONArray("items").getJSONObject(t).getJSONObject("id").getString("videoId");
- DiscordBot.lastMusicCmd.get(guild.getId()).sendMessage("Something went wrong when loading next autoplay track: " + ex.getMessage()).queue();
- loop = null;
- });
} catch (Exception e) {
e.printStackTrace();
DiscordBot.lastMusicCmd.get(guild.getId()).sendMessage("Something went wrong when loading next autoplay track. Is the last track a youtube video?").queue();
loop = null;
}
});
}
public List<AudioTrack> findTracks(int startIndex, int range) {
AudioTrack[] a = queue.toArray(new AudioTrack[queue.size()]);
int i = startIndex - 1 + range < 0 ? Integer.MAX_VALUE : startIndex - 1 + range; //prevent overflow
AudioTrack[] t = Arrays.copyOfRange(a, startIndex - 1, Math.min(queue.size() + 1, i)); //accounts for first track player thats not in queue
return Arrays.asList(t);
}
public AudioTrack removeTrack(int num) {
Iterator<AudioTrack> i = queue.iterator();
num = num - 1;
for (int times = 0; i.hasNext(); times++) {
AudioTrack removed = i.next();
if (num == times) {
i.remove();
return removed;
}
}
return null;
}
public AudioTrack moveTrack(int from, int to) {
List<AudioTrack> q = new ArrayList<>(Arrays.asList(queue.toArray(new AudioTrack[queue.size()])));
AudioTrack track = q.remove(from);
q.add(to, track);
synchronized (queue) { //obtain lock and operate before releasing or else queue might not be complete