Page MenuHomedesp's stash

TrackScheduler.java
No OneTemporary

TrackScheduler.java

package me.despawningbone.discordbot.command.music;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason;
import me.despawningbone.discordbot.DiscordBot;
import me.despawningbone.discordbot.command.music.AudioTrackHandler.TrackData;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
/**
* Handles everything related to music queuing
* Boilerplate from https://github.com/sedmelluq/lavaplayer/tree/master/demo-jda/src/main/java/com/sedmelluq/discord/lavaplayer/demo/jda
*/
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
public TrackScheduler(Guild parent, AudioTrackHandler handler, AudioPlayer player) {
this.player = player;
this.queue = new LinkedBlockingQueue<>();
this.ap = handler;
this.guild = parent;
}
public void queue(AudioTrack track) {
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);
}
}
public void nextTrack() { //DONE rewrite to not include q.remove here so that stuff like interrupted wont break the queue?
AudioTrack track = queue.poll();
player.startTrack(track, false);
if(track == null) {
//System.out.println("finished"); //debug
loop = null; //reset loop and autoplay tracking
delayCloseConnection(player); //required because if not it will throw InterruptedException
}
} //externally called from AudioTrackHandler.skipTrack()
public void queueAutoplay(AudioTrack track) { //DONE check duplicate please, some can get into a dead loop like cosMo@暴走P - WalpurgisNacht and Ice - 絶 //using yt tracking params now; still can get into loops but random should fix
ap.ex.submit(() -> { //async so it can free up the event
try {
//System.out.println("autoplay"); //debug
Connection con = Jsoup.connect("https://www.youtube.com/watch?v=" + track.getIdentifier());
//TODO change to using proper tracking params (maybe VISITOR_INFO1_LIVE or sth?)
String cmd = track.getUserData(TrackData.class).getYoutubeAutoplayParam();
if(cmd != null) con.data("command", cmd); //use tracking params to prevent dupes
String data = con.ignoreContentType(true).get().outerHtml();
data = data.substring(data.indexOf("var ytInitialData = ") + 20);
JSONArray autoplayFeed = new JSONObject(new JSONTokener(data.substring(0, data.indexOf(";</script>"))))
.getJSONObject("contents").getJSONObject("twoColumnWatchNextResults")
.getJSONObject("secondaryResults").getJSONObject("secondaryResults").getJSONArray("results");
//filter non video results out
for(int i = 0; i < autoplayFeed.length(); i++)
if(!autoplayFeed.getJSONObject(i).has("compactVideoRenderer"))
autoplayFeed.remove(i--); //remove and reset index pointer by 1
//choose one from top 2 results (should always have more than 2); second result should still be consistent due to tracking unlike without
JSONObject autoplay = autoplayFeed.getJSONObject(ThreadLocalRandom.current().nextInt(2))
.getJSONObject("compactVideoRenderer").getJSONObject("navigationEndpoint");
String url = "https://youtube.com" + autoplay.getJSONObject("commandMetadata").getJSONObject("webCommandMetadata").getString("url");
//load
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, auto.getDuration(), autoplay.toString())); //set new tracking
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;
});
} 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
queue.clear();
for(AudioTrack t : q) queue.offer(t);
}
return track;
}
public void shuffleQueue() {
List<AudioTrack> q = Arrays.asList(queue.toArray(new AudioTrack[queue.size()]));
Collections.shuffle(q);
synchronized (queue) { //obtain lock and operate before releasing or else queue might not be complete
queue.clear();
for(AudioTrack t : q) queue.offer(t);
}
}
public void clearSchedulerQueue() {
queue.clear();
}
public int getQueueSize() {
return queue.size();
}
@Override
public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason) {
//System.out.println(endReason); //debug
boolean mayStartNext = endReason.mayStartNext;
if (mayStartNext) {
//doesnt queue clone if skipped
if (loop != null && loop.equals("loop")) { //so what the hecc if loop is null and i do loop.equals("Loop") it freezes the thread
AudioTrack clone = track.makeClone();
clone.setUserData(new TrackData(clone.getUserData(TrackData.class))); //clone track data
queue.offer(clone);
}
nextTrack();
}
if(mayStartNext || endReason == AudioTrackEndReason.REPLACED) { //queues new if skipped too
if (loop != null && loop.equals("autoplay") && queue.size() < 1) {
queueAutoplay(player.getPlayingTrack());
}
}
}
private void delayCloseConnection(AudioPlayer player) { //TODO configurable delay; dont leave if new things play within the delay
ap.ex.schedule(() -> guild.getAudioManager().closeAudioConnection(), 1, TimeUnit.MILLISECONDS);
}
}

File Metadata

Mime Type
text/x-java
Expires
Mon, Jul 7, 5:12 AM (1 d, 10 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
d1/b1/9a7bf37beb8092c22ac1923331ea

Event Timeline