tobby48 3 years ago
parent
commit
fdda1016c7

+ 47
- 0
src/main/java/kr/co/swh/lecture/opensource/project/discode/music/jda/AudioPlayerSendHandler.java View File

@@ -0,0 +1,47 @@
1
+package kr.co.swh.lecture.opensource.project.discode.music.jda; 
2
+
3
+import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
4
+import com.sedmelluq.discord.lavaplayer.track.playback.MutableAudioFrame;
5
+import java.nio.Buffer;
6
+import net.dv8tion.jda.api.audio.AudioSendHandler;
7
+
8
+import java.nio.ByteBuffer;
9
+
10
+/**
11
+ * This is a wrapper around AudioPlayer which makes it behave as an AudioSendHandler for JDA. As JDA calls canProvide
12
+ * before every call to provide20MsAudio(), we pull the frame in canProvide() and use the frame we already pulled in
13
+ * provide20MsAudio().
14
+ */
15
+public class AudioPlayerSendHandler implements AudioSendHandler {
16
+	private final AudioPlayer audioPlayer;
17
+	private final ByteBuffer buffer;
18
+	private final MutableAudioFrame frame;
19
+
20
+	/**
21
+	 * @param audioPlayer Audio player to wrap.
22
+	 */
23
+	public AudioPlayerSendHandler(AudioPlayer audioPlayer) {
24
+		this.audioPlayer = audioPlayer;
25
+		this.buffer = ByteBuffer.allocate(1024);
26
+		this.frame = new MutableAudioFrame();
27
+		this.frame.setBuffer(buffer);
28
+	}
29
+
30
+	@Override
31
+	public boolean canProvide() {
32
+		// returns true if audio was provided
33
+		return audioPlayer.provide(frame);
34
+	}
35
+
36
+	@Override
37
+	public ByteBuffer provide20MsAudio() {
38
+		// flip to make it a read buffer
39
+		((Buffer) buffer).flip();
40
+		return buffer;
41
+	}
42
+
43
+	@Override
44
+	public boolean isOpus() {
45
+		return true;
46
+	}
47
+}

+ 35
- 0
src/main/java/kr/co/swh/lecture/opensource/project/discode/music/jda/GuildMusicManager.java View File

@@ -0,0 +1,35 @@
1
+package kr.co.swh.lecture.opensource.project.discode.music.jda; 
2
+
3
+import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
4
+import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
5
+
6
+/**
7
+ * Holder for both the player and a track scheduler for one guild.
8
+ */
9
+public class GuildMusicManager {
10
+	/**
11
+	 * Audio player for the guild.
12
+	 */
13
+	public final AudioPlayer player;
14
+	/**
15
+	 * Track scheduler for the player.
16
+	 */
17
+	public final TrackScheduler scheduler;
18
+
19
+	/**
20
+	 * Creates a player and a track scheduler.
21
+	 * @param manager Audio player manager to use for creating the player.
22
+	 */
23
+	public GuildMusicManager(AudioPlayerManager manager) {
24
+		player = manager.createPlayer();
25
+		scheduler = new TrackScheduler(player);
26
+		player.addListener(scheduler);
27
+	}
28
+
29
+	/**
30
+	 * @return Wrapper around AudioPlayer to use it as an AudioSendHandler.
31
+	 */
32
+	public AudioPlayerSendHandler getSendHandler() {
33
+		return new AudioPlayerSendHandler(player);
34
+	}
35
+}

+ 137
- 0
src/main/java/kr/co/swh/lecture/opensource/project/discode/music/jda/Main.java View File

