- Replace per-pixel RGBA->ABGR loop in Entry.upload() with a single
MemoryUtil.memCopy() into NativeImage's native buffer. The two layouts
are identical when viewed as little-endian bytes, so no swap is needed.
Cuts 1080p upload time from a ~2M-iter Java loop to one native memcpy.
- Move the frame-pump tick from 20Hz client tick (END_CLIENT_TICK) to
per-render-frame (LevelRenderEvents.START_MAIN). At 60+fps display vs
24fps source, this removes the worst stutter window where a decoded
frame waited up to 50ms for the next tick. Distance-gain math stays on
20Hz where it's plenty.
- Bump version 0.4.6 -> 0.4.7 in gradle.properties and README.
- 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.
- audio: distance attenuation now uses the panel center (width/2, height/2
offset from the anchor along the renderer's right/up axes) instead of the
anchor block corner, so a 4x4 panel sounds like it's coming from the
middle of the screen and not the bottom-left.
- preload: each client now posts a chat line on start / completion / failure
/ cache-hit, so a command-block sequence like /videopreload -> /videoplace
can be timed against the visible "[videopreload] 완료" message.
- safety: VideoPlayback.tick() verifies the anchor BE still exists at each
active position and forcibly stops playback if it doesn't — covers any
edge case where BLOCK_ENTITY_UNLOAD doesn't fire.
- /videopreload feedback now explicitly states "완료 알림 후 재생하세요".
- fix: stop playback when anchor block entity unloads (BLOCK_ENTITY_UNLOAD)
so deleting a video while audio is playing actually silences it.
- fix: force-stop SourceDataLine and grabber from outside the worker thread
so a blocked line.write() / grab() unblocks immediately on close.
- perf: tune FFmpeg streaming options (buffer_size, probesize, analyzeduration,
max_delay, fflags=+genpts, reconnect_delay_max) and pre-size audio line buffer
to ~0.5s to smooth out mid-stream stutter.
- feat: /videopreload <url> broadcasts a S2C PreloadPayload to all clients;
each client downloads the URL to <gameDir>/video_player_cache/<sha256> and
subsequent playback reads from the local file instead of streaming.
Gated by COMMANDS_GAMEMASTER (op level 2), so command blocks can invoke it.
JavaCPP Loader (javacpp.jar) ships pure-Java code that needs its own
JNI bridge DLL (jnijavacpp) to extract & link other native libraries.
That bridge lives in the platform-specific javacpp-<os>-<arch>.jar
which the old install guide silently omitted — users following it
ended up with a black panel and an UnsatisfiedLinkError for jnijavacpp
because FFmpeg natives could never be loaded.
This bumps the required JavaCV jar list from 4 to 5, updates the
-Xbootclasspath/a: examples on all three OSes, and adds a diagnostic
note for the matching log line.
Docs-only change; mod code is unchanged from v0.4.2.
The held video_stick item rendered as the default missing-model cube even
with v0.4.1 jar loaded (lang strings resolved, so the mod itself was active).
Root cause confirmed against Fabric 26.1.2 docs: the new model loader no
longer auto-resolves unprefixed parent paths. `item/generated` needs to be
written as `minecraft:item/generated`.
models/item/video_stick.json — parent → minecraft:item/generated.
gradle.properties — 0.4.1 → 0.4.2.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three fixes for v0.4.1:
1. Video stick item rendered as missing-texture because 26.1.2 requires the
new client_item descriptor at assets/<mod>/items/<name>.json. Add it; the
existing models/item/video_stick.json is kept as the underlying model.
2. Quad placement now anchors the local (0,0) corner at the bottom-left of
the wall face the player clicked, so the clicked block is the BL and the
video grows up & right. Previously it was centered on the anchor.
3. EAST/WEST face rotations were swapped, which placed the quad on the far
side of the air block (~1 block away from the wall) instead of flush.
Derived the correct rotations from first principles:
EAST = Axis.YP +90° (local +Z → world +X, +X → -Z = north)
WEST = Axis.YP -90° (local +Z → world -X, +X → +Z = south)
NORTH/SOUTH/UP/DOWN math re-verified — those were already correct.
The anchor block becomes invisible and non-collidable; it exists only as a
BlockEntity host in the air block adjacent to the clicked wall. The renderer
now translates and rotates the textured quad so it sits flush against the
surface of the wall the user actually clicked, on any of the six faces.
Stick interaction:
right-click face → place anchor at hit.relative(face), facing=face, open GUI
right-click face with anchor already there → reopen the GUI
sneak + left-click face with stick → delete the anchor on that face
The anchor's selection outline / collision / occlusion are all empty, so the
player can target the wall block behind it without interference.
JavaCV / streaming polish:
- Bump missing-JavaCV log to WARN so users notice when the runtime jar is
not installed (previously buried at INFO).
- Add HTTP resilience options: `timeout`, `reconnect`, `reconnect_streamed`,
`reconnect_at_eof`, and a `user_agent` so picky servers don't 403 us.
setVolume/Mute previously stored gain without affecting audible output: the
backend only called grabImage() and never opened an audio sink. Switch to
grab() (interleaved video+audio frames), force AV_SAMPLE_FMT_S16 on the
grabber so samples are always interleaved signed 16-bit PCM, open a matching
JavaSound SourceDataLine and write scaled samples per-frame. gain is read
on every block so /videoMute, GUI Mute and the per-tick distance attenuation
now take effect immediately. SourceDataLine.write blocking provides natural
A/V pacing, so the legacy 15ms sleep is dropped when an audio line is open;
sleep is retained as a 60fps cap when there is no audio device.
bump version to 0.3.1.
- net.fabricmc.fabric-loom 1.16-SNAPSHOT (no remap; MC 26.1+ ships unobfuscated)
- gradle.properties: minecraft_version=26.1.2, loader=0.19.2, fabric-api=0.149.0+26.1.2
- Java 25 toolchain
- fabric.mod.json: fabricloader>=0.19.0, java>=25
- Drop multi-version build script + matrix CI (single-target now)
- Backup of 1.21.6/7/8 working tree preserved on mc-1.21.x branch
Source migration to Mojmap names is in progress on follow-up commits;
this commit alone will not build until source files are ported.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>