v0.4.6: server config for auto-preload on join
Some checks failed
build / build (push) Has been cancelled
Some checks failed
build / build (push) Has been cancelled
- new: config/video_player.json on first server start. Field preload_urls
is a list of HTTP(S) URLs (≤256 chars each) that the server broadcasts
via PreloadPayload to every player when they finish joining, so common
videos are warmed into each client's video_player_cache/ before they
ever play. Reuses the same PreloadPayload + VideoCache path as
/videopreload, so chat feedback ("[videopreload] 완료") still applies.
- config is loaded once at mod init; invalid entries are dropped with a
WARN line. Edit + restart server to apply changes.
This commit is contained in:
99
src/main/java/com/ejclaw/videoplayer/VideoPlayerConfig.java
Normal file
99
src/main/java/com/ejclaw/videoplayer/VideoPlayerConfig.java
Normal file
@@ -0,0 +1,99 @@
|
||||
package com.ejclaw.videoplayer;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Server-side mod config, stored at {@code <gameDir>/config/video_player.json}.
|
||||
*
|
||||
* <p>Format (auto-generated on first start):
|
||||
* <pre>{@code
|
||||
* {
|
||||
* // List of HTTP(S) video URLs that the server will tell every player to preload
|
||||
* // into their local video_player_cache/ folder when they join. Identical to running
|
||||
* // /videopreload <url> for each joining player.
|
||||
* "preload_urls": [
|
||||
* "https://example.com/intro.mp4"
|
||||
* ]
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* <p>Why a list and not a dedicated tool: the same {@link
|
||||
* com.ejclaw.videoplayer.net.PreloadPayload} that powers {@code /videopreload} is reused, so the
|
||||
* client-side cache, chat feedback, and {@code /videoplace} → cache lookup paths all behave
|
||||
* identically for config-driven and command-driven preloads.
|
||||
*/
|
||||
public final class VideoPlayerConfig {
|
||||
private VideoPlayerConfig() {}
|
||||
|
||||
private static final String FILE_NAME = "video_player.json";
|
||||
private static volatile List<String> PRELOAD_URLS = Collections.emptyList();
|
||||
|
||||
/** Load (or create) the config file. Called once during mod initialization. */
|
||||
public static void load() {
|
||||
Path path = FabricLoader.getInstance().getConfigDir().resolve(FILE_NAME);
|
||||
try {
|
||||
if (!Files.exists(path)) {
|
||||
writeDefault(path);
|
||||
VideoPlayerMod.LOG.info("[{}] created default config at {}",
|
||||
VideoPlayerMod.MOD_ID, path);
|
||||
PRELOAD_URLS = Collections.emptyList();
|
||||
return;
|
||||
}
|
||||
String raw = Files.readString(path, StandardCharsets.UTF_8);
|
||||
JsonObject json = JsonParser.parseString(raw).getAsJsonObject();
|
||||
List<String> urls = new ArrayList<>();
|
||||
if (json.has("preload_urls") && json.get("preload_urls").isJsonArray()) {
|
||||
json.getAsJsonArray("preload_urls").forEach(el -> {
|
||||
if (el.isJsonPrimitive() && el.getAsJsonPrimitive().isString()) {
|
||||
String u = el.getAsString().trim();
|
||||
if (!u.isEmpty() && (u.startsWith("http://") || u.startsWith("https://"))
|
||||
&& u.length() <= 256) {
|
||||
urls.add(u);
|
||||
} else if (!u.isEmpty()) {
|
||||
VideoPlayerMod.LOG.warn(
|
||||
"[{}] config: ignoring invalid preload url '{}' (must be http/https, ≤256 chars)",
|
||||
VideoPlayerMod.MOD_ID, u);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
PRELOAD_URLS = Collections.unmodifiableList(urls);
|
||||
VideoPlayerMod.LOG.info("[{}] config loaded: {} preload url(s)",
|
||||
VideoPlayerMod.MOD_ID, urls.size());
|
||||
} catch (Throwable t) {
|
||||
VideoPlayerMod.LOG.warn("[{}] failed to read config {}: {} — using empty list",
|
||||
VideoPlayerMod.MOD_ID, path, t.toString());
|
||||
PRELOAD_URLS = Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
/** URLs to push to each joining player. Never null; possibly empty. */
|
||||
public static List<String> preloadUrls() {
|
||||
return PRELOAD_URLS;
|
||||
}
|
||||
|
||||
private static void writeDefault(Path path) throws IOException {
|
||||
Files.createDirectories(path.getParent());
|
||||
// Hand-rolled rather than Gson-serialized so we can carry a `_comment` field that
|
||||
// explains the format directly inside the file.
|
||||
JsonObject root = new JsonObject();
|
||||
root.addProperty("_comment",
|
||||
"preload_urls: HTTP(S) video URLs broadcast to every player on join. "
|
||||
+ "Equivalent to running /videopreload <url> per joiner. Max 256 chars per url.");
|
||||
root.add("preload_urls", new com.google.gson.JsonArray());
|
||||
Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
|
||||
Files.writeString(path, gson.toJson(root), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,15 @@ import com.ejclaw.videoplayer.command.VideoMuteCommand;
|
||||
import com.ejclaw.videoplayer.command.VideoPlaceCommand;
|
||||
import com.ejclaw.videoplayer.command.VideoPreloadCommand;
|
||||
import com.ejclaw.videoplayer.command.VideoStickCommand;
|
||||
import com.ejclaw.videoplayer.net.PreloadPayload;
|
||||
import com.ejclaw.videoplayer.net.VideoPlayerNetwork;
|
||||
import com.ejclaw.videoplayer.registry.VideoPlayerBlockEntities;
|
||||
import com.ejclaw.videoplayer.registry.VideoPlayerBlocks;
|
||||
import com.ejclaw.videoplayer.registry.VideoPlayerItems;
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -27,6 +30,8 @@ public class VideoPlayerMod implements ModInitializer {
|
||||
VideoPlayerNetwork.registerPayloadTypes();
|
||||
VideoPlayerNetwork.registerServerReceivers();
|
||||
|
||||
VideoPlayerConfig.load();
|
||||
|
||||
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, env) -> {
|
||||
VideoStickCommand.register(dispatcher);
|
||||
VideoPlaceCommand.register(dispatcher);
|
||||
@@ -35,6 +40,19 @@ public class VideoPlayerMod implements ModInitializer {
|
||||
VideoPreloadCommand.register(dispatcher);
|
||||
});
|
||||
|
||||
// When a player finishes joining, push every preload URL from the config so their
|
||||
// client kicks off the background download. Reuses the same PreloadPayload that
|
||||
// /videopreload sends, so the client-side caching path is identical.
|
||||
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> {
|
||||
java.util.List<String> urls = VideoPlayerConfig.preloadUrls();
|
||||
if (urls.isEmpty()) return;
|
||||
for (String url : urls) {
|
||||
ServerPlayNetworking.send(handler.getPlayer(), new PreloadPayload(url));
|
||||
}
|
||||
LOG.info("[{}] sent {} config preload(s) to {}",
|
||||
MOD_ID, urls.size(), handler.getPlayer().getName().getString());
|
||||
});
|
||||
|
||||
LOG.info("[{}] initialized", MOD_ID);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user