Skip to content

Commit 79d2e24

Browse files
committed
VM Support
1 parent 5c7677f commit 79d2e24

File tree

9 files changed

+168
-15
lines changed

9 files changed

+168
-15
lines changed

TODO

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
Development Milestones
2-
- Fix Virtual Machine Support
32
- Build QEMU Statically for All Platforms
4-
5-
- Add Images and GIF Support into Plugin
63
- Add Interaction Support for Virtual Machine
74
- Add Interaction Support for Browser
85

6+
- Add Images and GIF Support into Plugin
7+
98
Public Milestones
109
- Update video showcase in the README (music discs for VM, etc)
1110
- Post onto Reddit (admincraft, sysadmin, etc..)

mcav-common/src/main/java/me/brandonli/mcav/media/player/vm/VMPlayerImpl.java

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020
import static java.util.Objects.requireNonNull;
2121

2222
import java.io.IOException;
23+
import java.net.Socket;
2324
import java.util.ArrayList;
2425
import java.util.List;
26+
import me.brandonli.mcav.media.player.PlayerException;
2527
import me.brandonli.mcav.media.player.metadata.VideoMetadata;
2628
import me.brandonli.mcav.media.player.pipeline.step.VideoPipelineStep;
2729
import me.brandonli.mcav.media.player.vnc.VNCPlayer;
@@ -84,18 +86,32 @@ private void startQemuProcess() {
8486
final String[] arguments = command.toArray(new String[0]);
8587
final ProcessBuilder processBuilder = new ProcessBuilder(arguments);
8688
this.qemuProcess = processBuilder.start();
87-
try {
88-
Thread.sleep(10000);
89-
} catch (final InterruptedException e) {
90-
final Thread currentThread = Thread.currentThread();
91-
currentThread.interrupt();
92-
throw new AssertionError(e);
93-
}
89+
waitForConnection();
9490
} catch (final IOException e) {
9591
throw new UncheckedIOException(e.getMessage(), e);
9692
}
9793
}
9894