@@ -0,0 +1,137 @@
1
+package kr.co.swh.lecture.opensource.project.discode.music.jda; 
2
+
3
+import org.apache.log4j.Logger;
4
+
5
+import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
6
+import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
7
+import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager;
8
+import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers;
9
+import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
10
+import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
11
+import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
12
+
13
+import kr.co.swh.lecture.opensource.project.discode.music.YoutubeMusicListener;
14
+import net.dv8tion.jda.api.JDA;
15
+import net.dv8tion.jda.api.JDABuilder;
16
+import net.dv8tion.jda.api.entities.Guild;
17
+import net.dv8tion.jda.api.entities.TextChannel;
18
+import net.dv8tion.jda.api.entities.VoiceChannel;
19
+import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent;
20
+import net.dv8tion.jda.api.hooks.ListenerAdapter;
21
+import net.dv8tion.jda.api.managers.AudioManager;
22
+
23
+import java.util.HashMap;
24
+import java.util.Map;
25
+
26
+import static net.dv8tion.jda.api.requests.GatewayIntent.GUILD_MESSAGES;
27
+import static net.dv8tion.jda.api.requests.GatewayIntent.GUILD_VOICE_STATES;
28
+
29
+public class Main extends ListenerAdapter {
30
+	public static void main(String[] args) throws Exception {
31
+//		JDABuilder.create(System.getProperty("NjYzMjgzODYxMTUyNTk1OTkz.XhGROw.Ps3JJIKlEXoE1eah_5OKHwViThY"), GUILD_MESSAGES, GUILD_VOICE_STATES)
32
+//		.addEventListeners(new Main())
33
+//		.build();
34
+		 JDA jda = JDABuilder.createDefault("NjYzMjgzODYxMTUyNTk1OTkz.XhGROw.Ps3JJIKlEXoE1eah_5OKHwViThY")
35
+		            .addEventListeners(new Main())
36
+		            .build();
37
+
38
+		        // optionally block until JDA is ready
39
+		        jda.awaitReady();
40
+	}
41
+
42
+	private final AudioPlayerManager playerManager;
43
+	private final Map<Long, GuildMusicManager> musicManagers;
44
+
45
+	private Main() {
46
+		this.musicManagers = new HashMap<>();
47
+
48
+		this.playerManager = new DefaultAudioPlayerManager();
49
+		AudioSourceManagers.registerRemoteSources(playerManager);
50
+		AudioSourceManagers.registerLocalSource(playerManager);
51
+	}
52
+
53
+	private synchronized GuildMusicManager getGuildAudioPlayer(Guild guild) {
54
+		long guildId = Long.parseLong(guild.getId());
55
+		GuildMusicManager musicManager = musicManagers.get(guildId);
56
+
57
+		if (musicManager == null) {
58
+			musicManager = new GuildMusicManager(playerManager);
59
+			musicManagers.put(guildId, musicManager);
60
+		}
61
+
62
+		guild.getAudioManager().setSendingHandler(musicManager.getSendHandler());
63
+
64
+		return musicManager;
65
+	}
66
+
67
+	@Override
68
+	public void onGuildMessageReceived(GuildMessageReceivedEvent event) {
69
+		String[] command = event.getMessage().getContentRaw().split(" ", 2);
70
+
71
+		if ("~play".equals(command[0]) && command.length == 2) {
72
+			loadAndPlay(event.getChannel(), command[1]);
73
+		} else if ("~skip".equals(command[0])) {
74
+			skipTrack(event.getChannel());
75
+		}
76
+
77
+		super.onGuildMessageReceived(event);
78
+	}
79
+
80
+	private void loadAndPlay(final TextChannel channel, final String trackUrl) {
81
+		GuildMusicManager musicManager = getGuildAudioPlayer(channel.getGuild());
82
+
83
+		playerManager.loadItemOrdered(musicManager, trackUrl, new AudioLoadResultHandler() {
84
+			@Override
85
+			public void trackLoaded(AudioTrack track) {
86
+				channel.sendMessage("Adding to queue " + track.getInfo().title).queue();
87
+
88
+				play(channel.getGuild(), musicManager, track);
89
+			}
90
+
91
+			@Override
92
+			public void playlistLoaded(AudioPlaylist playlist) {
93
+				AudioTrack firstTrack = playlist.getSelectedTrack();
94
+
95
+				if (firstTrack == null) {
96
+					firstTrack = playlist.getTracks().get(0);
97
+				}
98
+
99
+				channel.sendMessage("Adding to queue " + firstTrack.getInfo().title + " (first track of playlist " + playlist.getName() + ")").queue();
100
+
101
+				play(channel.getGuild(), musicManager, firstTrack);
102
+			}
103
+
104
+			@Override
105
+			public void noMatches() {
106
+				channel.sendMessage("Nothing found by " + trackUrl).queue();
107
+			}
108
+
109
+			@Override
110
+			public void loadFailed(FriendlyException exception) {
111
+				channel.sendMessage("Could not play: " + exception.getMessage()).queue();
112
+			}
113
+		});
114
+	}
115
+
116
+	private void play(Guild guild, GuildMusicManager musicManager, AudioTrack track) {
117
+		connectToFirstVoiceChannel(guild.getAudioManager());
118
+
119
+		musicManager.scheduler.queue(track);
120
+	}
121
+
122
+	private void skipTrack(TextChannel channel) {
123
+		GuildMusicManager musicManager = getGuildAudioPlayer(channel.getGuild());
124
+		musicManager.scheduler.nextTrack();
125
+
126
+		channel.sendMessage("Skipped to next track.").queue();
127
+	}
128
+
129
+	private static void connectToFirstVoiceChannel(AudioManager audioManager) {
130
+		if (!audioManager.isConnected() && !audioManager.isAttemptingToConnect()) {
131
+			for (VoiceChannel voiceChannel : audioManager.getGuild().getVoiceChannels()) {
132
+				audioManager.openAudioConnection(voiceChannel);
133
+				break;
134
+			}
135
+		}
136
+	}
137
+}

