v0.4.6: server config for auto-preload on join
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:
tkrmagid
2026-05-15 21:58:26 +09:00
parent e6faae3f39
commit d34dc97671
4 changed files with 154 additions and 5 deletions

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

View File

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