95+
private void waitForConnection() {
96+
long timeout = System.currentTimeMillis() + 30000;
97+
boolean connected = false;
98+
while (System.currentTimeMillis() < timeout && !connected) {
99+
try {
100+
Socket socket = new Socket("localhost", this.vncPort);
101+
socket.close();
102+
connected = true;
103+
} catch (final IOException e) {
104+
try {
105+
Thread.sleep(500);
106+
} catch (final InterruptedException ex) {
107+
Thread thread = Thread.currentThread();
108+
thread.interrupt();
109+
throw new PlayerException(ex.getMessage(), ex);
110+
}
111+
}
112+
}
113+
}
114+
99115
private List<String> formatQemuArguments() {
100116
final List<String> command = new ArrayList<>();
101117
command.add(this.architecture.getCommand());

mcav-common/src/main/java/me/brandonli/mcav/media/player/vnc/VNCPlayerImpl.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,15 @@ private VernacularConfig createConfig(final VNCSource source) {
112112
}
113113

114114
private void processFrames() {
115+
final int width = this.videoMetadata.getVideoWidth();
116+
final int height = this.videoMetadata.getVideoHeight();
115117
while (this.running.get()) {
116118
try {
117119
if (this.current == null) {
118120
continue;
119121
}
120122
final StaticImage image = StaticImage.image(this.current);
123+
image.resize(width, height);
121124
VideoPipelineStep current = this.videoPipeline;
122125
while (current != null) {
123126
current.process(image, this.videoMetadata);

mcav-common/src/test/java/me/brandonli/mcav/VMInputExample.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ private void startVM(final JFrame frame, final String isoPath) throws Exception
107107

108108
final VMConfiguration config = VMConfiguration.builder().cdrom(isoPath).memory(2048);
109109

110-
this.vmPlayer.startAsync(pipeline, VMPlayer.Architecture.X86_64, config, VideoMetadata.of(600, 800));
110+
this.vmPlayer.start(pipeline, VMPlayer.Architecture.X86_64, config, VideoMetadata.of(600, 800));
111111
}
112112

113113
private void cleanup() {

sandbox/src/main/java/me/brandonli/mcav/sandbox/MCAVSandbox.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@
2626
import me.brandonli.mcav.sandbox.command.AnnotationParserHandler;
2727
import me.brandonli.mcav.sandbox.command.video.VideoPlayerManager;
2828
import me.brandonli.mcav.sandbox.data.PluginDataConfigurationMapper;
29+
import me.brandonli.mcav.sandbox.listener.JukeBoxListener;
2930
import org.bukkit.plugin.java.JavaPlugin;
3031

3132
public final class MCAVSandbox extends JavaPlugin {
3233

3334
private Logger logger;
3435

3536
private MCAVApi mcav;
37+
private JukeBoxListener listener;
3638
private AudioProvider audioProvider;
3739
private VideoPlayerManager videoPlayerManager;
3840
private AnnotationParserHandler annotationParserHandler;
@@ -55,6 +57,7 @@ public void onEnable() {
5557
this.loadPluginData();
5658
this.loadManager();
5759
this.loadCommands();
60+
this.loadListeners();
5861
this.initLookupTables();
5962
}
6063

@@ -86,11 +89,21 @@ private void loadPluginData() {
8689
@Override
8790
public void onDisable() {
8891
this.shutdownLookupTables();
92+
this.unloadListeners();
8993
this.shutdownCommands();
9094
this.saveData();
9195
this.unloadMCAV();
9296
}
9397

98+
private void unloadListeners() {
99+
this.listener.shutdown();
100+
}
101+
102+
private void loadListeners() {
103+
this.listener = new JukeBoxListener(this);
104+
this.listener.start();
105+
}
106+
94107
private void shutdownCommands() {
95108
if (this.annotationParserHandler != null) {
96109
this.annotationParserHandler.shutdownCommands();

sandbox/src/main/java/me/brandonli/mcav/sandbox/command/VirtualizeCommand.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,14 +121,15 @@ public void playVM(
121121
final VideoMetadata metadata = VideoMetadata.of(resolutionWidth, resolutionHeight);
122122
final VMConfiguration config = this.parseVMOptions(flags);
123123

124+
player.sendMessage(Message.VM_LOADING.build());
124125
try {
125126
this.vmPlayer = VMPlayer.vm();
126-
this.vmPlayer.startAsync(pipeline, architecture, config, metadata, this.service);
127+
this.vmPlayer.startAsync(pipeline, architecture, config, metadata, this.service).thenRun(() ->
128+
player.sendMessage(Message.VM_CREATE.build())
129+
);
127130
} catch (final Exception e) {
128131
throw new AssertionError(e);
129132
}
130-
131-
player.sendMessage(Message.VM_CREATE.build());
132133
}
133134

134135
private VMConfiguration parseVMOptions(final String commandLine) {
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* This file is part of mcav, a media playback library for Minecraft
3+
* Copyright (C) Brandon Li <https://brandonli.me/>
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
package me.brandonli.mcav.sandbox.listener;
19+
20+
import java.nio.file.Files;
21+
import java.nio.file.Path;
22+
import java.util.Set;
23+
import java.util.stream.Collectors;
24+
import me.brandonli.mcav.sandbox.MCAVSandbox;
25+
import me.brandonli.mcav.utils.IOUtils;
26+
import net.kyori.adventure.text.Component;
27+
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
28+
import org.bukkit.Bukkit;
29+
import org.bukkit.Material;
30+
import org.bukkit.Registry;
31+
import org.bukkit.Server;
32+
import org.bukkit.block.Block;
33+
import org.bukkit.entity.Player;
34+
import org.bukkit.event.EventHandler;
35+
import org.bukkit.event.HandlerList;
36+
import org.bukkit.event.Listener;
37+
import org.bukkit.event.block.Action;
38+
import org.bukkit.event.player.PlayerInteractEvent;
39+
import org.bukkit.inventory.ItemStack;
40+
import org.bukkit.plugin.PluginManager;
41+
import org.checkerframework.checker.initialization.qual.UnderInitialization;
42+
43+
public final class JukeBoxListener implements Listener {
44+
45+
private static final PlainTextComponentSerializer PLAIN_TEXT_COMPONENT_SERIALIZER = PlainTextComponentSerializer.plainText();
46+
47+
private static final Set<Material> MUSIC_DISCS = Registry.MATERIAL.stream()
48+
.filter(material -> material.name().startsWith("MUSIC_DISC_"))
49+
.collect(Collectors.toSet());
50+
51+
private final MCAVSandbox sandbox;
52+
private final Path isoPath;
53+
54+
public JukeBoxListener(final MCAVSandbox sandbox) {
55+
this.isoPath = this.createFolder(sandbox);
56+
this.sandbox = sandbox;
57+
}
58+
59+
public void shutdown() {
60+
HandlerList.unregisterAll(this);
61+
}
62+
63+
public void start() {
64+
final Server server = Bukkit.getServer();
65+
final PluginManager pluginManager = server.getPluginManager();
66+
pluginManager.registerEvents(this, this.sandbox);
67+
}
68+
69+
private Path createFolder(@UnderInitialization JukeBoxListener this, final MCAVSandbox sandbox) {
70+
final Path path = sandbox.getDataPath();
71+
final Path iso = path.resolve("iso");
72+
IOUtils.createDirectoryIfNotExists(iso);
73+
return iso;
74+
}
75+
76+
@EventHandler
77+
public void onJukeboxInteract(final PlayerInteractEvent event) {
78+
final Action action = event.getAction();
79+
if (action != Action.RIGHT_CLICK_BLOCK) {
80+
return;
81+
}
82+
83+
final Block clickedBlock = event.getClickedBlock();
84+
if (clickedBlock == null) {
85+
return;
86+
}
87+
88+
final Material blockType = clickedBlock.getType();
89+
if (blockType != Material.JUKEBOX) {
90+
return;
91+
}
92+
93+
final ItemStack itemInHand = event.getItem();
94+
if (itemInHand == null) {
95+
return;
96+
}
97+
98+
final Material itemType = itemInHand.getType();
99+
if (!MUSIC_DISCS.contains(itemType)) {
100+
return;
101+
}
102+
103+
final Component name = itemInHand.displayName();
104+
final String displayName = PLAIN_TEXT_COMPONENT_SERIALIZER.serialize(name);
105+
final String sanitizedName = displayName.replaceAll("[\\\\/:*?\"<>|]", "_");
106+
final String noBrackets = sanitizedName.replaceAll("[\\[\\]]", "");
107+
final Path path = this.isoPath.resolve(noBrackets);
108+
if (Files.notExists(path)) {
109+
return;
110+
}
111+
event.setCancelled(true);
112+
113+
final Path absolute = path.toAbsolutePath();
114+
final String raw = absolute.toString();
115+
final String cmd = "mcav vm create @a 640x640 5x5 0 filter_lite x86_64 -cdrom %s -m 2048M".formatted(raw);
116+
final Player player = event.getPlayer();
117+
player.performCommand(cmd);
118+
}
119+
}

sandbox/src/main/java/me/brandonli/mcav/sandbox/locale/Message.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import static me.brandonli.mcav.sandbox.locale.LocaleTools.direct;
2121

2222
public interface Message extends LocaleTools {
23+
NullComponent<Sender> VM_LOADING = direct("mcav.command.vm.loading");
2324
NullComponent<Sender> VM_CREATE = direct("mcav.command.vm.create");
2425
NullComponent<Sender> VM_RELEASE = direct("mcav.command.vm.release");
2526
UniComponent<Sender, String> AUDIO_HTTP = direct("mcav.command.audio.http", null);

sandbox/src/main/resources/locale/mcav_en_us.properties

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,5 @@ mcav.command.audio.http=<green>Click on the URL to listen onto the website! <aqu
4141
mcav.command.vm.release.info=Releases the virtual machine
4242
mcav.command.vm.create.info=Creates a new virtual machine
4343
mcav.command.vm.release=<gold>Virtual machine released!</gold>
44-
mcav.command.vm.create=<gold>Virtual machine created!</gold>
44+
mcav.command.vm.loading=<gold>Loading virtual machine...</gold>
45+
mcav.command.vm.create=<gold>Created virtual machine!</gold>

0 commit comments

Comments
 (0)