From 54873d85a6125ee7c345adbf4346793c0b48b548 Mon Sep 17 00:00:00 2001 From: Anthony Date: Sun, 4 Mar 2018 21:50:23 -0500 Subject: [PATCH] Implemented basic music playing --- .../command/music/PlayCommand.java | 68 ++++++++++++++++++ .../command/music/StopCommand.java | 10 +++ .../command/music/VolumeCommand.java | 70 +++++++++++++++++++ .../java/netneutrality/http/WebModule.java | 36 ++++++++-- .../music/AudioPlayerSendHandler.java | 54 ++++++++++++++ .../netneutrality/music/MusicManager.java | 48 +++++++++++++ .../netneutrality/music/TrackScheduler.java | 24 ++++++- .../netneutrality/utils/YoutubeClass.java | 35 ++++++++++ 8 files changed, 339 insertions(+), 6 deletions(-) create mode 100644 src/main/java/netneutrality/command/music/PlayCommand.java create mode 100644 src/main/java/netneutrality/command/music/StopCommand.java create mode 100644 src/main/java/netneutrality/command/music/VolumeCommand.java create mode 100644 src/main/java/netneutrality/music/AudioPlayerSendHandler.java create mode 100644 src/main/java/netneutrality/utils/YoutubeClass.java diff --git a/src/main/java/netneutrality/command/music/PlayCommand.java b/src/main/java/netneutrality/command/music/PlayCommand.java new file mode 100644 index 0000000..5acadd4 --- /dev/null +++ b/src/main/java/netneutrality/command/music/PlayCommand.java @@ -0,0 +1,68 @@ +package netneutrality.command.music; + +/* + * Copyright (c) anthorob + * + * This file is closed-source. It should not be distributed or copied in any way or form + */ + +import net.dv8tion.jda.core.entities.Member; +import net.dv8tion.jda.core.entities.VoiceChannel; +import net.dv8tion.jda.core.events.message.MessageReceivedEvent; +import netneutrality.command.AbstractCommand; +import netneutrality.command.Visibility; +import netneutrality.music.MusicManager; + +public class PlayCommand extends AbstractCommand { + @Override + public boolean needAdmin() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public String getCommand() { + return "play"; + } + + @Override + public String[] getAlias() { + return new String[0]; + } + + @Override + public String getUsage() { + return null; + } + + @Override + public Visibility getVisibility() { + return Visibility.CHANNEL; + } + + @Override + public String execute(MessageReceivedEvent event, String args) { + Member member = event.getMember(); + MusicManager player = MusicManager.get(event.getGuild()); + + if (member.getVoiceState().inVoiceChannel()) { + VoiceChannel vChannel = member.getVoiceState().getChannel(); + + if (player.isConnected()) { + if (player.isConnectedTo(vChannel)) { + player.play(args.split(" ")[0], event.getTextChannel()); + } else + event.getTextChannel().sendMessage("Not in your voice channel").queue(); + } else + event.getTextChannel().sendMessage("Not in a voice channel").queue(); + + } else + event.getTextChannel().sendMessage("You are not in a voice channel").queue(); + + return null; + } +} diff --git a/src/main/java/netneutrality/command/music/StopCommand.java b/src/main/java/netneutrality/command/music/StopCommand.java new file mode 100644 index 0000000..452290a --- /dev/null +++ b/src/main/java/netneutrality/command/music/StopCommand.java @@ -0,0 +1,10 @@ +package netneutrality.command.music; + +/* + * Copyright (c) anthorob + * + * This file is closed-source. It should not be distributed or copied in any way or form + */ + +public class StopCommand { +} diff --git a/src/main/java/netneutrality/command/music/VolumeCommand.java b/src/main/java/netneutrality/command/music/VolumeCommand.java new file mode 100644 index 0000000..0e9a460 --- /dev/null +++ b/src/main/java/netneutrality/command/music/VolumeCommand.java @@ -0,0 +1,70 @@ +package netneutrality.command.music; + +/* + * Copyright (c) anthorob + * + * This file is closed-source. It should not be distributed or copied in any way or form + */ + +import net.dv8tion.jda.core.entities.Member; +import net.dv8tion.jda.core.entities.VoiceChannel; +import net.dv8tion.jda.core.events.message.MessageReceivedEvent; +import netneutrality.command.AbstractCommand; +import netneutrality.command.Visibility; +import netneutrality.music.MusicManager; + +public class VolumeCommand extends AbstractCommand { + @Override + public boolean needAdmin() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public String getCommand() { + return "volume"; + } + + @Override + public String[] getAlias() { + return new String[0]; + } + + @Override + public String getUsage() { + return null; + } + + @Override + public Visibility getVisibility() { + return Visibility.CHANNEL; + } + + @Override + public String execute(MessageReceivedEvent event, String args) { + Member member = event.getMember(); + MusicManager player = MusicManager.get(event.getGuild()); + + if (member.getVoiceState().inVoiceChannel()) { + VoiceChannel vChannel = member.getVoiceState().getChannel(); + + if (player.isConnected()) { + if (player.isConnectedTo(vChannel)) { + int volume; + volume = Integer.parseInt(args.split(" ")[0]); + player.setVolume(volume); + } else + event.getTextChannel().sendMessage("Not in your voice channel").queue(); + } else + event.getTextChannel().sendMessage("Not in a voice channel").queue(); + + } else + event.getTextChannel().sendMessage("You are not in a voice channel").queue(); + + return null; + } +} diff --git a/src/main/java/netneutrality/http/WebModule.java b/src/main/java/netneutrality/http/WebModule.java index 0e70f78..590c27e 100644 --- a/src/main/java/netneutrality/http/WebModule.java +++ b/src/main/java/netneutrality/http/WebModule.java @@ -27,13 +27,17 @@ public class WebModule { * @return an instance of the WebModule class with the Response of the Web Request * @throws IOException */ - public WebModule request() throws IOException { + public WebModule request() { Request request = new Request.Builder().url(this.url) .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36") .build(); - response = client.newCall(request).execute(); - + try { + response = client.newCall(request).execute(); + } catch (IOException e) { + e.printStackTrace(); + } + return this; } @@ -141,9 +145,33 @@ public class WebModule { public String getRawData() throws IOException { if (response == null) throw new RuntimeException("Request has not been sent"); - + return response.body().string(); } + public String getTextInHtmlTag(String tag) { + String body = ""; + try { + body = getRawData(); + } catch (IOException | RuntimeException e) { + e.printStackTrace(); + } + int startOfTag = body.indexOf(tag); + int endOfTag = body.indexOf(getEndHtmlTag(tag)); + if (startOfTag < 0 || endOfTag < 0) { + return ""; + } + return body.substring(startOfTag + tag.length(), endOfTag - (tag.length() - 1)); + } + + private String getEndHtmlTag(String startingTag) { + String endingTag = ""; + if (startingTag.charAt(0) == '<' && startingTag.charAt(1) != '/' ) { + endingTag = ""; + } + System.out.println("endingTag = " + endingTag); + + return endingTag; + } } diff --git a/src/main/java/netneutrality/music/AudioPlayerSendHandler.java b/src/main/java/netneutrality/music/AudioPlayerSendHandler.java new file mode 100644 index 0000000..0db92a7 --- /dev/null +++ b/src/main/java/netneutrality/music/AudioPlayerSendHandler.java @@ -0,0 +1,54 @@ +package netneutrality.music; + +/* + * Copyright (c) anthorob + * + * This file is closed-source. It should not be distributed or copied in any way or form + */ + +import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; +import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame; +import net.dv8tion.jda.core.audio.AudioSendHandler; + +/** + * This is a wrapper around AudioPlayer which makes it behave as an AudioSendHandler for JDA. As JDA calls canProvide + * before every call to provide20MsAudio(), we pull the frame in canProvide() and use the frame we already pulled in + * provide20MsAudio(). + */ +public class AudioPlayerSendHandler implements AudioSendHandler { + private final AudioPlayer audioPlayer; + private AudioFrame lastFrame; + + /** + * @param audioPlayer Audio player to wrap. + */ + public AudioPlayerSendHandler(AudioPlayer audioPlayer) { + this.audioPlayer = audioPlayer; + } + + @Override + public boolean canProvide() { + if (lastFrame == null) { + lastFrame = audioPlayer.provide(); + } + + return lastFrame != null; + } + + @Override + public byte[] provide20MsAudio() { + if (lastFrame == null) { + lastFrame = audioPlayer.provide(); + } + + byte[] data = lastFrame != null ? lastFrame.data : null; + lastFrame = null; + + return data; + } + + @Override + public boolean isOpus() { + return true; + } +} diff --git a/src/main/java/netneutrality/music/MusicManager.java b/src/main/java/netneutrality/music/MusicManager.java index 5b8235d..5b62fd8 100644 --- a/src/main/java/netneutrality/music/MusicManager.java +++ b/src/main/java/netneutrality/music/MusicManager.java @@ -6,13 +6,20 @@ package netneutrality.music; * This file is closed-source. It should not be distributed or copied in any way or form */ +import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler; import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager; import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers; +import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; +import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist; +import com.sedmelluq.discord.lavaplayer.track.AudioTrack; import net.dv8tion.jda.core.entities.Guild; +import net.dv8tion.jda.core.entities.TextChannel; import net.dv8tion.jda.core.entities.VoiceChannel; import netneutrality.MusicBot; +import netneutrality.handler.LogHandler; +import netneutrality.utils.YoutubeClass; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -30,10 +37,13 @@ public class MusicManager { this.guildId = guild.getIdLong(); player = playerManager.createPlayer(); + guild.getAudioManager().setSendingHandler(new AudioPlayerSendHandler(player)); scheduler = new TrackScheduler(player); player.addListener(scheduler); + player.setVolume(30); AudioSourceManagers.registerRemoteSources(playerManager); + AudioSourceManagers.registerLocalSource(playerManager); managers.put(guild.getId(), this); } @@ -79,4 +89,42 @@ public class MusicManager { public synchronized void stopMusic() { player.destroy(); } + + public synchronized void play(String url, TextChannel channel) { + + String youtubeCode = YoutubeClass.getCodeFromUrl(url); + System.out.println(youtubeCode); + if (!youtubeCode.isEmpty()) { + playerManager.loadItem(youtubeCode, new AudioLoadResultHandler() { + @Override + public void trackLoaded(AudioTrack audioTrack) { + channel.sendMessage("Starting track [" + audioTrack.getInfo().title + "]").queue(); + scheduler.queue(audioTrack); + } + + @Override + public void playlistLoaded(AudioPlaylist playlist) { + for (AudioTrack track : playlist.getTracks()) { + scheduler.queue(track); + } + } + + @Override + public void noMatches() { + System.out.println("Oh shit! I got nothing"); + } + + @Override + public void loadFailed(FriendlyException e) { + LogHandler.log(e); + } + }); + + + } + } + + public void setVolume(int volume) { + player.setVolume(volume); + } } diff --git a/src/main/java/netneutrality/music/TrackScheduler.java b/src/main/java/netneutrality/music/TrackScheduler.java index 95c05f4..78b5f30 100644 --- a/src/main/java/netneutrality/music/TrackScheduler.java +++ b/src/main/java/netneutrality/music/TrackScheduler.java @@ -12,12 +12,32 @@ import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; import com.sedmelluq.discord.lavaplayer.track.AudioTrack; import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + public class TrackScheduler extends AudioEventAdapter { - private AudioPlayer player; + private final AudioPlayer player; + private final BlockingQueue queue; public TrackScheduler(AudioPlayer player) { this.player = player; + this.queue = new LinkedBlockingQueue<>(); + } + + 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); + } + } + + public void nextTrack() { + // 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. + player.startTrack(queue.poll(), false); } @Override @@ -38,7 +58,7 @@ public class TrackScheduler extends AudioEventAdapter { @Override public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason) { if (endReason.mayStartNext) { - // Start next track + nextTrack(); } // endReason == FINISHED: A track finished or died by an exception (mayStartNext = true). diff --git a/src/main/java/netneutrality/utils/YoutubeClass.java b/src/main/java/netneutrality/utils/YoutubeClass.java new file mode 100644 index 0000000..7335fb6 --- /dev/null +++ b/src/main/java/netneutrality/utils/YoutubeClass.java @@ -0,0 +1,35 @@ +package netneutrality.utils; + +/* + * Copyright (c) anthorob + * + * This file is closed-source. It should not be distributed or copied in any way or form + */ + +import netneutrality.http.WebModule; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class YoutubeClass { + + public static Pattern urlPattern = Pattern.compile("^(?:https?:\\/\\/)?(?:(?:www\\.)?)?(?:youtube\\.com|youtu\\.be)\\/.*?(?:embed|e|v|watch.*?v=)?\\/?([-_a-z0-9]{10,})?(?:&?index=\\d+)?(?>(?:playlist\\?|&)?list=([^#\\\\&\\?]{12,}))?", Pattern.CASE_INSENSITIVE); + private final static Pattern youtubeCode = Pattern.compile("^[A-Za-z0-9_-]{11}$"); + + public static String getCodeFromUrl(String url) { + Matcher matcher = urlPattern.matcher(url); + if (matcher.find()) { + if (matcher.group(1) != null) { + return matcher.group(1); + } + } + return ""; + } + + public static String getVideoTitle(String url) { + WebModule youtube = new WebModule(url).request(); + + return youtube.getTextInHtmlTag(""); + } + +}