diff --git a/gradle.properties b/gradle.properties index f6e5d5e..cc2ea9a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ org.gradle.configuration-cache=false # Mod mod_id=video_player -mod_version=0.4.27 +mod_version=0.4.28 maven_group=com.ejclaw.videoplayer archives_base_name=video_player diff --git a/src/main/java/com/ejclaw/videoplayer/VideoPlayerClient.java b/src/main/java/com/ejclaw/videoplayer/VideoPlayerClient.java index 97d4b7a..97f1e6d 100644 --- a/src/main/java/com/ejclaw/videoplayer/VideoPlayerClient.java +++ b/src/main/java/com/ejclaw/videoplayer/VideoPlayerClient.java @@ -3,6 +3,7 @@ package com.ejclaw.videoplayer; import com.ejclaw.videoplayer.block.VideoAnchorBlockEntity; import com.ejclaw.videoplayer.client.MusicQuizClient; import com.ejclaw.videoplayer.client.net.ClientNetworking; +import com.ejclaw.videoplayer.client.playback.VideoCache; import com.ejclaw.videoplayer.client.playback.VideoPlayback; import com.ejclaw.videoplayer.client.render.VideoAnchorRenderer; import com.ejclaw.videoplayer.item.VideoStickItem; @@ -12,6 +13,7 @@ import net.fabricmc.api.ClientModInitializer; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientBlockEntityEvents; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.fabric.api.client.rendering.v1.BlockEntityRendererRegistry; @@ -82,6 +84,11 @@ public class VideoPlayerClient implements ClientModInitializer { } }); + // Wipe video_player_cache/ on game exit so preloaded clips don't pile up across + // sessions. Cache entries are re-broadcast by the server on every JOIN, so a freshly + // started game will repopulate the cache automatically. + ClientLifecycleEvents.CLIENT_STOPPING.register(client -> VideoCache.wipeOnShutdown()); + VideoPlayerMod.LOG.info("[{}] client initialized", VideoPlayerMod.MOD_ID); } diff --git a/src/main/java/com/ejclaw/videoplayer/client/playback/VideoCache.java b/src/main/java/com/ejclaw/videoplayer/client/playback/VideoCache.java index 8e7d258..40662aa 100644 --- a/src/main/java/com/ejclaw/videoplayer/client/playback/VideoCache.java +++ b/src/main/java/com/ejclaw/videoplayer/client/playback/VideoCache.java @@ -226,6 +226,40 @@ public final class VideoCache { IN_FLIGHT.clear(); } + /** + * Wipe the cache directory at game shutdown so the user doesn't accumulate disk usage + * across sessions. Distinct from {@link #clearAll()}: at shutdown there are no concurrent + * downloads to race and no player to chat-notify, so we skip the chat ping but still bump + * the epoch (defensive — any straggler write inside the in-flight {@code .part} stream + * will detect the bump and self-clean) and drop indexes. + */ + public static void wipeOnShutdown() { + CACHE_EPOCH.incrementAndGet(); + READY.clear(); + IN_FLIGHT.clear(); + int deleted = 0; + int failed = 0; + try { + Path dir = cacheDir(); + if (dir != null && Files.isDirectory(dir)) { + try (var stream = Files.newDirectoryStream(dir)) { + for (Path p : stream) { + try { + if (Files.isRegularFile(p) && Files.deleteIfExists(p)) deleted++; + } catch (Throwable t) { + failed++; + } + } + } + } + } catch (Throwable t) { + VideoPlayerMod.LOG.warn("[{}] wipeOnShutdown failed: {}", + VideoPlayerMod.MOD_ID, t.toString()); + } + VideoPlayerMod.LOG.info("[{}] wipeOnShutdown: deleted={} failed={}", + VideoPlayerMod.MOD_ID, deleted, failed); + } + /** Caller-supplied: current set of URLs that are fully cached, for diagnostics. */ public static Set readyUrls() { return new HashSet<>(READY.keySet());