+ 56
- 0
src/main/java/kr/co/swh/lecture/opensource/project/discode/music/jda/TrackScheduler.java View File

@@ -0,0 +1,56 @@
1
+package kr.co.swh.lecture.opensource.project.discode.music.jda; 
2
+
3
+import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
4
+import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter;
5
+import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
6
+import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason;
7
+
8
+import java.util.concurrent.BlockingQueue;
9
+import java.util.concurrent.LinkedBlockingQueue;
10
+
11
+/**
12
+ * This class schedules tracks for the audio player. It contains the queue of tracks.
13
+ */
14
+public class TrackScheduler extends AudioEventAdapter {
15
+	private final AudioPlayer player;
16
+	private final BlockingQueue<AudioTrack> queue;
17
+
18
+	/**
19
+	 * @param player The audio player this scheduler uses
20
+	 */
21
+	public TrackScheduler(AudioPlayer player) {
22
+		this.player = player;
23
+		this.queue = new LinkedBlockingQueue<>();
24
+	}
25
+
26
+	/**
27
+	 * Add the next track to queue or play right away if nothing is in the queue.
28
+	 *
29
+	 * @param track The track to play or add to queue.
30
+	 */
31
+	public void queue(AudioTrack track) {
32
+		// Calling startTrack with the noInterrupt set to true will start the track only if nothing is currently playing. If
33
+		// something is playing, it returns false and does nothing. In that case the player was already playing so this
34
+		// track goes to the queue instead.
35
+		if (!player.startTrack(track, true)) {
36
+			queue.offer(track);
37
+		}
38
+	}
39
+
40
+	/**
41
+	 * Start the next track, stopping the current one if it is playing.
42
+	 */
43
+	public void nextTrack() {
44
+		// Start the next track, regardless of if something is already playing or not. In case queue was empty, we are
45
+		// giving null to startTrack, which is a valid argument and will simply stop the player.
46
+		player.startTrack(queue.poll(), false);
47
+	}
48
+
49
+	@Override
50
+	public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason) {
51
+		// Only start the next track if the end reason is suitable for it (FINISHED or LOAD_FAILED)
52
+		if (endReason.mayStartNext) {
53
+			nextTrack();
54
+		}
55
+	}
56
+}