From 9e7feaaf35b028a92832709351119388aa171b73 Mon Sep 17 00:00:00 2001 From: Minecon724 Date: Thu, 30 Jan 2025 09:38:30 +0100 Subject: [PATCH] Now it runs --- pom.xml | 15 +++++- .../eu/m724/music_plugin/MusicPlugin.java | 26 ++++++---- .../eu/m724/music_plugin/TestCommand.java | 50 ++++++++++--------- .../eu/m724/music_plugin/audio/Converter.java | 3 +- .../music_plugin/audio/OpusFileEncoder.java | 35 +++++++++---- .../audio/player/OpusFilePlayer.java | 6 +-- .../audio/storage/Downloader.java | 31 ++++++------ src/main/resources/plugin.yml | 22 ++------ 8 files changed, 105 insertions(+), 83 deletions(-) diff --git a/pom.xml b/pom.xml index 4d4523a..804adc0 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ - maxhenkel + maxhenkel-repo https://maven.maxhenkel.de/repository/public @@ -62,6 +62,12 @@ inventorygui 1.6.4-SNAPSHOT + + org.jetbrains + annotations + 26.0.1 + compile + @@ -89,5 +95,12 @@ + + + + src/main/resources + true + + diff --git a/src/main/java/eu/m724/music_plugin/MusicPlugin.java b/src/main/java/eu/m724/music_plugin/MusicPlugin.java index 000bc48..7a64e81 100644 --- a/src/main/java/eu/m724/music_plugin/MusicPlugin.java +++ b/src/main/java/eu/m724/music_plugin/MusicPlugin.java @@ -12,6 +12,8 @@ import org.bukkit.NamespacedKey; import org.bukkit.plugin.java.JavaPlugin; import java.io.IOException; +import java.nio.file.Files; +import java.util.Objects; import java.util.logging.Level; public final class MusicPlugin extends JavaPlugin { @@ -30,10 +32,19 @@ public final class MusicPlugin extends JavaPlugin { DebugLogger.logger = getLogger(); var service = getServer().getServicesManager().load(BukkitVoicechatService.class); + if (service == null) { + getLogger().severe("\"Simple Voice Chat\" is required for this plugin"); + getServer().getPluginManager().disablePlugin(this); + return; + } service.registerPlugin(new MyVoicechatPlugin(api -> VOICECHAT_API = api)); - getDataFolder().mkdir(); var storagePath = getDataFolder().toPath().resolve("storage"); + try { + Files.createDirectories(storagePath); + } catch (IOException e) { + throw new RuntimeException(e); + } AUDIO_FILE_STORAGE = new AudioFileStorage(storagePath); DOWNLOADER = new Downloader(storagePath); @@ -44,16 +55,11 @@ public final class MusicPlugin extends JavaPlugin { throw new RuntimeException("Failed to initialize FFmpeg", e); } - getCommand("test").setExecutor(new TestCommand()); + Objects.requireNonNull(getCommand("test")) + .setExecutor(new TestCommand()); - /*var mcmd = new MusicCommands(new AudioFileStorage(getDataFolder())); - getCommand("download").setExecutor(mcmd); - getCommand("convert").setExecutor(mcmd); - getCommand("play").setExecutor(mcmd); - getCommand("stop").setExecutor(mcmd); - getCommand("pause").setExecutor(mcmd); - getCommand("resume").setExecutor(mcmd); - getCommand("pmp").setExecutor(mcmd);*/ + Objects.requireNonNull(getCommand("pmp")) + .setExecutor(new MusicCommands()); getServer().getPluginManager().registerEvents(new ItemEvents(), this); diff --git a/src/main/java/eu/m724/music_plugin/TestCommand.java b/src/main/java/eu/m724/music_plugin/TestCommand.java index d4a5180..4e3bc41 100644 --- a/src/main/java/eu/m724/music_plugin/TestCommand.java +++ b/src/main/java/eu/m724/music_plugin/TestCommand.java @@ -11,6 +11,7 @@ import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.gagravarr.opus.OpusFile; +import org.jetbrains.annotations.NotNull; import java.io.IOException; import java.net.URI; @@ -20,19 +21,18 @@ public class TestCommand implements CommandExecutor { private OpusFilePlayer opusFilePlayer; @Override - public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) { var player = (Player) sender; + var action = args.length > 0 ? args[0] : null; - if (args[0].equals("download")) { - testDownload(player, args[1]); - } else if (args[0].equals("convert")) { - testConvert(player, args[1], Integer.parseInt(args[2])); - } else if (args[0].equals("play")) { - testPlay(player, args[1], Integer.parseInt(args[2])); - } else if (args[0].equals("pause")) { - opusFilePlayer.pause(); - } else if (args[0].equals("unpause")) { - opusFilePlayer.unpause(); + switch (action) { + case "download" -> testDownload(player, args[1]); + case "convert" -> testConvert(player, args[1], Integer.parseInt(args[2])); + case "play" -> testPlay(player, args[1], Integer.parseInt(args[2])); + case "pause" -> opusFilePlayer.pause(); + case "unpause" -> opusFilePlayer.unpause(); + case "stop" -> opusFilePlayer.stop(); + case null, default -> sender.sendMessage("download | convert | play | pause | unpause | stop"); } return true; @@ -41,18 +41,20 @@ public class TestCommand implements CommandExecutor { private void testDownload(Player player, String url) { player.sendMessage("Downloading " + url); MusicPlugin.getDownloader().download(URI.create(url)).handle((hash, ex) -> { - if (ex != null) - player.sendMessage("ERROR downloading: " + ex.getMessage()); - else + if (ex != null) { + ex.printStackTrace(); + player.sendMessage("Error downloading. See console for details."); + } else { player.spigot().sendMessage( - new ComponentBuilder("Hash: " + hash) - .event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("Click to copy"))) + new ComponentBuilder("Downloaded. Hash: " + hash.substring(0, 7) + "...") + .event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("Click to copy hash"))) .event(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, hash)) .append("\nClick here to convert to 48 kbps") .event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("Click to do that"))) - .event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/convert " + hash + " 48000")) + .event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/test convert " + hash + " 48000")) .create() ); + } return null; }); } @@ -60,15 +62,17 @@ public class TestCommand implements CommandExecutor { private void testConvert(Player player, String hash, int bitrate) { try { MusicPlugin.getStorage().convert(MusicPlugin.getConverter(), hash, bitrate).handle((f, ex) -> { - if (ex != null) - player.sendMessage("ERROR converting: " + ex.getMessage()); - else + if (ex != null) { + ex.printStackTrace(); + player.sendMessage("Error converting. See console for details."); + } else { player.spigot().sendMessage( new ComponentBuilder("Converted " + hash.substring(0, 7) + "... to " + bitrate + "bps! Click to play") .event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("Click to play"))) .event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/test play " + hash + " " + bitrate)) .create() ); + } return null; }); } catch (IOException e) { @@ -87,13 +91,11 @@ public class TestCommand implements CommandExecutor { try { opusFilePlayer = new OpusFilePlayer(new OpusFile(MusicPlugin.getStorage().get(hash, bitrate).toFile()), channel); opusFilePlayer.play(); - opusFilePlayer.setOnTrackEvent(e -> { - player.sendMessage("Event: " + e); - }); + opusFilePlayer.setOnTrackEvent(e -> player.sendMessage("Event: " + e)); player.sendMessage("Now playing"); } catch (IOException e) { e.printStackTrace(); - player.sendMessage("Error"); + player.sendMessage("Error. See console for details."); } } } diff --git a/src/main/java/eu/m724/music_plugin/audio/Converter.java b/src/main/java/eu/m724/music_plugin/audio/Converter.java index 7eb3799..f891990 100644 --- a/src/main/java/eu/m724/music_plugin/audio/Converter.java +++ b/src/main/java/eu/m724/music_plugin/audio/Converter.java @@ -4,7 +4,6 @@ import net.bramp.ffmpeg.FFmpeg; import net.bramp.ffmpeg.FFmpegExecutor; import net.bramp.ffmpeg.builder.FFmpegBuilder; -import java.io.File; import java.io.IOException; import java.net.URL; import java.nio.file.Path; @@ -44,7 +43,7 @@ public class Converter { private CompletableFuture convert(String input, Path output, int bitrate) throws IOException { var builder = new FFmpegBuilder() .setInput(input) - .addOutput(output.toAbsolutePath().toUri()) + .addOutput(output.toAbsolutePath().toString()) .setAudioChannels(1) .setAudioSampleRate(48_000) .setAudioBitRate(bitrate) diff --git a/src/main/java/eu/m724/music_plugin/audio/OpusFileEncoder.java b/src/main/java/eu/m724/music_plugin/audio/OpusFileEncoder.java index 668ac27..14e2e9f 100644 --- a/src/main/java/eu/m724/music_plugin/audio/OpusFileEncoder.java +++ b/src/main/java/eu/m724/music_plugin/audio/OpusFileEncoder.java @@ -3,14 +3,17 @@ package eu.m724.music_plugin.audio; import de.maxhenkel.voicechat.api.opus.OpusEncoder; import eu.m724.music_plugin.DebugLogger; import org.apache.commons.lang3.NotImplementedException; +import org.gagravarr.opus.OpusAudioData; import org.gagravarr.opus.OpusFile; -import java.io.File; import java.io.IOException; public class OpusFileEncoder implements OpusEncoder { private final OpusFile opusFile; private boolean closed = false; + private boolean closeLock = false; + + private OpusAudioData nextPacket; public OpusFileEncoder(OpusFile file) { this.opusFile = file; @@ -18,14 +21,10 @@ public class OpusFileEncoder implements OpusEncoder { @Override public byte[] encode(short[] rawAudio) { - try { - var packet = opusFile.getNextAudioPacket(); - if (packet != null) - return packet.getData(); - else - return new byte[0]; - } catch (IOException e) { - throw new RuntimeException("Reading next packet", e); + if (nextPacket != null) { + return nextPacket.getData(); + } else { + return new byte[0]; } } @@ -43,6 +42,12 @@ public class OpusFileEncoder implements OpusEncoder { @Override public void close() { DebugLogger.fine("opus file just closed"); + if (closeLock) { + DebugLogger.fine("but locked"); + closeLock = false; + return; + } + if (closed) return; closed = true; @@ -53,11 +58,21 @@ public class OpusFileEncoder implements OpusEncoder { } } + public void lock() { + closeLock = true; + } + /* SUPPLIER */ private final short[] array = new short[960]; public short[] supplier() { - return !closed ? array : null; + try { + nextPacket = opusFile.getNextAudioPacket(); + } catch (IOException e) { + // TODO catch this? + } + + return nextPacket != null ? array : null; } } diff --git a/src/main/java/eu/m724/music_plugin/audio/player/OpusFilePlayer.java b/src/main/java/eu/m724/music_plugin/audio/player/OpusFilePlayer.java index dbcdcff..6b43ea9 100644 --- a/src/main/java/eu/m724/music_plugin/audio/player/OpusFilePlayer.java +++ b/src/main/java/eu/m724/music_plugin/audio/player/OpusFilePlayer.java @@ -102,10 +102,10 @@ public class OpusFilePlayer { if (!playing) return; playing = false; - audioPlayer.stopPlaying(); + if (!reset) + encoder.lock(); - if (reset) - encoder.resetState(); + audioPlayer.stopPlaying(); DebugLogger.fine("Playback stopped (reset: %s)", reset); } diff --git a/src/main/java/eu/m724/music_plugin/audio/storage/Downloader.java b/src/main/java/eu/m724/music_plugin/audio/storage/Downloader.java index 47af394..5887699 100644 --- a/src/main/java/eu/m724/music_plugin/audio/storage/Downloader.java +++ b/src/main/java/eu/m724/music_plugin/audio/storage/Downloader.java @@ -9,9 +9,9 @@ import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; -import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.HexFormat; @@ -32,14 +32,19 @@ public class Downloader { * @return the downloaded file's SHA-256 hash (in a future) */ public CompletableFuture download(URI uri) { - DebugLogger.fine("About to download from " + uri); + DebugLogger.fine("About to download from %s", uri); - try ( - var client = HttpClient.newBuilder().followRedirects(HttpClient.Redirect.ALWAYS).build(); - ) { - var request = HttpRequest.newBuilder(uri).GET().build(); - return client.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream()).thenApply(this::futureFunction); - } + // TODO fix this. Yes this is the only way. sendAsync doesn't block for some reason. I don't know why. + return CompletableFuture.supplyAsync(() -> { + try ( + var client = HttpClient.newBuilder().followRedirects(HttpClient.Redirect.ALWAYS).build() + ) { + var request = HttpRequest.newBuilder(uri).GET().build(); + return client.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream()).thenApply(this::futureFunction).join(); + } catch (Exception e) { + throw new CompletionException(e); + } + }); } private String futureFunction(HttpResponse response) { @@ -51,7 +56,7 @@ public class Downloader { try { digest = MessageDigest.getInstance("SHA-256"); } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); + throw new CompletionException(e); } try ( @@ -65,17 +70,15 @@ public class Downloader { digest.update(dataBuffer, 0, bytesRead); } } catch (IOException e) { - throw new RuntimeException("Exception downloading from " + response.uri(), e); + throw new CompletionException("Exception downloading from " + response.uri(), e); } String hash = HexFormat.of().formatHex(digest.digest()); DebugLogger.fine("Downloaded, hash: " + hash); try { - Files.createDirectory(basePath.resolve(hash)); - Files.move(temp, basePath.resolve(hash).resolve("0.original")); - } catch (FileAlreadyExistsException e) { - DebugLogger.fine("Already exists!"); + Files.createDirectories(basePath.resolve(hash)); + Files.move(temp, basePath.resolve(hash).resolve("0.original"), StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { throw new CompletionException("Exception saving file", e); } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 7f0f5f9..b2e18b1 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -10,22 +10,6 @@ libraries: - org.gagravarr:vorbis-java-core:0.8 commands: - download: - description: Downloads a file from URL. Run /convert next - usage: / - convert: - description: Converts original downloaded file to some bitrate (in bps) - usage: / - play: - description: Plays the audio file of specified bitrate. Run /convert first - usage: / - stop: - description: Stops playback - usage: / - pause: - description: Pauses playback - usage: / - resume: - description: Resumes playback - usage: / - pmp: \ No newline at end of file + pmp: + + test: \ No newline at end of file