Minor refactoring
Compiles but not tested
This commit is contained in:
parent
26963e32ca
commit
a4d8e56a4e
37 changed files with 993 additions and 969 deletions
38
pom.xml
38
pom.xml
|
@ -19,10 +19,15 @@
|
|||
<id>spigotmc-repo</id>
|
||||
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
|
||||
</repository>
|
||||
<!-- this one has no ipv6 -->
|
||||
<repository>
|
||||
<id>maxhenkel</id>
|
||||
<url>https://maven.maxhenkel.de/repository/public</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>minebench-repo</id>
|
||||
<url>https://repo.minebench.de/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
|
@ -51,5 +56,38 @@
|
|||
<artifactId>ffmpeg</artifactId>
|
||||
<version>0.8.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>de.themoep</groupId>
|
||||
<artifactId>inventorygui</artifactId>
|
||||
<version>1.6.4-SNAPSHOT</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.6.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<minimizeJar>true</minimizeJar>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<artifactSet>
|
||||
<includes>
|
||||
<include>de.themoep:inventorygui</include>
|
||||
</includes>
|
||||
</artifactSet>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
|
@ -1,137 +0,0 @@
|
|||
package eu.m724.musicPlugin;
|
||||
|
||||
import eu.m724.musicPlugin.file.AudioFileStorage;
|
||||
import eu.m724.musicPlugin.item.PortableMediaPlayer;
|
||||
import eu.m724.musicPlugin.item.PortableMediaPlayers;
|
||||
import eu.m724.musicPlugin.player.MusicPlayer;
|
||||
import eu.m724.musicPlugin.file.AudioFile;
|
||||
import eu.m724.musicPlugin.player.StaticMusicPlayer;
|
||||
import net.md_5.bungee.api.chat.ClickEvent;
|
||||
import net.md_5.bungee.api.chat.ComponentBuilder;
|
||||
import net.md_5.bungee.api.chat.HoverEvent;
|
||||
import net.md_5.bungee.api.chat.hover.content.Text;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
|
||||
public class MusicCommands implements CommandExecutor {
|
||||
private final AudioFileStorage storage;
|
||||
private MusicPlayer player;
|
||||
|
||||
public MusicCommands(AudioFileStorage storage) {
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (command.getName().equals("download")) {
|
||||
try {
|
||||
var cf = storage.download(URI.create(args[0]).toURL());
|
||||
sender.sendMessage("Started download");
|
||||
cf.handle((hash, ex) -> {
|
||||
if (ex != null)
|
||||
sender.sendMessage("ERROR downloading: " + ex.getMessage());
|
||||
else
|
||||
sender.spigot().sendMessage(
|
||||
new ComponentBuilder("Hash: " + hash)
|
||||
.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("Click to copy")))
|
||||
.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"))
|
||||
.create()
|
||||
);
|
||||
return null;
|
||||
});
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else if (command.getName().equals("convert")) {
|
||||
try {
|
||||
var cf = storage.convert(args[0], Integer.parseInt(args[1]));
|
||||
sender.sendMessage("Converting " + args[0] + " to " + args[1] + "bps...");
|
||||
cf.thenAccept(f -> {
|
||||
sender.spigot().sendMessage(
|
||||
new ComponentBuilder("Converted " + args[0] + " to " + args[1] + "bps! Click to play")
|
||||
.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("Click to play")))
|
||||
.event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/play " + args[0] + " " + args[1]))
|
||||
.create()
|
||||
);
|
||||
}).exceptionally(ex -> {
|
||||
sender.sendMessage("ERROR converting: " + ex.getMessage());
|
||||
return null;
|
||||
});
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else if (command.getName().equals("play")) {
|
||||
if (args.length == 1) {
|
||||
StringBuilder msg = new StringBuilder("Available bitrates:");
|
||||
for (Map.Entry<Integer, File> entry : storage.getVersions(args[0]).entrySet()) {
|
||||
msg.append(" ").append(entry.getKey());
|
||||
}
|
||||
sender.sendMessage(msg.toString());
|
||||
return true;
|
||||
}
|
||||
var file = storage.get(args[0], Integer.parseInt(args[1]));
|
||||
sender.sendMessage("Initializeng");
|
||||
player = new StaticMusicPlayer(((Player)sender).getLocation());
|
||||
player.init();
|
||||
|
||||
try (FileInputStream fis = new FileInputStream(file)) {
|
||||
var song = new AudioFile(fis);
|
||||
song.load();
|
||||
player.setAudio(song);
|
||||
player.unpause();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
sender.sendMessage("done si r now playing it");
|
||||
} else if (command.getName().equals("stop")) {
|
||||
player.stop();
|
||||
sender.sendMessage("stopedd");
|
||||
} else if (command.getName().equals("pause")) {
|
||||
player.pause();
|
||||
sender.sendMessage("puased");
|
||||
} else if (command.getName().equals("resume")) {
|
||||
player.unpause();
|
||||
sender.sendMessage("unapuised");
|
||||
} else if (command.getName().equals("pmp")) {
|
||||
var p = (Player) sender;
|
||||
var pmp = PortableMediaPlayers.get(p.getItemInHand());
|
||||
|
||||
switch (args[0]) {
|
||||
case "info" -> {
|
||||
sender.sendMessage(String.valueOf(pmp.id));
|
||||
sender.sendMessage(String.valueOf(pmp.premium));
|
||||
sender.sendMessage(pmp.engraving);
|
||||
}
|
||||
case "create" -> {
|
||||
pmp = PortableMediaPlayer.create(args[0].equals("yes"), args[1]);
|
||||
p.getInventory().addItem(pmp.getItemStack());
|
||||
}
|
||||
case "play" -> {
|
||||
var file = storage.get(args[1], Integer.parseInt(args[2]));
|
||||
|
||||
try (FileInputStream fis = new FileInputStream(file)) {
|
||||
var song = new AudioFile(fis);
|
||||
song.load();
|
||||
pmp.play(song);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
package eu.m724.musicPlugin;
|
||||
|
||||
import de.maxhenkel.voicechat.api.BukkitVoicechatService;
|
||||
import de.maxhenkel.voicechat.api.VoicechatConnection;
|
||||
import de.maxhenkel.voicechat.api.VoicechatServerApi;
|
||||
import eu.m724.musicPlugin.file.AudioFileStorage;
|
||||
import eu.m724.musicPlugin.item.ItemEvents;
|
||||
import eu.m724.musicPlugin.item.speaker.BlockChecker;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
public final class MusicPlugin extends JavaPlugin {
|
||||
@Override
|
||||
public void onEnable() {
|
||||
Statics.plugin = this;
|
||||
|
||||
BukkitVoicechatService service = getServer().getServicesManager().load(BukkitVoicechatService.class);
|
||||
|
||||
if (service != null) {
|
||||
service.registerPlugin(
|
||||
new MyVoicechatPlugin(
|
||||
this::onApiStarted,
|
||||
this::onPlayerConnected
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
getDataFolder().mkdir();
|
||||
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);
|
||||
|
||||
getServer().getPluginManager().registerEvents(new ItemEvents(), this);
|
||||
|
||||
// TODO do this better, maybe along events
|
||||
new BlockChecker().runTaskTimerAsynchronously(this, 0, 20);
|
||||
}
|
||||
|
||||
private void onApiStarted(VoicechatServerApi api) {
|
||||
getLogger().info("registerating...");
|
||||
|
||||
var category = api.volumeCategoryBuilder()
|
||||
.setId("musicc")
|
||||
.setName("Music players")
|
||||
.build();
|
||||
|
||||
api.registerVolumeCategory(category);
|
||||
|
||||
Statics.api = api;
|
||||
|
||||
getLogger().info("Sucess");
|
||||
}
|
||||
|
||||
private void onPlayerConnected(VoicechatConnection connection) {
|
||||
getLogger().info("Player connected: " + connection);
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
package eu.m724.musicPlugin;
|
||||
|
||||
import de.maxhenkel.voicechat.api.VoicechatApi;
|
||||
import de.maxhenkel.voicechat.api.VoicechatConnection;
|
||||
import de.maxhenkel.voicechat.api.VoicechatPlugin;
|
||||
import de.maxhenkel.voicechat.api.VoicechatServerApi;
|
||||
import de.maxhenkel.voicechat.api.events.EventRegistration;
|
||||
import de.maxhenkel.voicechat.api.events.PlayerConnectedEvent;
|
||||
import de.maxhenkel.voicechat.api.events.VoicechatServerStartedEvent;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class MyVoicechatPlugin implements VoicechatPlugin {
|
||||
private final Consumer<VoicechatServerApi> apiConsumer;
|
||||
private final Consumer<VoicechatConnection> playerConnected;
|
||||
|
||||
public MyVoicechatPlugin(Consumer<VoicechatServerApi> apiConsumer, Consumer<VoicechatConnection> playerConnected) {
|
||||
this.apiConsumer = apiConsumer;
|
||||
this.playerConnected = playerConnected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPluginId() {
|
||||
return "myplgugonbo";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(VoicechatApi api) {
|
||||
VoicechatPlugin.super.initialize(api);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerEvents(EventRegistration registration) {
|
||||
registration.registerEvent(VoicechatServerStartedEvent.class, this::onServerStarted);
|
||||
registration.registerEvent(PlayerConnectedEvent.class, this::onPlayerConnected);
|
||||
}
|
||||
|
||||
public void onServerStarted(VoicechatServerStartedEvent event) {
|
||||
apiConsumer.accept(event.getVoicechat());
|
||||
}
|
||||
|
||||
public void onPlayerConnected(PlayerConnectedEvent event) {
|
||||
playerConnected.accept(event.getConnection());
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package eu.m724.musicPlugin;
|
||||
|
||||
import de.maxhenkel.voicechat.api.VoicechatServerApi;
|
||||
|
||||
// TODO find a better way
|
||||
public class Statics {
|
||||
public static MusicPlugin plugin;
|
||||
public static VoicechatServerApi api;
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
package eu.m724.musicPlugin.file;
|
||||
|
||||
import org.gagravarr.ogg.OggFile;
|
||||
import org.gagravarr.opus.OpusAudioData;
|
||||
import org.gagravarr.opus.OpusFile;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class AudioFile {
|
||||
private final InputStream inputStream;
|
||||
|
||||
private NotEncoder encoder;
|
||||
private String title, artist;
|
||||
private int duration;
|
||||
|
||||
public AudioFile(File file) {
|
||||
try {
|
||||
this.inputStream = new FileInputStream(file);
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public AudioFile(InputStream inputStream) {
|
||||
this.inputStream = inputStream;
|
||||
}
|
||||
|
||||
public void load() throws IOException {
|
||||
if (encoder != null) return;
|
||||
|
||||
var list = new ArrayList<byte[]>();
|
||||
|
||||
var opus = new OpusFile(new OggFile(inputStream));
|
||||
var frames = 0;
|
||||
|
||||
OpusAudioData packet;
|
||||
while ((packet = opus.getNextAudioPacket()) != null) {
|
||||
list.add(packet.getData());
|
||||
frames += packet.getNumberOfFrames();
|
||||
}
|
||||
|
||||
this.duration = frames / 50; // a frame is 20 ms long so 20 * 50 is 1000
|
||||
System.out.printf("audiop file has %s framrs\n", frames);
|
||||
|
||||
this.title = opus.getTags().getTitle();
|
||||
this.artist = opus.getTags().getArtist();
|
||||
|
||||
this.encoder = new NotEncoder(list.toArray(byte[][]::new));
|
||||
}
|
||||
|
||||
|
||||
public NotEncoder getEncoder() {
|
||||
return encoder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if data was loaded i.e. if load was called
|
||||
*/
|
||||
public boolean isLoaded() {
|
||||
return encoder != null;
|
||||
}
|
||||
|
||||
/** @return Track duration in seconds */
|
||||
public int getTrackDuration() { return duration; }
|
||||
|
||||
/** @return Track title if available */
|
||||
public String getTrackTitle() { return title; }
|
||||
|
||||
/** @return Track artist if available */
|
||||
public String getTrackArtist() { return artist; }
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package eu.m724.musicPlugin.file;
|
||||
|
||||
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.util.concurrent.CompletableFuture;
|
||||
|
||||
public class AudioFileConverter {
|
||||
private final FFmpeg ffmpeg;
|
||||
|
||||
public AudioFileConverter(FFmpeg ffmpeg) {
|
||||
this.ffmpeg = ffmpeg;
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> convert(File input, File output, int bitrate) throws IOException {
|
||||
return convert(input.getAbsolutePath(), output, bitrate);
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> convert(URL source, File output, int bitrate) throws IOException {
|
||||
return convert(source.toString(), output, bitrate);
|
||||
}
|
||||
|
||||
private CompletableFuture<Void> convert(String input, File output, int bitrate) throws IOException {
|
||||
var builder = new FFmpegBuilder()
|
||||
.setInput(input)
|
||||
.addOutput(output.getAbsolutePath())
|
||||
.setAudioChannels(1)
|
||||
.setAudioSampleRate(48_000)
|
||||
.setAudioBitRate(bitrate)
|
||||
.done();
|
||||
|
||||
var executor = new FFmpegExecutor(ffmpeg);
|
||||
return CompletableFuture.runAsync(executor.createJob(builder));
|
||||
}
|
||||
}
|
|
@ -1,118 +0,0 @@
|
|||
package eu.m724.musicPlugin.file;
|
||||
|
||||
import net.bramp.ffmpeg.FFmpeg;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class AudioFileStorage {
|
||||
private final File basePath;
|
||||
private final AudioFileConverter converter;
|
||||
|
||||
public AudioFileStorage(File basePath) {
|
||||
this.basePath = basePath;
|
||||
|
||||
try {
|
||||
this.converter = new AudioFileConverter(new FFmpeg()); // TODO better error handling and not here
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get versions (bitrates) of an audio file
|
||||
*
|
||||
* @param hash TODO NOT SANITIZED
|
||||
* @return Map of Bps to file, or null if no such hash
|
||||
*/
|
||||
public Map<Integer, File> getVersions(String hash) {
|
||||
var dir = new File(basePath, hash);
|
||||
|
||||
var files = dir.listFiles();
|
||||
if (files == null) return null;
|
||||
|
||||
return Arrays.stream(files).collect(Collectors.toMap(
|
||||
f -> Integer.parseInt(f.getName().split("\\.")[0]),
|
||||
f -> f
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an audio file of given original hash and bitrate
|
||||
*
|
||||
* @param hash TODO NOT SANITIZED
|
||||
* @param bitrate the bitrate in bps like 32000
|
||||
*/
|
||||
public File get(String hash, int bitrate) {
|
||||
return new File(basePath, hash + "/" + bitrate + ".opus");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the original file with a hash
|
||||
*/
|
||||
public File getOriginal(String hash) {
|
||||
return new File(basePath, hash + "/0.original");
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a (original) file to some bitrate
|
||||
*
|
||||
* @param hash the hash
|
||||
* @param bitrate the target bitrate in bps
|
||||
* @return the future with the new file
|
||||
*/
|
||||
public CompletableFuture<File> convert(String hash, int bitrate) throws IOException {
|
||||
var file = get(hash, bitrate);
|
||||
if (file.exists()) return CompletableFuture.completedFuture(file);
|
||||
|
||||
var og = getOriginal(hash);
|
||||
if (!og.exists()) return null;
|
||||
|
||||
return converter.convert(og, file, bitrate).thenApply(v -> file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads an audio file and saves it as original
|
||||
*
|
||||
* @param url the url to download from
|
||||
* @return the hash
|
||||
*/
|
||||
public CompletableFuture<String> download(URL url) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
var temp = new File(basePath, "temp_" + RandomStringUtils.randomAlphabetic(8));
|
||||
var digest = MessageDigest.getInstance("SHA-256");
|
||||
|
||||
try (
|
||||
var input = new BufferedInputStream(url.openStream());
|
||||
var output = new FileOutputStream(temp)
|
||||
) {
|
||||
var dataBuffer = new byte[1024];
|
||||
int bytesRead;
|
||||
while ((bytesRead = input.read(dataBuffer, 0, 1024)) != -1) {
|
||||
output.write(dataBuffer, 0, bytesRead);
|
||||
digest.update(dataBuffer, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
|
||||
String hash = HexFormat.of().formatHex(digest.digest());
|
||||
var og = getOriginal(hash);
|
||||
og.getParentFile().mkdir();
|
||||
if (!og.exists())
|
||||
Files.move(temp.toPath(), og.toPath());
|
||||
|
||||
return hash;
|
||||
} catch (IOException | NoSuchAlgorithmException e) {
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
package eu.m724.musicPlugin.file;
|
||||
|
||||
import de.maxhenkel.voicechat.api.opus.OpusEncoder;
|
||||
|
||||
public class NotEncoder implements OpusEncoder {
|
||||
private final byte[][] opusFrames;
|
||||
private int i = 0;
|
||||
private final int size;
|
||||
|
||||
private boolean closed = false;
|
||||
|
||||
public NotEncoder(byte[][] opusFrames) {
|
||||
this.opusFrames = opusFrames;
|
||||
this.size = opusFrames.length;
|
||||
}
|
||||
|
||||
public void seek(int frame) {
|
||||
i = Math.clamp(frame, 0, size);
|
||||
}
|
||||
|
||||
public int getFrame() { return i; }
|
||||
|
||||
public boolean hasRemaining() {
|
||||
return i < size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encode(short[] rawAudio) {
|
||||
if (i < size)
|
||||
return opusFrames[i++];
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetState() {
|
||||
closed = false;
|
||||
i = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return closed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() { closed = true; }
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
package eu.m724.musicPlugin.item.speaker;
|
||||
|
||||
import de.maxhenkel.voicechat.api.audiochannel.AudioChannel;
|
||||
import eu.m724.musicPlugin.file.AudioFile;
|
||||
import eu.m724.musicPlugin.player.MovingMusicPlayer;
|
||||
import eu.m724.musicPlugin.player.MusicPlayer;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public abstract class Speaker {
|
||||
private final MusicPlayer musicPlayer;
|
||||
private Consumer<Void> destroyCallback;
|
||||
|
||||
protected Speaker(MusicPlayer musicPlayer) {
|
||||
this.musicPlayer = musicPlayer;
|
||||
musicPlayer.init();
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
musicPlayer.stop();
|
||||
|
||||
onDestroy();
|
||||
destroyCallback.accept(null);
|
||||
}
|
||||
|
||||
public void setDestroyCallback(Consumer<Void> consumer) {
|
||||
this.destroyCallback = consumer;
|
||||
}
|
||||
|
||||
public MusicPlayer getMusicPlayer() {
|
||||
return musicPlayer;
|
||||
}
|
||||
|
||||
abstract void onDestroy();
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
package eu.m724.musicPlugin.player;
|
||||
|
||||
import de.maxhenkel.voicechat.api.audiochannel.AudioChannel;
|
||||
import de.maxhenkel.voicechat.api.audiochannel.EntityAudioChannel;
|
||||
import org.bukkit.entity.Entity;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class EntityMusicPlayer extends MusicPlayer {
|
||||
private final Entity entity;
|
||||
|
||||
public EntityMusicPlayer(Entity entity) {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
AudioChannel createChannel() {
|
||||
var channel = api.createEntityAudioChannel(
|
||||
UUID.randomUUID(),
|
||||
api.fromEntity(entity)
|
||||
);
|
||||
|
||||
channel.setCategory("musicc");
|
||||
channel.setDistance(32);
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDistance(int distance) {
|
||||
((EntityAudioChannel)this.channel).setDistance(distance);
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
package eu.m724.musicPlugin.player;
|
||||
|
||||
import de.maxhenkel.voicechat.api.ServerLevel;
|
||||
import de.maxhenkel.voicechat.api.VoicechatConnection;
|
||||
import de.maxhenkel.voicechat.api.audiochannel.AudioChannel;
|
||||
import eu.m724.musicPlugin.Statics;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class LocalMusicPlayer extends MusicPlayer {
|
||||
private final ServerLevel level;
|
||||
private final VoicechatConnection connection;
|
||||
|
||||
public LocalMusicPlayer(Player player) throws NotConnectedException {
|
||||
this.level = Statics.api.fromServerLevel(player.getWorld());
|
||||
this.connection = Statics.api.getConnectionOf(player.getUniqueId());
|
||||
|
||||
if (connection == null) {
|
||||
throw new NotConnectedException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
AudioChannel createChannel() {
|
||||
var channel = api.createStaticAudioChannel(
|
||||
UUID.randomUUID(),
|
||||
level,
|
||||
connection
|
||||
);
|
||||
|
||||
channel.setCategory("musicc");
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDistance(int distance) { } // distance doesn't apply to this one
|
||||
|
||||
public static class NotConnectedException extends Exception {}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
package eu.m724.musicPlugin.player;
|
||||
|
||||
import de.maxhenkel.voicechat.api.audiochannel.AudioChannel;
|
||||
import de.maxhenkel.voicechat.api.audiochannel.LocationalAudioChannel;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Entity;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class MovingMusicPlayer extends MusicPlayer {
|
||||
private Entity entity;
|
||||
private Location location;
|
||||
|
||||
public void moveTo(Location location) {
|
||||
System.out.println("Mvoed to a");
|
||||
|
||||
this.location = location;
|
||||
this.channel = createChannel();
|
||||
|
||||
if (isReady() && !isPlaying()) {
|
||||
this.pause();
|
||||
this.unpause();
|
||||
}
|
||||
}
|
||||
|
||||
public void attachTo(Entity entity) {
|
||||
var channel = api.createEntityAudioChannel(
|
||||
UUID.randomUUID(),
|
||||
api.fromEntity(entity)
|
||||
);
|
||||
|
||||
channel.setCategory("musicc");
|
||||
channel.setDistance(4);
|
||||
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
AudioChannel createChannel() {
|
||||
var channel = api.createLocationalAudioChannel(
|
||||
UUID.randomUUID(),
|
||||
api.fromServerLevel(location.getWorld()),
|
||||
api.createPosition(location.getX(), location.getY(), location.getZ())
|
||||
);
|
||||
|
||||
channel.setCategory("musicc");
|
||||
channel.setDistance(4);
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDistance(int distance) {
|
||||
((LocationalAudioChannel)this.channel).setDistance(distance);
|
||||
}
|
||||
}
|
|
@ -1,181 +0,0 @@
|
|||
package eu.m724.musicPlugin.player;
|
||||
|
||||
import de.maxhenkel.voicechat.api.VoicechatServerApi;
|
||||
import de.maxhenkel.voicechat.api.audiochannel.AudioChannel;
|
||||
import de.maxhenkel.voicechat.api.audiochannel.AudioPlayer;
|
||||
import eu.m724.musicPlugin.Statics;
|
||||
import eu.m724.musicPlugin.file.AudioFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public abstract class MusicPlayer {
|
||||
// TODO find a better way
|
||||
final VoicechatServerApi api = Statics.api;
|
||||
|
||||
AudioChannel channel;
|
||||
|
||||
private boolean ready = false, playing = false;
|
||||
private AudioPlayer player;
|
||||
private AudioFile audioFile;
|
||||
|
||||
private Consumer<TrackAction> onAction = (r) -> {};
|
||||
|
||||
abstract AudioChannel createChannel();
|
||||
abstract public void setDistance(int distance);
|
||||
|
||||
/**
|
||||
* Initializes this music player
|
||||
*/
|
||||
public void init() {
|
||||
this.channel = createChannel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the consumer that will be called after playback is paused, stopped, resumed etc<br>
|
||||
* There can be only one for one music player
|
||||
*
|
||||
* @see TrackAction
|
||||
*/
|
||||
public void setOnAction(Consumer<TrackAction> onAction) {
|
||||
this.onAction = onAction;
|
||||
}
|
||||
|
||||
|
||||
/* Playback control */
|
||||
|
||||
/**
|
||||
* Sets audio file to play<br>
|
||||
* If it's not loaded, it will be loaded synchronously.<br>
|
||||
* The file is not rewinded. If you want to start from beginning, {@link MusicPlayer#seek(int)}
|
||||
* After this, do {@link MusicPlayer#unpause()}
|
||||
*
|
||||
* @see MusicPlayer#pause()
|
||||
* @see MusicPlayer#stop()
|
||||
*/
|
||||
public void setAudio(AudioFile audioFile) {
|
||||
if (audioFile != null)
|
||||
this.audioFile = audioFile;
|
||||
|
||||
if (ready)
|
||||
stop();
|
||||
|
||||
if (!audioFile.isLoaded()) {
|
||||
try {
|
||||
System.out.println("Audio not already loaded, so I load it");
|
||||
audioFile.load();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
System.out.printf("Audio loaded it's %d seconds long, so I'm guessing %d frames\n", audioFile.getTrackDuration(), audioFile.getTrackDuration() * 50);
|
||||
|
||||
ready = true;
|
||||
|
||||
unpause();
|
||||
}
|
||||
|
||||
public void unpause() {
|
||||
if (!ready || playing) return;
|
||||
System.out.println("playback unpaused");
|
||||
|
||||
var sa = new short[960];
|
||||
var enc = audioFile.getEncoder();
|
||||
|
||||
player = api.createAudioPlayer(channel, enc, () -> enc.hasRemaining() ? sa : null);
|
||||
player.setOnStopped(this::onStop);
|
||||
player.startPlaying();
|
||||
|
||||
// TODO this may take a long time and it might glitch if it does
|
||||
onAction.accept(TrackAction.UNPAUSE);
|
||||
|
||||
playing = true;
|
||||
|
||||
System.out.println("playback unpaused now");
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops playback and rewinds
|
||||
*/
|
||||
public void stop() {
|
||||
if (!ready) return;
|
||||
|
||||
stopPlayback(false);
|
||||
audioFile.getEncoder().resetState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses playback
|
||||
*/
|
||||
public void pause() {
|
||||
if (!ready || playing) return;
|
||||
stopPlayback(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Seek to some point in current track
|
||||
*
|
||||
* @param target the target time in milliseconds
|
||||
*/
|
||||
public void seek(int target) {
|
||||
audioFile.getEncoder().seek(target / 20); // a frame is 20 ms usually
|
||||
}
|
||||
|
||||
|
||||
/* Internal methods */
|
||||
|
||||
|
||||
private void stopPlayback(boolean pause) {
|
||||
System.out.println("playback stopped");
|
||||
playing = false;
|
||||
ready = pause;
|
||||
|
||||
player.stopPlaying();
|
||||
player = null;
|
||||
|
||||
onStop();
|
||||
}
|
||||
|
||||
private void onStop() {
|
||||
if (ready && !playing) { // paused
|
||||
System.out.println("I detected pause");
|
||||
onAction.accept(TrackAction.PAUSE);
|
||||
playing = false;
|
||||
} else if (ready) { // not paused and still playing
|
||||
System.out.println("I detected end");
|
||||
onAction.accept(TrackAction.DURATION);
|
||||
ready = false;
|
||||
} else { // not playing
|
||||
System.out.println("I detected stop");
|
||||
onAction.accept(TrackAction.STOP);
|
||||
}
|
||||
}
|
||||
|
||||
public enum TrackAction {
|
||||
/**
|
||||
* Song stopped playing because it played
|
||||
*/
|
||||
DURATION,
|
||||
/**
|
||||
* {@link MusicPlayer#stop()} was called, or started playing a new track
|
||||
*/
|
||||
STOP,
|
||||
/**
|
||||
* {@link MusicPlayer#pause()} was called
|
||||
*/
|
||||
PAUSE,
|
||||
/**
|
||||
* {@link MusicPlayer#unpause()} was called
|
||||
*/
|
||||
UNPAUSE
|
||||
}
|
||||
|
||||
public boolean isReady() {
|
||||
return ready;
|
||||
}
|
||||
|
||||
public boolean isPlaying() {
|
||||
return ready && playing;
|
||||
}
|
||||
}
|
51
src/main/java/eu/m724/music_plugin/DebugLogger.java
Normal file
51
src/main/java/eu/m724/music_plugin/DebugLogger.java
Normal file
|
@ -0,0 +1,51 @@
|
|||
package eu.m724.music_plugin;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class DebugLogger {
|
||||
static Logger logger;
|
||||
|
||||
public static void info(String message, Object... format) {
|
||||
log(Level.INFO, message, format);
|
||||
}
|
||||
|
||||
public static void warning(String message, Object... format) {
|
||||
log(Level.WARNING, message, format);
|
||||
}
|
||||
|
||||
public static void severe(String message, Object... format) {
|
||||
log(Level.SEVERE, message, format);
|
||||
}
|
||||
|
||||
public static void fine(String message, Object... format) {
|
||||
log(Level.FINE, message, format);
|
||||
}
|
||||
|
||||
public static void finer(String message, Object... format) {
|
||||
log(Level.FINER, message, format);
|
||||
}
|
||||
|
||||
private static void log(Level level, String message, Object... format) {
|
||||
if (logger.getLevel().intValue() > level.intValue()) return;
|
||||
|
||||
var caller = Thread.currentThread().getStackTrace()[3].getClassName();
|
||||
|
||||
if (caller.startsWith("eu.m724.music_plugin."))
|
||||
caller = caller.substring(21);
|
||||
|
||||
message = "[" + caller + "] " + message.formatted(format);
|
||||
|
||||
if (level.intValue() < Level.INFO.intValue()) { // levels below info are never logged even if set for some reason
|
||||
// colors text gray (cyan is close to gray)
|
||||
if (level == Level.FINE) {
|
||||
message = "\033[38;5;250m" + message + "\033[39m";
|
||||
} else {
|
||||
message = "\033[38;5;245m" + message + "\033[39m";
|
||||
}
|
||||
level = Level.INFO;
|
||||
}
|
||||
|
||||
logger.log(level, message);
|
||||
}
|
||||
}
|
44
src/main/java/eu/m724/music_plugin/MusicCommands.java
Normal file
44
src/main/java/eu/m724/music_plugin/MusicCommands.java
Normal file
|
@ -0,0 +1,44 @@
|
|||
package eu.m724.music_plugin;
|
||||
|
||||
import eu.m724.music_plugin.item.PortableMediaPlayer;
|
||||
import eu.m724.music_plugin.item.PortableMediaPlayers;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class MusicCommands implements CommandExecutor {
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (command.getName().equals("pmp")) {
|
||||
var p = (Player) sender;
|
||||
var pmp = PortableMediaPlayers.get(p.getItemInHand());
|
||||
|
||||
switch (args[0]) {
|
||||
case "info" -> {
|
||||
sender.sendMessage(String.valueOf(pmp.id));
|
||||
sender.sendMessage(String.valueOf(pmp.premium));
|
||||
sender.sendMessage(pmp.engraving);
|
||||
}
|
||||
case "create" -> {
|
||||
pmp = PortableMediaPlayer.create(args[0].equals("yes"), args[1]);
|
||||
p.getInventory().addItem(pmp.getItemStack());
|
||||
}
|
||||
case "play" -> {
|
||||
var storage = MusicPlugin.getStorage();
|
||||
var path = storage.get(args[1], Integer.parseInt(args[2]));
|
||||
|
||||
try {
|
||||
pmp.play(path.toFile());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
90
src/main/java/eu/m724/music_plugin/MusicPlugin.java
Normal file
90
src/main/java/eu/m724/music_plugin/MusicPlugin.java
Normal file
|
@ -0,0 +1,90 @@
|
|||
package eu.m724.music_plugin;
|
||||
|
||||
import de.maxhenkel.voicechat.api.BukkitVoicechatService;
|
||||
import de.maxhenkel.voicechat.api.VoicechatServerApi;
|
||||
import eu.m724.music_plugin.audio.storage.AudioFileStorage;
|
||||
import eu.m724.music_plugin.audio.Converter;
|
||||
import eu.m724.music_plugin.audio.storage.Downloader;
|
||||
import eu.m724.music_plugin.item.ItemEvents;
|
||||
import eu.m724.music_plugin.item.speaker.BlockChecker;
|
||||
import net.bramp.ffmpeg.FFmpeg;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public final class MusicPlugin extends JavaPlugin {
|
||||
private static MusicPlugin INSTANCE;
|
||||
private static VoicechatServerApi VOICECHAT_API;
|
||||
private static AudioFileStorage AUDIO_FILE_STORAGE;
|
||||
private static Converter CONVERTER;
|
||||
private static Downloader DOWNLOADER;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
var start = System.nanoTime();
|
||||
INSTANCE = this;
|
||||
|
||||
getLogger().setLevel(Level.FINEST);
|
||||
DebugLogger.logger = getLogger();
|
||||
|
||||
var service = getServer().getServicesManager().load(BukkitVoicechatService.class);
|
||||
service.registerPlugin(new MyVoicechatPlugin(api -> VOICECHAT_API = api));
|
||||
|
||||
getDataFolder().mkdir();
|
||||
var storagePath = getDataFolder().toPath().resolve("storage");
|
||||
|
||||
AUDIO_FILE_STORAGE = new AudioFileStorage(storagePath);
|
||||
DOWNLOADER = new Downloader(storagePath);
|
||||
|
||||
try {
|
||||
CONVERTER = new Converter(new FFmpeg());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to initialize FFmpeg", e);
|
||||
}
|
||||
|
||||
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);*/
|
||||
|
||||
getServer().getPluginManager().registerEvents(new ItemEvents(), this);
|
||||
|
||||
// TODO do this better, maybe along events
|
||||
new BlockChecker().runTaskTimerAsynchronously(this, 0, 20);
|
||||
|
||||
var end = System.nanoTime();
|
||||
DebugLogger.fine("Enabled in %.3f milliseconds", (end - start) / 1000000.0);
|
||||
}
|
||||
|
||||
public static MusicPlugin getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public static VoicechatServerApi getVoicechatApi() {
|
||||
return VOICECHAT_API;
|
||||
}
|
||||
|
||||
public static AudioFileStorage getStorage() {
|
||||
return AUDIO_FILE_STORAGE;
|
||||
}
|
||||
|
||||
public static Converter getConverter() {
|
||||
return CONVERTER;
|
||||
}
|
||||
|
||||
public static Downloader getDownloader() {
|
||||
return DOWNLOADER;
|
||||
}
|
||||
|
||||
public static NamespacedKey getNamespacedKey(String key) {
|
||||
return new NamespacedKey(INSTANCE, key);
|
||||
}
|
||||
}
|
42
src/main/java/eu/m724/music_plugin/MyVoicechatPlugin.java
Normal file
42
src/main/java/eu/m724/music_plugin/MyVoicechatPlugin.java
Normal file
|
@ -0,0 +1,42 @@
|
|||
package eu.m724.music_plugin;
|
||||
|
||||
import de.maxhenkel.voicechat.api.VoicechatPlugin;
|
||||
import de.maxhenkel.voicechat.api.VoicechatServerApi;
|
||||
import de.maxhenkel.voicechat.api.events.EventRegistration;
|
||||
import de.maxhenkel.voicechat.api.events.VoicechatServerStartedEvent;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class MyVoicechatPlugin implements VoicechatPlugin {
|
||||
private final Consumer<VoicechatServerApi> apiConsumer;
|
||||
|
||||
public MyVoicechatPlugin(Consumer<VoicechatServerApi> apiConsumer) {
|
||||
this.apiConsumer = apiConsumer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPluginId() {
|
||||
return "music724";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerEvents(EventRegistration registration) {
|
||||
registration.registerEvent(VoicechatServerStartedEvent.class, this::onServerStarted);
|
||||
}
|
||||
|
||||
private void onServerStarted(VoicechatServerStartedEvent event) {
|
||||
var api = event.getVoicechat();
|
||||
|
||||
var category = api.volumeCategoryBuilder()
|
||||
.setId("music")
|
||||
.setName("Music players")
|
||||
.build();
|
||||
|
||||
api.registerVolumeCategory(category);
|
||||
|
||||
apiConsumer.accept(api);
|
||||
|
||||
DebugLogger.fine("Registered");
|
||||
}
|
||||
}
|
||||
|
99
src/main/java/eu/m724/music_plugin/TestCommand.java
Normal file
99
src/main/java/eu/m724/music_plugin/TestCommand.java
Normal file
|
@ -0,0 +1,99 @@
|
|||
package eu.m724.music_plugin;
|
||||
|
||||
import eu.m724.music_plugin.audio.channel.LocationalChannel;
|
||||
import eu.m724.music_plugin.audio.player.OpusFilePlayer;
|
||||
import net.md_5.bungee.api.chat.ClickEvent;
|
||||
import net.md_5.bungee.api.chat.ComponentBuilder;
|
||||
import net.md_5.bungee.api.chat.HoverEvent;
|
||||
import net.md_5.bungee.api.chat.hover.content.Text;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.gagravarr.opus.OpusFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
// TODO remove
|
||||
public class TestCommand implements CommandExecutor {
|
||||
private OpusFilePlayer opusFilePlayer;
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
var player = (Player) sender;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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
|
||||
player.spigot().sendMessage(
|
||||
new ComponentBuilder("Hash: " + hash)
|
||||
.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("Click to copy")))
|
||||
.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"))
|
||||
.create()
|
||||
);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
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) {
|
||||
e.printStackTrace();
|
||||
player.sendMessage("Error");
|
||||
}
|
||||
}
|
||||
|
||||
private void testPlay(Player player, String hash, int bitrate) {
|
||||
var channel = new LocationalChannel(player.getLocation()).getAudioChannel();
|
||||
|
||||
if (opusFilePlayer != null) {
|
||||
opusFilePlayer.stop();
|
||||
}
|
||||
|
||||
try {
|
||||
opusFilePlayer = new OpusFilePlayer(new OpusFile(MusicPlugin.getStorage().get(hash, bitrate).toFile()), channel);
|
||||
opusFilePlayer.play();
|
||||
opusFilePlayer.setOnTrackEvent(e -> {
|
||||
player.sendMessage("Event: " + e);
|
||||
});
|
||||
player.sendMessage("Now playing");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
player.sendMessage("Error");
|
||||
}
|
||||
}
|
||||
}
|
56
src/main/java/eu/m724/music_plugin/audio/Converter.java
Normal file
56
src/main/java/eu/m724/music_plugin/audio/Converter.java
Normal file
|
@ -0,0 +1,56 @@
|
|||
package eu.m724.music_plugin.audio;
|
||||
|
||||
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;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class Converter {
|
||||
private final FFmpeg ffmpeg;
|
||||
|
||||
public Converter(FFmpeg ffmpeg) {
|
||||
this.ffmpeg = ffmpeg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a media file to OPUS with given bitrate.
|
||||
*
|
||||
* @param bitrate the target bitrate in Bps (bits per second)
|
||||
* @return the future that results in {@code output} when done
|
||||
* @throws IOException if something went wrong with FFmpeg
|
||||
*/
|
||||
public CompletableFuture<Path> convert(Path input, Path output, int bitrate) throws IOException {
|
||||
return convert(input.toAbsolutePath().toString(), output, bitrate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a media file to OPUS with given bitrate.
|
||||
*
|
||||
* @deprecated It's better to use this plugin to download
|
||||
* @param bitrate the target bitrate in Bps (bits per second)
|
||||
* @return the future that results in {@code output} when done
|
||||
* @throws IOException if something went wrong with FFmpeg
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public CompletableFuture<Path> convert(URL source, Path output, int bitrate) throws IOException {
|
||||
return convert(source.toString(), output, bitrate);
|
||||
}
|
||||
|
||||
private CompletableFuture<Path> convert(String input, Path output, int bitrate) throws IOException {
|
||||
var builder = new FFmpegBuilder()
|
||||
.setInput(input)
|
||||
.addOutput(output.toAbsolutePath().toUri())
|
||||
.setAudioChannels(1)
|
||||
.setAudioSampleRate(48_000)
|
||||
.setAudioBitRate(bitrate)
|
||||
.done();
|
||||
|
||||
var executor = new FFmpegExecutor(ffmpeg);
|
||||
return CompletableFuture.runAsync(executor.createJob(builder)).thenApply(v -> output);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
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.OpusFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class OpusFileEncoder implements OpusEncoder {
|
||||
private final OpusFile opusFile;
|
||||
private boolean closed = false;
|
||||
|
||||
public OpusFileEncoder(OpusFile file) {
|
||||
this.opusFile = file;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetState() {
|
||||
// TODO
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return closed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
DebugLogger.fine("opus file just closed");
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
|
||||
try {
|
||||
opusFile.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Closing opus file", e);
|
||||
}
|
||||
}
|
||||
|
||||
/* SUPPLIER */
|
||||
|
||||
private final short[] array = new short[960];
|
||||
|
||||
public short[] supplier() {
|
||||
return !closed ? array : null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package eu.m724.music_plugin.audio.channel;
|
||||
|
||||
import de.maxhenkel.voicechat.api.audiochannel.AudioChannel;
|
||||
|
||||
public abstract class AbstractChannel<T extends AudioChannel> {
|
||||
protected T channel;
|
||||
|
||||
public T getAudioChannel() {
|
||||
if (this.channel == null)
|
||||
this.channel = doCreateChannel();
|
||||
return this.channel;
|
||||
}
|
||||
|
||||
protected abstract T doCreateChannel();
|
||||
abstract void setDistance(int distance);
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package eu.m724.music_plugin.audio.channel;
|
||||
|
||||
import de.maxhenkel.voicechat.api.audiochannel.EntityAudioChannel;
|
||||
import eu.m724.music_plugin.MusicPlugin;
|
||||
import org.bukkit.entity.Entity;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class EntityChannel extends AbstractChannel<EntityAudioChannel> {
|
||||
private final Entity entity;
|
||||
|
||||
public EntityChannel(Entity entity) {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EntityAudioChannel doCreateChannel() {
|
||||
var api = MusicPlugin.getVoicechatApi();
|
||||
|
||||
var channel = api.createEntityAudioChannel(
|
||||
UUID.randomUUID(),
|
||||
api.fromEntity(entity)
|
||||
);
|
||||
|
||||
channel.setCategory("music");
|
||||
channel.setDistance(32);
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDistance(int distance) {
|
||||
// we go getChannel() not just channel, because getChannel() creates it if it doesn't exist
|
||||
getAudioChannel().setDistance(distance);
|
||||
}
|
||||
}
|
|
@ -1,27 +1,29 @@
|
|||
package eu.m724.musicPlugin.player;
|
||||
package eu.m724.music_plugin.audio.channel;
|
||||
|
||||
import de.maxhenkel.voicechat.api.audiochannel.AudioChannel;
|
||||
import de.maxhenkel.voicechat.api.audiochannel.LocationalAudioChannel;
|
||||
import eu.m724.music_plugin.MusicPlugin;
|
||||
import org.bukkit.Location;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class StaticMusicPlayer extends MusicPlayer {
|
||||
public class LocationalChannel extends AbstractChannel<LocationalAudioChannel> {
|
||||
private final Location location;
|
||||
|
||||
public StaticMusicPlayer(Location location) {
|
||||
public LocationalChannel(Location location) {
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
@Override
|
||||
AudioChannel createChannel() {
|
||||
protected LocationalAudioChannel doCreateChannel() {
|
||||
var api = MusicPlugin.getVoicechatApi();
|
||||
|
||||
var channel = api.createLocationalAudioChannel(
|
||||
UUID.randomUUID(),
|
||||
api.fromServerLevel(location.getWorld()),
|
||||
api.createPosition(location.getX(), location.getY(), location.getZ())
|
||||
);
|
||||
|
||||
channel.setCategory("musicc");
|
||||
channel.setCategory("music");
|
||||
channel.setDistance(32);
|
||||
|
||||
return channel;
|
||||
|
@ -29,6 +31,7 @@ public class StaticMusicPlayer extends MusicPlayer {
|
|||
|
||||
@Override
|
||||
public void setDistance(int distance) {
|
||||
((LocationalAudioChannel)this.channel).setDistance(distance);
|
||||
// we go getChannel() not just channel, because getChannel() creates it if it doesn't exist
|
||||
getAudioChannel().setDistance(distance);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
package eu.m724.music_plugin.audio.player;
|
||||
|
||||
import de.maxhenkel.voicechat.api.audiochannel.AudioChannel;
|
||||
import de.maxhenkel.voicechat.api.audiochannel.AudioPlayer;
|
||||
import eu.m724.music_plugin.DebugLogger;
|
||||
import eu.m724.music_plugin.MusicPlugin;
|
||||
import eu.m724.music_plugin.audio.OpusFileEncoder;
|
||||
import org.gagravarr.opus.OpusFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class OpusFilePlayer {
|
||||
private final OpusFileEncoder encoder;
|
||||
|
||||
private AudioChannel channel;
|
||||
private AudioPlayer audioPlayer;
|
||||
private boolean playing = false;
|
||||
|
||||
private Consumer<TrackEvent> onTrackEvent = (r) -> {};
|
||||
|
||||
public OpusFilePlayer(OpusFile opusFile, AudioChannel channel) {
|
||||
this.encoder = new OpusFileEncoder(opusFile);
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
public OpusFilePlayer(File file, AudioChannel channel) throws IOException {
|
||||
this(new OpusFile(file), channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the consumer that will be called after playback is paused, stopped, resumed etc<br>
|
||||
* There can be only one for one music player
|
||||
*
|
||||
* @see TrackEvent
|
||||
*/
|
||||
public void setOnTrackEvent(Consumer<TrackEvent> onTrackEvent) {
|
||||
this.onTrackEvent = onTrackEvent;
|
||||
}
|
||||
|
||||
public void setChannel(AudioChannel channel) {
|
||||
DebugLogger.finer("Changing channel...");
|
||||
this.channel = channel;
|
||||
|
||||
if (!playing) {
|
||||
// to not call events pointlessly
|
||||
var ote = this.onTrackEvent;
|
||||
this.onTrackEvent = (r) -> {};
|
||||
|
||||
// playing recreates the audio channel
|
||||
pause();
|
||||
play();
|
||||
|
||||
this.onTrackEvent = ote;
|
||||
}
|
||||
|
||||
DebugLogger.fine("Channel changed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts or resumes playback
|
||||
*/
|
||||
public void play() {
|
||||
DebugLogger.finer("Playback starting...");
|
||||
if (playing) return;
|
||||
|
||||
audioPlayer = MusicPlugin.getVoicechatApi().createAudioPlayer(channel, encoder, encoder::supplier);
|
||||
audioPlayer.setOnStopped(this::onPlayerStopped);
|
||||
audioPlayer.startPlaying();
|
||||
|
||||
playing = true;
|
||||
|
||||
onTrackEvent.accept(TrackEvent.START);
|
||||
DebugLogger.fine("Playback started");
|
||||
}
|
||||
|
||||
/**
|
||||
* An alias for {@link #play()}
|
||||
*/
|
||||
public void unpause() {
|
||||
play();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause playback<br>
|
||||
* To resume, {@link #play()}
|
||||
*/
|
||||
public void pause() {
|
||||
stop(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops playback (pauses and rewinds)
|
||||
*/
|
||||
public void stop() {
|
||||
stop(true);
|
||||
}
|
||||
|
||||
private void stop(boolean reset) {
|
||||
DebugLogger.fine("Playback stopping... (reset: %s)", reset);
|
||||
if (!playing) return;
|
||||
|
||||
playing = false;
|
||||
audioPlayer.stopPlaying();
|
||||
|
||||
if (reset)
|
||||
encoder.resetState();
|
||||
|
||||
DebugLogger.fine("Playback stopped (reset: %s)", reset);
|
||||
}
|
||||
|
||||
private void onPlayerStopped() {
|
||||
if (playing) {
|
||||
DebugLogger.fine("onPlayerStopped called and playing, calling END");
|
||||
onTrackEvent.accept(TrackEvent.END);
|
||||
} else {
|
||||
DebugLogger.fine("onPlayerStopped called and not playing, calling STOP");
|
||||
onTrackEvent.accept(TrackEvent.STOP);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Is player playing (not paused)
|
||||
*/
|
||||
public boolean isPlaying() {
|
||||
return playing;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package eu.m724.music_plugin.audio.player;
|
||||
|
||||
public enum TrackEvent {
|
||||
/**
|
||||
* Track ended
|
||||
*/
|
||||
END,
|
||||
/**
|
||||
* Track was stopped (or paused)<br>
|
||||
* If it ended, {@link TrackEvent#END} is used instead
|
||||
*/
|
||||
STOP,
|
||||
/**
|
||||
* Track started playing (or unpaused)
|
||||
*/
|
||||
START
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
package eu.m724.music_plugin.audio.storage;
|
||||
|
||||
import eu.m724.music_plugin.audio.Converter;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NotDirectoryException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class AudioFileStorage {
|
||||
private final Path basePath;
|
||||
|
||||
public AudioFileStorage(Path basePath) {
|
||||
this.basePath = basePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get versions (bitrates) of an audio file<br>
|
||||
* Returns a map of bitrate: file, or null if no such hash<br>
|
||||
* Original is 0
|
||||
*
|
||||
* @param hash TODO NOT SANITIZED
|
||||
* @return Map of Bps to file, or null if no such hash
|
||||
*/
|
||||
public Map<Integer, Path> getVersions(String hash) throws IOException {
|
||||
assert isHashValid(hash);
|
||||
|
||||
var dir = basePath.resolve(hash);
|
||||
|
||||
try (var files = Files.list(dir)) {
|
||||
return files.collect(Collectors.toMap(
|
||||
k -> Integer.parseInt(k.getFileName().toString().split("\\.")[0]),
|
||||
v -> v
|
||||
));
|
||||
} catch (NotDirectoryException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an audio file of given original hash and bitrate
|
||||
*
|
||||
* @param hash the hex sha256 hash of the original
|
||||
* @param bitrate the bitrate in bps like 32000
|
||||
*/
|
||||
public Path get(String hash, int bitrate) {
|
||||
assert isHashValid(hash);
|
||||
return basePath.resolve(hash).resolve(bitrate + ".opus");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the original file with a hash
|
||||
*/
|
||||
public Path getOriginal(String hash) {
|
||||
assert isHashValid(hash);
|
||||
return basePath.resolve(hash).resolve("0.original");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a hash is a valid SHA256 hash
|
||||
* @param hash the hash in hex format
|
||||
*/
|
||||
private boolean isHashValid(String hash) {
|
||||
var bytes = HexFormat.of().parseHex(hash);
|
||||
return bytes.length == 32;
|
||||
}
|
||||
|
||||
// TODO move those
|
||||
|
||||
/**
|
||||
* Converts a (original) file to some bitrate
|
||||
*
|
||||
* @param hash the hash
|
||||
* @param bitrate the target bitrate in bps
|
||||
* @return the future with the new file
|
||||
*/
|
||||
public CompletableFuture<Path> convert(Converter converter, String hash, int bitrate) throws IOException {
|
||||
var file = get(hash, bitrate);
|
||||
if (Files.exists(file)) return CompletableFuture.completedFuture(file);
|
||||
|
||||
var og = getOriginal(hash);
|
||||
if (!Files.exists(og)) return null;
|
||||
|
||||
return converter.convert(og, file, bitrate).thenApply(v -> file);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package eu.m724.music_plugin.audio.storage;
|
||||
|
||||
import eu.m724.music_plugin.DebugLogger;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
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.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.HexFormat;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
|
||||
public class Downloader {
|
||||
private final Path basePath;
|
||||
|
||||
public Downloader(Path basePath) {
|
||||
this.basePath = basePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads an audio file and saves it as original
|
||||
*
|
||||
* @param uri the uri to download from
|
||||
* @return the downloaded file's SHA-256 hash (in a future)
|
||||
*/
|
||||
public CompletableFuture<String> download(URI uri) {
|
||||
DebugLogger.fine("About to download from " + 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);
|
||||
}
|
||||
}
|
||||
|
||||
private String futureFunction(HttpResponse<InputStream> response) {
|
||||
var contentLength = response.headers().firstValue("Content-Length");
|
||||
DebugLogger.finer("Connected. Size: %s", contentLength.orElse(null));
|
||||
|
||||
var temp = basePath.resolve("temp_" + RandomStringUtils.randomAlphabetic(8));
|
||||
MessageDigest digest;
|
||||
try {
|
||||
digest = MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
try (
|
||||
var input = response.body();
|
||||
var output = Files.newOutputStream(temp)
|
||||
) {
|
||||
var dataBuffer = new byte[1024 * 1024];
|
||||
int bytesRead;
|
||||
while ((bytesRead = input.read(dataBuffer, 0, dataBuffer.length)) != -1) {
|
||||
output.write(dataBuffer, 0, bytesRead);
|
||||
digest.update(dataBuffer, 0, bytesRead);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("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!");
|
||||
} catch (IOException e) {
|
||||
throw new CompletionException("Exception saving file", e);
|
||||
}
|
||||
|
||||
DebugLogger.fine("Done");
|
||||
return hash;
|
||||
}
|
||||
}
|
|
@ -1,22 +1,13 @@
|
|||
package eu.m724.musicPlugin.item;
|
||||
package eu.m724.music_plugin.item;
|
||||
|
||||
import eu.m724.musicPlugin.item.speaker.BlockSpeaker;
|
||||
import eu.m724.musicPlugin.item.speaker.Speaker;
|
||||
import eu.m724.musicPlugin.player.MovingMusicPlayer;
|
||||
import eu.m724.music_plugin.item.speaker.BlockSpeaker;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Item;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.event.block.BlockDamageEvent;
|
||||
import org.bukkit.event.entity.EntityDeathEvent;
|
||||
import org.bukkit.event.entity.EntityDropItemEvent;
|
||||
import org.bukkit.event.entity.EntityPickupItemEvent;
|
||||
import org.bukkit.event.entity.ItemDespawnEvent;
|
||||
import org.bukkit.event.inventory.InventoryMoveItemEvent;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
import org.bukkit.event.player.PlayerDropItemEvent;
|
|
@ -1,8 +1,10 @@
|
|||
package eu.m724.musicPlugin.item;
|
||||
package eu.m724.music_plugin.item;
|
||||
|
||||
import eu.m724.musicPlugin.file.AudioFile;
|
||||
import eu.m724.musicPlugin.item.speaker.Speaker;
|
||||
import eu.m724.musicPlugin.player.MusicPlayer;
|
||||
import eu.m724.music_plugin.DebugLogger;
|
||||
import eu.m724.music_plugin.MusicPlugin;
|
||||
import eu.m724.music_plugin.audio.player.OpusFilePlayer;
|
||||
import eu.m724.music_plugin.audio.player.TrackEvent;
|
||||
import eu.m724.music_plugin.item.speaker.Speaker;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
|
@ -11,14 +13,16 @@ import org.bukkit.inventory.ItemFlag;
|
|||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class PortableMediaPlayer {
|
||||
static NamespacedKey idKey = new NamespacedKey("tweaks724", "player_id");
|
||||
static NamespacedKey dataKey = new NamespacedKey("tweaks724", "player_data");
|
||||
static final NamespacedKey idKey = MusicPlugin.getNamespacedKey("player_id");
|
||||
static final NamespacedKey dataKey = MusicPlugin.getNamespacedKey("player_data");;
|
||||
|
||||
public final int id;
|
||||
|
||||
|
@ -29,8 +33,8 @@ public class PortableMediaPlayer {
|
|||
public final boolean premium;
|
||||
public final String engraving;
|
||||
|
||||
private PlayedSong played;
|
||||
private Speaker speaker;
|
||||
private OpusFilePlayer player; // TODO rename?
|
||||
private Speaker<?> speaker;
|
||||
|
||||
PortableMediaPlayer(int id, int storageSeconds, int audioBitrate, boolean premium, String engraving) {
|
||||
this.id = id;
|
||||
|
@ -44,7 +48,7 @@ public class PortableMediaPlayer {
|
|||
return new PortableMediaPlayer(ThreadLocalRandom.current().nextInt(), 600, 32000, premium, engraving);
|
||||
}
|
||||
|
||||
public void setSpeaker(Speaker speaker) {
|
||||
public void setSpeaker(Speaker<?> speaker) {
|
||||
if (speaker.equals(this.speaker))
|
||||
return;
|
||||
|
||||
|
@ -54,80 +58,73 @@ public class PortableMediaPlayer {
|
|||
this.speaker.destroy();
|
||||
}
|
||||
|
||||
speaker.setDestroyCallback(c -> {
|
||||
speaker.setDestroyCallback(v -> {
|
||||
System.out.println("spekar rip");
|
||||
if (played != null)
|
||||
played.pause(); // this one stops the tracker not playback, which is stopped in Speaker
|
||||
if (player != null)
|
||||
player.pause();
|
||||
this.speaker = null;
|
||||
});
|
||||
|
||||
this.speaker = speaker;
|
||||
|
||||
if (played != null && !played.paused()) {
|
||||
unpause();
|
||||
}
|
||||
player.setChannel(speaker.getChannel().getAudioChannel());
|
||||
}
|
||||
|
||||
public void play(AudioFile audioFile) {
|
||||
public void play(File file) throws IOException {
|
||||
DebugLogger.finer("pmp play");
|
||||
if (speaker == null) return;
|
||||
|
||||
played = new PlayedSong(audioFile);
|
||||
//played = new Track(audioFile);
|
||||
if (player != null)
|
||||
player.stop();
|
||||
|
||||
speaker.getMusicPlayer().setOnAction(action -> {
|
||||
if (action == MusicPlayer.TrackAction.PAUSE) {
|
||||
// track paused
|
||||
System.out.println("Okay its paused");
|
||||
played.pause();
|
||||
played.hint(played.file.getEncoder().getFrame());
|
||||
} else if (action == MusicPlayer.TrackAction.UNPAUSE) { // track unpaused
|
||||
System.out.println("Okay its unpaused");
|
||||
played.unpause();
|
||||
} else if (action == MusicPlayer.TrackAction.DURATION) { // track ended
|
||||
System.out.println("Okay its ened");
|
||||
next();
|
||||
this.player = new OpusFilePlayer(file, speaker.getChannel().getAudioChannel());
|
||||
|
||||
player.setOnTrackEvent(event -> {
|
||||
if (event == TrackEvent.START) {
|
||||
DebugLogger.finer("I detected track START");
|
||||
//played.unpause();
|
||||
} else if (event == TrackEvent.STOP) {
|
||||
DebugLogger.finer("I detected track STOP");
|
||||
//played.pause();
|
||||
//played.hint(played.file.getEncoder().getFrame());
|
||||
} else if (event == TrackEvent.END) {
|
||||
DebugLogger.finer("I detected track END");
|
||||
//next();
|
||||
}
|
||||
});
|
||||
|
||||
unpause();
|
||||
player.play();
|
||||
}
|
||||
|
||||
void pause() {
|
||||
System.out.println("pmp pase");
|
||||
if (played == null || speaker == null) return;
|
||||
System.out.println("pmp pasedd");
|
||||
DebugLogger.finer("pmp pause");
|
||||
|
||||
speaker.getMusicPlayer().pause();
|
||||
if (player != null)
|
||||
player.pause();
|
||||
}
|
||||
|
||||
void unpause() {
|
||||
System.out.println("pmp unpase");
|
||||
if (played == null || speaker == null) return;
|
||||
System.out.println("pmp unpasedd");
|
||||
DebugLogger.finer("pmp unpause");
|
||||
|
||||
speaker.getMusicPlayer().setAudio(played.file);
|
||||
speaker.getMusicPlayer().seek(played.getProgress());
|
||||
speaker.getMusicPlayer().unpause();
|
||||
if (player != null)
|
||||
player.unpause();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
System.out.println("pmp stop");
|
||||
DebugLogger.finer("pmp stop");
|
||||
|
||||
if (speaker != null)
|
||||
speaker.destroy();
|
||||
|
||||
this.played = null;
|
||||
if (player != null)
|
||||
player.stop();
|
||||
}
|
||||
|
||||
// TODO
|
||||
void prev() {
|
||||
System.out.println("pmp prev");
|
||||
this.played = new PlayedSong(played.file);
|
||||
unpause();
|
||||
DebugLogger.fine("pmp previous (does nothing)");
|
||||
}
|
||||
|
||||
// TODO
|
||||
void next() {
|
||||
System.out.println("pmp next");
|
||||
this.played = null;
|
||||
// TODO
|
||||
System.out.println("pmp next (does nothing)");
|
||||
}
|
||||
|
||||
/* Item functions */
|
||||
|
@ -136,7 +133,7 @@ public class PortableMediaPlayer {
|
|||
var is = new ItemStack(Material.IRON_INGOT);
|
||||
var meta = is.getItemMeta();
|
||||
|
||||
meta.setItemName("Portable music player");
|
||||
meta.setItemName("Portable Music Player");
|
||||
meta.addEnchant(Enchantment.UNBREAKING, 1, false);
|
||||
meta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
meta.getPersistentDataContainer().set(idKey, PersistentDataType.INTEGER, id);
|
||||
|
@ -151,7 +148,7 @@ public class PortableMediaPlayer {
|
|||
|
||||
private byte[] getData() {
|
||||
var buffer = ByteBuffer.allocate(11 + engraving.length());
|
||||
buffer.put((byte) 0); // version
|
||||
buffer.put((byte) 0); // data format version
|
||||
buffer.put((byte) (premium ? 1 : 0));
|
||||
|
||||
buffer.putShort((short) storageSeconds); // make int if 18 hours is not enough. or store as minutes
|
|
@ -1,4 +1,4 @@
|
|||
package eu.m724.musicPlugin.item;
|
||||
package eu.m724.music_plugin.item;
|
||||
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
|
@ -1,4 +1,4 @@
|
|||
package eu.m724.musicPlugin.item.speaker;
|
||||
package eu.m724.music_plugin.item.speaker;
|
||||
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
|
@ -1,17 +1,16 @@
|
|||
package eu.m724.musicPlugin.item.speaker;
|
||||
package eu.m724.music_plugin.item.speaker;
|
||||
|
||||
import eu.m724.musicPlugin.Statics;
|
||||
import eu.m724.musicPlugin.player.StaticMusicPlayer;
|
||||
import eu.m724.music_plugin.DebugLogger;
|
||||
import eu.m724.music_plugin.MusicPlugin;
|
||||
import eu.m724.music_plugin.audio.channel.LocationalChannel;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.metadata.FixedMetadataValue;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class BlockSpeaker extends Speaker {
|
||||
public class BlockSpeaker extends Speaker<LocationalChannel> {
|
||||
static final Map<Location, BlockSpeaker> speakers = new HashMap<>();
|
||||
|
||||
public static BlockSpeaker get(Location location) {
|
||||
|
@ -25,7 +24,8 @@ public class BlockSpeaker extends Speaker {
|
|||
return null;
|
||||
|
||||
var isTopSpeaker = below.getBlock().getType() == Material.NOTE_BLOCK;
|
||||
System.out.println("SPekaer is top: " + isTopSpeaker);
|
||||
DebugLogger.fine("SPekaer is top: " + isTopSpeaker);
|
||||
|
||||
return speakers.compute(isTopSpeaker ? below : location, (k, v) -> new BlockSpeaker(k));
|
||||
}
|
||||
|
||||
|
@ -35,9 +35,9 @@ public class BlockSpeaker extends Speaker {
|
|||
private boolean large;
|
||||
|
||||
public BlockSpeaker(Location location) {
|
||||
super(new StaticMusicPlayer(location));
|
||||
super(new LocationalChannel(location));
|
||||
this.location = location;
|
||||
this.location.getBlock().setMetadata("t_speaker", new FixedMetadataValue(Statics.plugin, true));
|
||||
this.location.getBlock().setMetadata("t_speaker", new FixedMetadataValue(MusicPlugin.getInstance(), true));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -50,17 +50,18 @@ public class BlockSpeaker extends Speaker {
|
|||
if (location.clone().add(0, 1, 0).getBlock().getType() == Material.NOTE_BLOCK) {
|
||||
if (!large) {
|
||||
System.out.println("Speaker now large");
|
||||
getMusicPlayer().setDistance(64);
|
||||
getChannel().setDistance(64);
|
||||
this.large = true;
|
||||
}
|
||||
} else {
|
||||
if (large) {
|
||||
System.out.println("Speaker now small");
|
||||
getMusicPlayer().setDistance(32);
|
||||
getChannel().setDistance(32);
|
||||
this.large = false;
|
||||
}
|
||||
}
|
||||
if (getMusicPlayer().isPlaying()) {
|
||||
// TODO bring it back
|
||||
/*if (getMusicPlayer().isPlaying()) {
|
||||
if (large) {
|
||||
location.getWorld().spawnParticle(Particle.NOTE, location.clone().add(0.6, 0.5, 0), 1, 0, ThreadLocalRandom.current().nextDouble(-1, 0.5), ThreadLocalRandom.current().nextDouble(-0.4, 0.4));
|
||||
location.getWorld().spawnParticle(Particle.NOTE, location.clone().add(-0.6, 0.5, 0), 1, 0, ThreadLocalRandom.current().nextDouble(-1, 0.5), ThreadLocalRandom.current().nextDouble(-0.4, 0.4));
|
||||
|
@ -69,7 +70,7 @@ public class BlockSpeaker extends Speaker {
|
|||
} else {
|
||||
location.getWorld().spawnParticle(Particle.NOTE, location.clone().add(0, 0.7, 0), 1, ThreadLocalRandom.current().nextDouble(-0.4, 0.4), 0, ThreadLocalRandom.current().nextDouble(-0.4, 0.4));
|
||||
}
|
||||
}
|
||||
}*/
|
||||
} else {
|
||||
System.out.println("Speaker disaper");
|
||||
destroy();
|
29
src/main/java/eu/m724/music_plugin/item/speaker/Speaker.java
Normal file
29
src/main/java/eu/m724/music_plugin/item/speaker/Speaker.java
Normal file
|
@ -0,0 +1,29 @@
|
|||
package eu.m724.music_plugin.item.speaker;
|
||||
|
||||
import eu.m724.music_plugin.audio.channel.AbstractChannel;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public abstract class Speaker<T extends AbstractChannel<?>> {
|
||||
private final T channel;
|
||||
private Consumer<Void> destroyCallback;
|
||||
|
||||
protected Speaker(T channel) {
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
onDestroy();
|
||||
destroyCallback.accept(null);
|
||||
}
|
||||
|
||||
public void setDestroyCallback(Consumer<Void> consumer) {
|
||||
this.destroyCallback = consumer;
|
||||
}
|
||||
|
||||
public T getChannel() {
|
||||
return this.channel;
|
||||
}
|
||||
|
||||
abstract void onDestroy();
|
||||
}
|
21
src/main/java/eu/m724/music_plugin/library/Library.java
Normal file
21
src/main/java/eu/m724/music_plugin/library/Library.java
Normal file
|
@ -0,0 +1,21 @@
|
|||
package eu.m724.music_plugin.library;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Library {
|
||||
private List<Track> tracks = new ArrayList<>();
|
||||
private int playingTrack;
|
||||
|
||||
public void addTrack(Track track) {
|
||||
tracks.add(track);
|
||||
}
|
||||
|
||||
public void removeTrack(Track track) {
|
||||
tracks.remove(track);
|
||||
}
|
||||
|
||||
public Track getPlayingTrack() {
|
||||
return tracks.get(playingTrack);
|
||||
}
|
||||
}
|
|
@ -1,14 +1,14 @@
|
|||
package eu.m724.musicPlugin.item;
|
||||
package eu.m724.music_plugin.library;
|
||||
|
||||
import eu.m724.musicPlugin.file.AudioFile;
|
||||
import java.io.File;
|
||||
|
||||
public class PlayedSong {
|
||||
public final AudioFile file;
|
||||
public class Track {
|
||||
public final File file;
|
||||
|
||||
private int progress = 0;
|
||||
private long started = -1;
|
||||
|
||||
public PlayedSong(AudioFile file) {
|
||||
public Track(File file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
name: music-plugin
|
||||
version: 1
|
||||
main: eu.m724.musicPlugin.MusicPlugin
|
||||
api-version: '1.21'
|
||||
version: ${project.version}
|
||||
main: eu.m724.music_plugin.MusicPlugin
|
||||
api-version: '1.21.1'
|
||||
|
||||
depend: [voicechat]
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue