diff --git a/src/main/java/com/jagrosh/jmusicbot/BotConfig.java b/src/main/java/com/jagrosh/jmusicbot/BotConfig.java index 48d676e34..ba9cd601b 100644 --- a/src/main/java/com/jagrosh/jmusicbot/BotConfig.java +++ b/src/main/java/com/jagrosh/jmusicbot/BotConfig.java @@ -16,8 +16,8 @@ package com.jagrosh.jmusicbot; import com.jagrosh.jmusicbot.entities.Prompt; -import com.jagrosh.jmusicbot.utils.FormatUtil; import com.jagrosh.jmusicbot.utils.OtherUtil; +import com.jagrosh.jmusicbot.utils.TimeUtil; import com.sedmelluq.discord.lavaplayer.track.AudioTrack; import com.typesafe.config.*; import java.io.IOException; @@ -294,7 +294,7 @@ public long getMaxSeconds() public String getMaxTime() { - return FormatUtil.formatTime(maxSeconds * 1000); + return TimeUtil.formatTime(maxSeconds * 1000); } public boolean isTooLong(AudioTrack track) diff --git a/src/main/java/com/jagrosh/jmusicbot/JMusicBot.java b/src/main/java/com/jagrosh/jmusicbot/JMusicBot.java index 683df91d3..89ae0ec13 100644 --- a/src/main/java/com/jagrosh/jmusicbot/JMusicBot.java +++ b/src/main/java/com/jagrosh/jmusicbot/JMusicBot.java @@ -109,6 +109,7 @@ public static void main(String[] args) new RemoveCmd(bot), new SearchCmd(bot), new SCSearchCmd(bot), + new SeekCmd(bot), new ShuffleCmd(bot), new SkipCmd(bot), diff --git a/src/main/java/com/jagrosh/jmusicbot/audio/AudioHandler.java b/src/main/java/com/jagrosh/jmusicbot/audio/AudioHandler.java index 3fd2beef3..5c5cde06c 100644 --- a/src/main/java/com/jagrosh/jmusicbot/audio/AudioHandler.java +++ b/src/main/java/com/jagrosh/jmusicbot/audio/AudioHandler.java @@ -17,6 +17,7 @@ import com.jagrosh.jmusicbot.JMusicBot; import com.jagrosh.jmusicbot.playlist.PlaylistLoader.Playlist; +import com.jagrosh.jmusicbot.utils.TimeUtil; import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter; import com.sedmelluq.discord.lavaplayer.track.AudioTrack; @@ -226,7 +227,7 @@ public Message getNowPlaying(JDA jda) double progress = (double)audioPlayer.getPlayingTrack().getPosition()/track.getDuration(); eb.setDescription((audioPlayer.isPaused() ? JMusicBot.PAUSE_EMOJI : JMusicBot.PLAY_EMOJI) + " "+FormatUtil.progressBar(progress) - + " `[" + FormatUtil.formatTime(track.getPosition()) + "/" + FormatUtil.formatTime(track.getDuration()) + "]` " + + " `[" + TimeUtil.formatTime(track.getPosition()) + "/" + TimeUtil.formatTime(track.getDuration()) + "]` " + FormatUtil.volumeIcon(audioPlayer.getVolume())); return mb.setEmbed(eb.build()).build(); @@ -257,7 +258,7 @@ public String getTopicFormat(JDA jda) title = track.getInfo().uri; return "**"+title+"** ["+(userid==0 ? "autoplay" : "<@"+userid+">")+"]" + "\n" + (audioPlayer.isPaused() ? JMusicBot.PAUSE_EMOJI : JMusicBot.PLAY_EMOJI) + " " - + "[" + FormatUtil.formatTime(track.getDuration()) + "] " + + "[" + TimeUtil.formatTime(track.getDuration()) + "] " + FormatUtil.volumeIcon(audioPlayer.getVolume()); } else return "No music playing " + JMusicBot.STOP_EMOJI + " " + FormatUtil.volumeIcon(audioPlayer.getVolume()); diff --git a/src/main/java/com/jagrosh/jmusicbot/audio/QueuedTrack.java b/src/main/java/com/jagrosh/jmusicbot/audio/QueuedTrack.java index 22af75028..6754aa342 100644 --- a/src/main/java/com/jagrosh/jmusicbot/audio/QueuedTrack.java +++ b/src/main/java/com/jagrosh/jmusicbot/audio/QueuedTrack.java @@ -15,9 +15,9 @@ */ package com.jagrosh.jmusicbot.audio; +import com.jagrosh.jmusicbot.utils.TimeUtil; import com.sedmelluq.discord.lavaplayer.track.AudioTrack; import com.jagrosh.jmusicbot.queue.Queueable; -import com.jagrosh.jmusicbot.utils.FormatUtil; import net.dv8tion.jda.core.entities.User; /** @@ -53,6 +53,6 @@ public AudioTrack getTrack() @Override public String toString() { - return "`[" + FormatUtil.formatTime(track.getDuration()) + "]` **" + track.getInfo().title + "** - <@" + track.getUserData(Long.class) + ">"; + return "`[" + TimeUtil.formatTime(track.getDuration()) + "]` **" + track.getInfo().title + "** - <@" + track.getUserData(Long.class) + ">"; } } diff --git a/src/main/java/com/jagrosh/jmusicbot/commands/dj/PlaynextCmd.java b/src/main/java/com/jagrosh/jmusicbot/commands/dj/PlaynextCmd.java index b9f2c8883..dbda0b012 100644 --- a/src/main/java/com/jagrosh/jmusicbot/commands/dj/PlaynextCmd.java +++ b/src/main/java/com/jagrosh/jmusicbot/commands/dj/PlaynextCmd.java @@ -21,6 +21,7 @@ import com.jagrosh.jmusicbot.audio.QueuedTrack; import com.jagrosh.jmusicbot.commands.DJCommand; import com.jagrosh.jmusicbot.utils.FormatUtil; +import com.jagrosh.jmusicbot.utils.TimeUtil; import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler; import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist; @@ -79,13 +80,13 @@ private void loadSingle(AudioTrack track) if(bot.getConfig().isTooLong(track)) { m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" This track (**"+track.getInfo().title+"**) is longer than the allowed maximum: `" - +FormatUtil.formatTime(track.getDuration())+"` > `"+FormatUtil.formatTime(bot.getConfig().getMaxSeconds()*1000)+"`")).queue(); + + TimeUtil.formatTime(track.getDuration())+"` > `"+ TimeUtil.formatTime(bot.getConfig().getMaxSeconds()*1000)+"`")).queue(); return; } AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler(); int pos = handler.addTrackToFront(new QueuedTrack(track, event.getAuthor()))+1; String addMsg = FormatUtil.filter(event.getClient().getSuccess()+" Added **"+track.getInfo().title - +"** (`"+FormatUtil.formatTime(track.getDuration())+"`) "+(pos==0?"to begin playing":" to the queue at position "+pos)); + +"** (`"+ TimeUtil.formatTime(track.getDuration())+"`) "+(pos==0?"to begin playing":" to the queue at position "+pos)); m.editMessage(addMsg).queue(); } diff --git a/src/main/java/com/jagrosh/jmusicbot/commands/music/PlayCmd.java b/src/main/java/com/jagrosh/jmusicbot/commands/music/PlayCmd.java index 7119b5ccd..aad6bc334 100644 --- a/src/main/java/com/jagrosh/jmusicbot/commands/music/PlayCmd.java +++ b/src/main/java/com/jagrosh/jmusicbot/commands/music/PlayCmd.java @@ -15,6 +15,7 @@ */ package com.jagrosh.jmusicbot.commands.music; +import com.jagrosh.jmusicbot.utils.TimeUtil; import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler; import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; import com.sedmelluq.discord.lavaplayer.tools.FriendlyException.Severity; @@ -108,13 +109,13 @@ private void loadSingle(AudioTrack track, AudioPlaylist playlist) if(bot.getConfig().isTooLong(track)) { m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" This track (**"+track.getInfo().title+"**) is longer than the allowed maximum: `" - +FormatUtil.formatTime(track.getDuration())+"` > `"+FormatUtil.formatTime(bot.getConfig().getMaxSeconds()*1000)+"`")).queue(); + + TimeUtil.formatTime(track.getDuration())+"` > `"+ TimeUtil.formatTime(bot.getConfig().getMaxSeconds()*1000)+"`")).queue(); return; } AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler(); int pos = handler.addTrack(new QueuedTrack(track, event.getAuthor()))+1; String addMsg = FormatUtil.filter(event.getClient().getSuccess()+" Added **"+track.getInfo().title - +"** (`"+FormatUtil.formatTime(track.getDuration())+"`) "+(pos==0?"to begin playing":" to the queue at position "+pos)); + +"** (`"+ TimeUtil.formatTime(track.getDuration())+"`) "+(pos==0?"to begin playing":" to the queue at position "+pos)); if(playlist==null || !event.getSelfMember().hasPermission(event.getTextChannel(), Permission.MESSAGE_ADD_REACTION)) m.editMessage(addMsg).queue(); else diff --git a/src/main/java/com/jagrosh/jmusicbot/commands/music/QueueCmd.java b/src/main/java/com/jagrosh/jmusicbot/commands/music/QueueCmd.java index d36e5b9dc..da319124b 100644 --- a/src/main/java/com/jagrosh/jmusicbot/commands/music/QueueCmd.java +++ b/src/main/java/com/jagrosh/jmusicbot/commands/music/QueueCmd.java @@ -26,6 +26,7 @@ import com.jagrosh.jmusicbot.commands.MusicCommand; import com.jagrosh.jmusicbot.settings.Settings; import com.jagrosh.jmusicbot.utils.FormatUtil; +import com.jagrosh.jmusicbot.utils.TimeUtil; import net.dv8tion.jda.core.MessageBuilder; import net.dv8tion.jda.core.Permission; import net.dv8tion.jda.core.entities.Message; @@ -113,7 +114,7 @@ private String getQueueTitle(AudioHandler ah, String success, int songslength, l .append(ah.getPlayer().getPlayingTrack().getInfo().title).append("**\n"); } return FormatUtil.filter(sb.append(success).append(" Current Queue | ").append(songslength) - .append(" entries | `").append(FormatUtil.formatTime(total)).append("` ") + .append(" entries | `").append(TimeUtil.formatTime(total)).append("` ") .append(repeatmode ? "| " + REPEAT : "").toString()); } } diff --git a/src/main/java/com/jagrosh/jmusicbot/commands/music/SearchCmd.java b/src/main/java/com/jagrosh/jmusicbot/commands/music/SearchCmd.java index 8d811895e..b9f5b9b82 100644 --- a/src/main/java/com/jagrosh/jmusicbot/commands/music/SearchCmd.java +++ b/src/main/java/com/jagrosh/jmusicbot/commands/music/SearchCmd.java @@ -15,6 +15,7 @@ */ package com.jagrosh.jmusicbot.commands.music; +import com.jagrosh.jmusicbot.utils.TimeUtil; import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler; import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; import com.sedmelluq.discord.lavaplayer.tools.FriendlyException.Severity; @@ -88,13 +89,13 @@ public void trackLoaded(AudioTrack track) if(bot.getConfig().isTooLong(track)) { m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" This track (**"+track.getInfo().title+"**) is longer than the allowed maximum: `" - +FormatUtil.formatTime(track.getDuration())+"` > `"+bot.getConfig().getMaxTime()+"`")).queue(); + + TimeUtil.formatTime(track.getDuration())+"` > `"+bot.getConfig().getMaxTime()+"`")).queue(); return; } AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler(); int pos = handler.addTrack(new QueuedTrack(track, event.getAuthor()))+1; m.editMessage(FormatUtil.filter(event.getClient().getSuccess()+" Added **"+track.getInfo().title - +"** (`"+FormatUtil.formatTime(track.getDuration())+"`) "+(pos==0 ? "to begin playing" + +"** (`"+ TimeUtil.formatTime(track.getDuration())+"`) "+(pos==0 ? "to begin playing" : " to the queue at position "+pos))).queue(); } @@ -110,13 +111,13 @@ public void playlistLoaded(AudioPlaylist playlist) if(bot.getConfig().isTooLong(track)) { event.replyWarning("This track (**"+track.getInfo().title+"**) is longer than the allowed maximum: `" - +FormatUtil.formatTime(track.getDuration())+"` > `"+bot.getConfig().getMaxTime()+"`"); + + TimeUtil.formatTime(track.getDuration())+"` > `"+bot.getConfig().getMaxTime()+"`"); return; } AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler(); int pos = handler.addTrack(new QueuedTrack(track, event.getAuthor()))+1; event.replySuccess("Added **" + FormatUtil.filter(track.getInfo().title) - + "** (`" + FormatUtil.formatTime(track.getDuration()) + "`) " + (pos==0 ? "to begin playing" + + "** (`" + TimeUtil.formatTime(track.getDuration()) + "`) " + (pos==0 ? "to begin playing" : " to the queue at position "+pos)); }) .setCancel((msg) -> {}) @@ -125,7 +126,7 @@ public void playlistLoaded(AudioPlaylist playlist) for(int i=0; i<4 && i. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jagrosh.jmusicbot.commands.music; + +import com.jagrosh.jdautilities.command.CommandEvent; +import com.jagrosh.jmusicbot.Bot; +import com.jagrosh.jmusicbot.audio.AudioHandler; +import com.jagrosh.jmusicbot.commands.DJCommand; +import com.jagrosh.jmusicbot.commands.MusicCommand; +import com.jagrosh.jmusicbot.utils.TimeUtil; +import com.sedmelluq.discord.lavaplayer.track.AudioTrack; + + +/** + * @author Whew., Inc. + */ +public class SeekCmd extends MusicCommand +{ + public SeekCmd(Bot bot) + { + super(bot); + this.name = "seek"; + this.help = "seeks the current song"; + this.arguments = "[+ | -] "; + this.aliases = bot.getConfig().getAliases(this.name); + this.beListening = true; + this.bePlaying = true; + } + + @Override + public void doCommand(CommandEvent event) + { + AudioHandler handler = (AudioHandler) event.getGuild().getAudioManager().getSendingHandler(); + AudioTrack playingTrack = handler.getPlayer().getPlayingTrack(); + if (!playingTrack.isSeekable()) + { + event.replyError("This track is not seekable."); + return; + } + + + if (!DJCommand.checkDJPermission(event) && playingTrack.getUserData(Long.class) != event.getAuthor().getIdLong()) + { + event.replyError("You cannot seek **" + playingTrack.getInfo().title + "** because you didn't add it!"); + return; + } + + String args = event.getArgs(); + TimeUtil.SeekTime seekTime = TimeUtil.parseTime(args); + if (seekTime == null) + { + event.replyError("Invalid seek! Expected format: " + arguments + "\nExamples: `1:02:23` `+1:10` `-90`"); + return; + } + + long currentPosition = playingTrack.getPosition(); + long trackDuration = playingTrack.getDuration(); + + long seekMilliseconds = seekTime.relative ? currentPosition + seekTime.milliseconds : seekTime.milliseconds; + if (seekMilliseconds > trackDuration) + { + event.replyError("Cannot seek to `" + TimeUtil.formatTime(seekMilliseconds) + "` because the current track is `" + TimeUtil.formatTime(trackDuration) + "` long!"); + } else try + { + playingTrack.setPosition(seekMilliseconds); + } catch (Exception e) + { + event.replyError("An error occurred while trying to seek!"); + e.printStackTrace(); + return; + } + event.replySuccess("Successfully seeked to `" + TimeUtil.formatTime(playingTrack.getPosition()) + "/" + TimeUtil.formatTime(playingTrack.getDuration()) + "`!"); + } +} diff --git a/src/main/java/com/jagrosh/jmusicbot/utils/FormatUtil.java b/src/main/java/com/jagrosh/jmusicbot/utils/FormatUtil.java index 7524ed7a3..3d2a1a8d0 100644 --- a/src/main/java/com/jagrosh/jmusicbot/utils/FormatUtil.java +++ b/src/main/java/com/jagrosh/jmusicbot/utils/FormatUtil.java @@ -25,19 +25,7 @@ * @author John Grosh */ public class FormatUtil { - - public static String formatTime(long duration) - { - if(duration == Long.MAX_VALUE) - return "LIVE"; - long seconds = Math.round(duration/1000.0); - long hours = seconds/(60*60); - seconds %= 60*60; - long minutes = seconds/60; - seconds %= 60; - return (hours>0 ? hours+":" : "") + (minutes<10 ? "0"+minutes : minutes) + ":" + (seconds<10 ? "0"+seconds : seconds); - } - + public static String progressBar(double percent) { String str = ""; diff --git a/src/main/java/com/jagrosh/jmusicbot/utils/TimeUtil.java b/src/main/java/com/jagrosh/jmusicbot/utils/TimeUtil.java new file mode 100644 index 000000000..ee1687cec --- /dev/null +++ b/src/main/java/com/jagrosh/jmusicbot/utils/TimeUtil.java @@ -0,0 +1,87 @@ +/* + * Copyright 2020 John Grosh . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jagrosh.jmusicbot.utils; + +public class TimeUtil +{ + + public static String formatTime(long duration) + { + if(duration == Long.MAX_VALUE) + return "LIVE"; + long seconds = Math.round(duration/1000.0); + long hours = seconds/(60*60); + seconds %= 60*60; + long minutes = seconds/60; + seconds %= 60; + return (hours>0 ? hours+":" : "") + (minutes<10 ? "0"+minutes : minutes) + ":" + (seconds<10 ? "0"+seconds : seconds); + } + + /** + * @param args timestamp formatted as: [+ | -] <HH:MM:SS | MM:SS | SS> + * @return Time in milliseconds, negative if seeking backwards relatively + */ + public static SeekTime parseTime(String args) + { + if (args.length() == 0) return null; + String timestamp = args; + boolean relative = false; // seek forward or backward + boolean isSeekingBackwards = false; + char first = timestamp.charAt(0); + if (first == '+' || first == '-') + { + relative = true; + isSeekingBackwards = first == '-'; + timestamp = timestamp.substring(1); + } + + String[] timestampSplitArray = timestamp.split(":+"); + if(timestampSplitArray.length > 3 ) + return null; + double[] timeUnitArray = new double[3]; // hours, minutes, seconds + for(int index = 0; index < timestampSplitArray.length; index++) + { + String unit = timestampSplitArray[index]; + if (unit.startsWith("+") || unit.startsWith("-")) return null; + unit = unit.replace(",", "."); + try + { + timeUnitArray[index + 3 - timestampSplitArray.length] = Double.parseDouble(unit); + } + catch (NumberFormatException e) + { + return null; + } + } + long milliseconds = Math.round(timeUnitArray[0] * 3600000 + timeUnitArray[1] * 60000 + timeUnitArray[2] * 1000); + milliseconds *= isSeekingBackwards ? -1 : 1; + + return new SeekTime(milliseconds, relative); + } + + + public static class SeekTime + { + public final long milliseconds; + public final boolean relative; + + private SeekTime(long milliseconds, boolean relative) + { + this.milliseconds = milliseconds; + this.relative = relative; + } + } +} diff --git a/src/test/java/com/jagrosh/jmusicbot/TimeUtilTest.java b/src/test/java/com/jagrosh/jmusicbot/TimeUtilTest.java new file mode 100644 index 000000000..faad64f40 --- /dev/null +++ b/src/test/java/com/jagrosh/jmusicbot/TimeUtilTest.java @@ -0,0 +1,107 @@ +/* + * Copyright 2020 John Grosh . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jagrosh.jmusicbot; + + +import com.jagrosh.jmusicbot.utils.TimeUtil; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * @author Whew., Inc. + */ +public class TimeUtilTest +{ + @Test + public void singleDigit() + { + TimeUtil.SeekTime seek = TimeUtil.parseTime("5"); + assertNotNull(seek); + assertEquals(5000, seek.milliseconds); + } + + @Test + public void multipleDigits() + { + TimeUtil.SeekTime seek = TimeUtil.parseTime("99:9:999"); + assertNotNull(seek); + assertEquals(357939000, seek.milliseconds); + } + + @Test + public void decimalDigits() + { + TimeUtil.SeekTime seek = TimeUtil.parseTime("99.5:9.0:999.777"); + assertNotNull(seek); + assertEquals(359739777, seek.milliseconds); + } + + @Test + public void seeking() + { + TimeUtil.SeekTime seek = TimeUtil.parseTime("5"); + assertNotNull(seek); + assertFalse(seek.relative); + assertEquals(5000, seek.milliseconds); + } + + @Test + public void relativeSeekingForward() + { + TimeUtil.SeekTime seek = TimeUtil.parseTime("+5"); + assertNotNull(seek); + assertTrue(seek.relative); + assertEquals(5000, seek.milliseconds); + } + + @Test + public void relativeSeekingBackward() + { + TimeUtil.SeekTime seek = TimeUtil.parseTime("-5"); + assertNotNull(seek); + assertTrue(seek.relative); + assertEquals(-5000, seek.milliseconds); + } + + @Test + public void parseTimeArgumentLength() + { + TimeUtil.SeekTime seek = TimeUtil.parseTime(""); + assertNull(seek); + } + + @Test + public void timestampTotalUnits() + { + TimeUtil.SeekTime seek = TimeUtil.parseTime("1:1:1:1"); + assertNull(seek); + } + + @Test + public void relativeSymbol() + { + TimeUtil.SeekTime seek = TimeUtil.parseTime("+-1:-+1:+-1"); + assertNull(seek); + } + + @Test + public void timestampNumberFormat() + { + TimeUtil.SeekTime seek = TimeUtil.parseTime("1:1:a"); + assertNull(seek); + } +}