Now it runs

This commit is contained in:
Minecon724 2025-01-30 09:38:30 +01:00
commit 9e7feaaf35
No known key found for this signature in database
GPG key ID: 3CCC4D267742C8E8
8 changed files with 105 additions and 83 deletions

15
pom.xml
View file

@ -21,7 +21,7 @@
</repository>
<!-- this one has no ipv6 -->
<repository>
<id>maxhenkel</id>
<id>maxhenkel-repo</id>
<url>https://maven.maxhenkel.de/repository/public</url>
</repository>
<repository>
@ -62,6 +62,12 @@
<artifactId>inventorygui</artifactId>
<version>1.6.4-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>26.0.1</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
@ -89,5 +95,12 @@
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
</project>

View file

@ -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);

View file

@ -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.");
}
}
}

View file

@ -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<Path> 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)

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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<String> 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<InputStream> 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);
}

View file

@ -10,22 +10,6 @@ libraries:
- org.gagravarr:vorbis-java-core:0.8
commands:
download:
description: Downloads a file from URL. Run /convert next
usage: /<command> <url>
convert:
description: Converts original downloaded file to some bitrate (in bps)
usage: /<command> <hash> <bitrate>
play:
description: Plays the audio file of specified bitrate. Run /convert first
usage: /<command> <hash> <bitrate>
stop:
description: Stops playback
usage: /<command>
pause:
description: Pauses playback
usage: /<command>
resume:
description: Resumes playback
usage: /<command>
pmp:
pmp:
test: