
WATERMeDIA: Multimedia API
Библиотека для Minecraft с поддержкой видео и аудио. Работает на Forge, NeoForge, Fabric через VLC и поддерживает YouTube, Twitch, Google Drive и другие платформы. Используется модами вроде WaterFrames, LittleFrames.
Оцените первым
1.3M
118
Список изменений
📦 UPDATE 3.0.0.22 (BETA)
⚡ Core — Boot pipeline, modules & progress metrics
- ⚙️ Renamed:
WaterMediaAPI→WaterMediaModule(moved toorg.watermedia) — non-API boot processes extend it too now; the lifecycle (load/start/release) isprotected, so only theWaterMediaorchestrator (same package) can drive it - ⚙️ Changed: Binaries and Config are homologated as boot modules —
WaterMediaBinariesandWaterMediaConfigextendWaterMediaModuleand boot through the same registry, orderedBinaries → Config → Codecs → Platform → Media → Network; the hand-rolled pre-registry startup blocks inWaterMedia.startare gone - ⚙️ Changed: the global
load()pre-pass is interleaved per module (load→start), so config-dependent step counts (e.g.NetworkAPI's file-server step) read registered config values instead of compile-time defaults - ⚙️ Changed: the FFmpeg bootstrap is centralized in
MediaAPI(code-sectioning into the player served no purpose) —FFMediaPlayer.load()/loaded()/loadError()/vulkanDecodeAvailable()becameMediaAPI.ffmpegLoaded()/ffmpegError()/vulkanDecode(); binaries extraction stays separate in the binaries module - ✨ Added: three-level boot metrics on
WaterMedia, names only —step()/steps()/stepName()(which module is loading),taskStep()/taskSteps()/taskName()(which element the module is loading),work()/workTotal()/workName()(units of the demanding task in flight: download/extraction bytes) — enough to emulate three loading bars;currentAPI(),totalWorkSteps()andcompletedWorkSteps()are removed, module instances are never exposed - ✨ Added:
WaterMedia.failures()— safe (non-fatal) boot failures asFailure(api, step)records (a failed FFmpeg load, binaries extraction, media cache init or file-server bind), surfaced as[NO]lines in the app's boot splash - ✨ Added: FFmpeg extraction publishes real byte progress (compressed bytes read vs. the shipped zip size) through the binaries module's work bar; the boot splash shows the percentage on the active line
⚡ API — Sealing & extension hardening (security)
- ⚙️ Changed:
GFXEngineis nowsealed(permitsGLEngine,VKEngine,HeadlessGFXEngine) andSFXEngineis nowsealed(permitsALEngine,JSEngine) — the publicMediaPlayer/TxMediaPlayer/FFMediaPlayerconstructors take an engine instance, so sealing closes the injection gate: a caller can only pass WaterMedia's own engines, never a hand-rolled subclass, and can no longer reimplement an engine to work around a rendering bug that isn't WaterMedia's - ⚙️ Added:
HeadlessGFXEngine— a permitted no-GPUGFXEnginethat records uploads into memory instead of a context (server-side probing, CI, headless validation); exposesuploadCount()/lastUpload()/format()/activeFrame()for pipeline introspection and replaces the former test-only stand-in - ⚙️ Changed: every concrete API class a developer receives an instance of is now
finalso it can't be extended — the sixImageReaders (GIF/JPEG/PNG/WEBP/NETPBM/SVGReader), the fourNetpbmDecoders,ImageMetadata,NetRequest,NetworkServer, the fourWaterMediaAPIfacades (Codecs/Media/Network/PlatformAPI), and the built-inIPlatformimplementations (WaterPlatform+ every web platform) - ⚙️ Changed:
XCodecExceptionis nowsealed(permitsUnsupportedFormatException) andYtDlpPlatformis nowsealed(permitsYouTubePlatform) - 🔸 Note:
IPlatform(registered by apps throughPlatformAPI.register),VKContext(the modder Vulkan-device bridge), and the platform exception hierarchy (PlatformException/MatureContentException) stay open on purpose — they are documented extension points, not instances handed back to callers - 🔸 Note: the unnamed-module boundary means
sealedonly compiles for same-package subclasses, so the cross-package abstract bases (WaterMediaAPI,ImageReader,ImageWriter,NetpbmDecoder) are hardened by finalizing their leaves instead — a developer subclass of the base is inert because WaterMedia never accepts one
⚡ CodecsAPI — JPEG decoder (security hardening)
- 🐛 Fixed: uncapped 16-bit SOF dimensions overflowed the coefficient/sample/BGRA int math — a few-byte header could wrap
new int[...]to0/negative (ArrayIndexOutOfBoundsException/NegativeArraySizeException) or force multi-GB allocations (OutOfMemoryError); both axes are now hard-capped at 16384 (16K) and rejected with a clean codec error - 🐛 Fixed: the scan header's 4-bit DC/AC Huffman table selectors (0..15) were used unchecked to index the
2table array, so a selector of 4..15 threw an uncontrolledArrayIndexOutOfBoundsException; selectors are now validated to 0..3 - 🐛 Fixed: a malformed DHT declaring an oversubscribed (non-canonical) Huffman code overflowed the fast-lookup table fill and value indexing with an uncontrolled
ArrayIndexOutOfBoundsException; the table builder now rejects oversubscribed codes with a clean codec error - 🐛 Fixed: a corrupt DC Huffman table could yield an out-of-range magnitude category (SSSS > 15) feeding a bogus bit count into the coefficient extend; the category is now validated to 0..15
⚡ CodecsAPI — GIF decoder (security hardening)
- 🐛 Fixed: uncapped 16-bit logical-screen dimensions overflowed
width*height*4int math (NegativeArraySizeException) or forced multi-GB allocations (OutOfMemoryError) for the canvas and LZW index buffers; the canvas is now hard-capped at 16384 (16K) per axis and rejected with a clean codec error - 🐛 Fixed: an animation frame whose image descriptor extends beyond the logical screen overflowed the
width*heightLZW allocation; frames are now bounds-checked against the canvas and rejected with a clean codec error - ⚙️ Added: config
decoders.gif.clampImageDesc(default off) — when enabled, an out-of-bounds frame is clamped to the canvas (best-effort, logged as WARN, may show artifacts) instead of failing the decode - 🐛 Fixed:
ScreenDescriptor,ImageDescriptorandColorTablethrew uncheckedIllegalArgumentExceptionon malformed data, escaping theIOExceptioncontract and crashing the caller; validation now surfaces asXCodecExceptionat the parse boundary - 🐛 Fixed: the sub-block buffer growth (
next <<= 1) could overflow to a negative size (NegativeArraySizeException) on pathologically large inputs; growth is now overflow-guarded and bounded
⚡ CodecsAPI — PNG/APNG decoder (security hardening)
- 🐛 Fixed: APNG
fcTLframe geometry was never validated against the canvas — a frame declaring a size larger than the canvas, or negative x/y offsets, drove out-of-bounds writes (uncontrolledArrayIndexOutOfBoundsException) in the decode, blend and dispose paths; every frame is now bounds-checked (the existingFCTL.validateis now actually called, and hardened tolongmath so offset+size can't wrap past the check) and rejected with a clean codec error - 🐛 Fixed: uncapped
IHDRdimensions — the raw 32-bit width/height overflowedwidth*height*4int math (NegativeArraySizeException) or forced multi-GB allocations (OutOfMemoryError); both axes are now hard-capped at 16384 (16K) and rejected with a clean codec error - 🐛 Fixed: IDAT/fdAT decompression bomb — inflation grew the output buffer unbounded, so a tiny image could inflate to gigabytes (
OutOfMemoryError); output is now bounded to the exact size a well-formed frame decompresses to (computed aslongto avoid overflow on 16K depth-16 frames) and aborts with a clean codec error past it - 🐛 Fixed: zTXt/iTXt decompression bomb — compressed text metadata inflated with no output limit; both are now capped at 2 MB decompressed and throw past it
- 🐛 Fixed: indexed
bKGDwith a palette index beyond thePLTE(or with noPLTEat all) hit the uncheckedPLTE.getColorwith an uncontrolledArrayIndexOutOfBoundsException— validated at construction (only reachable withdecoders.png.useBKGDChunkenabled)
⚡ CodecsAPI — WEBP decoder (security hardening)
- 🐛 Fixed: uncapped 24-bit VP8X canvas and ANMF frame dimensions — a crafted file of a few bytes could declare up to 16777216×16777216, overflowing
width*height*4int math and forcing multi-GB allocations (OutOfMemoryError) or uncontrolledArrayIndexOutOfBoundsException; both are now hard-capped at 16384 (16K) per axis and rejected with a clean codec error - 🐛 Fixed: lossless images using bundled palettes (16 colors or fewer) crashed with an uncontrolled
IndexOutOfBoundsException(the image decodes at a reduced width but the palette inverse copied back at full width) — they now fail with a clean codec error; proper bundling decode is pending - 🐛 Fixed: VP8 lossy frames declaring broken partition sizes (partition 0 or token partitions overrunning the chunk, truncated size table, empty partitions) escaped the
IOExceptioncontract with uncontrolledIllegalArgumentException/BufferUnderflowException— sizes are now validated up front and fail with a clean codec error - 🐛 Fixed: VP8L bodies shorter than the 5-byte header inside extended (
VP8X) or animation (ANMF) containers crashed with an uncontrolledIllegalArgumentExceptionwhen skipping the header (only the simpleVP8Lpath validated it) — now rejected with a clean codec error - ⚙️ Added: config
decoders.vp8.brokenTokens(default off) — instead of failing, attempts to repair frames with broken token partition sizes: declared sizes are clamped to the bytes actually available and unusable partitions are replaced with a duplicate of the previous one or with fake zeroed data; decode continues best-effort and may show artifacts, every repair is logged as WARN - 🐛 Fixed: the container-declared dimensions (VP8X canvas or ANMF frame) were never checked against the dimensions the embedded VP8/VP8L bitstream actually carries — output buffers are sized from the container, so a crafted mismatch overran the YUV plane copy (
IndexOutOfBoundsException), the BGRA buffer limit (IllegalArgumentException) or the animation compositor (ArrayIndexOutOfBoundsException), and a smaller canvas silently delivered truncated pixels; both sides must now agree (as the spec requires) and mismatches fail with a clean codec error - 🐛 Fixed: ANMF sub-chunk sizes were read as signed 32-bit and used unvalidated — a negative size made the sub-chunk cursor stop advancing (infinite loop / thread hang) or walk into negative indices (
ArrayIndexOutOfBoundsException), and a huge size wrapped the bounds check; sizes are now validated with overflow-free math before use - 🐛 Fixed: animation frames extending past the canvas were silently clipped while still decoding (and allocating) the full off-canvas frame; the spec requires frames to lie fully inside the canvas, so they are now rejected with a clean codec error — which also bounds every per-frame allocation by the validated canvas size
- 🐛 Fixed: the animation pre-scan crashed the constructor with an uncontrolled
IllegalArgumentExceptionon an odd-sized VP8X chunk truncated exactly at its end (the RIFF padding byte seek landed one past the buffer limit); the scan now clamps and degrades to a static-image summary - 🐛 Fixed: the 16K-per-axis cap alone still allowed 16384×16384 declarations — ~1 GiB per pixel buffer and ~2 GiB on the animated path (canvas + output) from a few-byte header (
OutOfMemoryError); a total-pixel cap (64 Mpx, e.g. 8192×8192) now rejects them at construction, and zero-dimension VP8 frames are rejected instead of producing empty buffers - 🐛 Fixed: VP8L transform declarations were unbounded — repeating transform types let a tiny file queue thousands of full-image inverse passes (CPU exhaustion); each transform type may appear at most once per the spec and duplicates now fail with a clean codec error
- ⚙️ Changed: VP8L huffman group counts are now enforced through a feasibility gate — each group costs at least 20 bits, so a count the remaining bitstream cannot physically encode (e.g. a 1×1 entropy image declaring 65536 groups ≈ 200 MB of tables from a few-KB file) fails before allocating instead of after; counts past the recommended 256 still log a WARN
⚡ MediaAPI — Engines (rewrite: construction, naming & BCn pixel formats)
- ⚙️ Changed: engine creation is centralized in
MediaAPIfactories —glEngine(thread, ex),vkEngine(ctx),jfxEngine(onFrame),awtEngine(onFrame),headlessEngine(preload),alEngine([buffers]),jsEngine([bufferMs]); everyBuilderclass andbuildDefault()is removed (they were one-argument wrappers around one-argument constructors) - ⚙️ Changed: the client-side check moved into the sealed
GFXEngine/SFXEnginebase constructors — one enforcement point covering every engine (previously onlyGLEngine/VKEnginechecked, each on its own), impossible to bypass even with a directnew;HeadlessGFXEngineis the sanctioned server-side exception via a package-private unchecked path - ⚙️ Renamed (
GFXEngine):setVideoFormat→format(...),supportsFormat→supports,supportsFrameTextures()→preload(),uploadFrameTextures→preload(frames, stride),useFrameTexture→frame(index),requiredBufferAlignment()→alignment(),releaseBuffer(buf)→release(buf),bitsPerComponent()→bits()— record-like naming, overload-paired with the getters - ⚙️ Renamed (
SFXEngine):setAudioFormat→format(type, channels, rate), mirroring the GFX contract - ✨ Added:
PixelFormatnow carries intrinsic layout data —planes(),blockBytes(),compressed()— and gains the block-compressed constantsBC1/BC2/BC3/BC5/BC7ahead of the ISPC encoders, so BCn is a first-class pixel format instead of an external codec-only concept - ⚙️ Removed:
GFXEngine#supportsCompressedTextures(String)/uploadCompressedFrames(ByteBuffer[], String, int)— compressed textures ride the standard path now:supports(PixelFormat.BC*)+format(BC*, w, h)+preload(blocks, 0), withblockBytesintrinsic to the enum; theTxMediaPlayercodec-cache replay maps the DDS codec id straight to itsPixelFormatconstant - ⚙️ Changed:
GLEngine's five conversion shaders share one uniform scheme (plane0..plane3/bitScale/uvSwap/outputWidth) — undeclared locations resolve to -1 and are silently ignored per the GL spec, so a single compile/bind path replaces the three per-format switches (~25 uniform fields and ~150 duplicated lines dropped) - ⚙️ Changed: single-use helpers aggressively inlined across both GPU engines (
GLEngine: quad/PBO init, shader release, hub acquire/release, async frame-texture scheduling;VKEngine:init,buildFormat,updateDescriptorSet,ensureComputePipeline, the SPIR-V cache wrappers) — locals stay in one frame for the JIT and the read flow is linear; big cold well-named steps (ensureYcbcr, ring lifecycle) stay extracted
⚡ MediaAPI — Engines (VKEngine)
- ⚙️ Renamed:
VKContext#instance()→vkInstance()andVKContext#device()→vkDevice()— avoids overloading Minecraft 26.x'sVulkanDevice#instance()by return type alone (the JVM resolves by full descriptor, so the mixin-merged class is legal bytecode, but same-name methods muddy stack traces and crash reports); a mod can now implementVKContextdirectly on it through a mixin (MC's ownvkDevice()accessor satisfies the interface verbatim) and pass the cast device straight toMediaAPI.vkEngine; the README documents the full pattern
⚡ MediaAPI — Engines (GLEngine)
- ⚙️ Removed: the 9 GL proxy callbacks (
setGenTexture/setBindTexture/setTexParameter/setPixelStore/setDelTexture/setActiveTexture/setBindVertexArray/setBindFrameBuffer/setBindBuffer) and theBindConsumer/TexParamConsumerinterfaces — the engine is now self-contained;MediaAPI.glEngine(renderThread, renderThreadEx)is the whole contract - ⚙️ Added: exact GL state capture/restore around every upload wave — texture bindings, active unit, sampler bindings, pixel-store, PBO/ARRAY_BUFFER bindings, VAO, READ/DRAW framebuffers, program, viewport and draw toggles are read once and restored to their exact prior values, so any host-side skip-if-equal cache (Minecraft
GlStateManager, Sodium/Iris trackers) stays truthful without integration - ⚙️ Added: per-render-thread drain hub — all engines sharing a render thread drain through one batched task per frame inside a single capture/restore envelope, keeping the
glGet*cost constant per frame (not per player) at any scale; a broken engine no longer aborts the rest of the wave - ⚙️ Added: provably-safe deletion of exposed texture names — storage is freed immediately on release (zero-size respec) and the name is deleted the moment no texture unit still binds it (which, given exact state restore, proves no host binding cache references it), so a cached binding can never point at a driver-reused name; pending names are tracked per
GLCapabilitiesidentity so they never leak across recreated contexts - 🐛 Fixed: the YUV→RGBA convert pass ran with whatever blend/scissor/colorMask/cull/polygon-mode/sampler state the host left behind — frames could come out clipped, blended or mis-sampled; the pass now forces a clean draw state and restores the host's exactly
- 🐛 Fixed: a queued upload draining after
release()resurrected GL resources (leaked textures on a dead engine) — engines are now terminally flagged and stale render tasks become no-ops - ⚙️ Changed:
GLEngineconstruction now rejects a non-nullrenderThreadwithout an executor instead of failing later on submit
⚡ MediaAPI — Engines (SFXEngine)
- ⚙️ Added:
JSEngine— a native, dependency-freeSFXEnginebacked by the Java Sound API (javax.sound.sampled), playing decoded PCM straight through the OS mixer (WASAPI/DirectSound, ALSA/PulseAudio, CoreAudio) with no OpenAL context and no external library; a first-class alternative toALEngine, usable even when OpenAL is available - ⚙️ Added: stream-based
SFXEnginebackend —format(...)opens a freshSourceDataLine(reopens on format change); uploads are non-blocking viaavailable()backpressure, mirroringALEngine's buffer-pool contract so the clock keeps tracking the audible position throughpendingMs()(framesWritten − getLongFramePosition(), rebased onflush()); volume viaMASTER_GAIN; conservative U8/S16 mono/stereo capability tables so format negotiation always lands on an openable line - ⚙️ Added: standalone app — an "Audio engine" selector (Settings → App → General) switches between
OPENALandJAVASOUND; the choice (AppContext.audioEngine) feeds theSupplier<SFXEngine>at player creation, so it applies to the next media opened - ⚙️ Added:
SFXEngine#speed()— reports whether the backend can change the playback speed (ALEngine→true,JSEngine→false), so players and UIs can honor engines stuck at 1.0× instead of desyncing against them - ⚙️ Added:
MediaPlayer#canSpeed()—trueonly when the source and the audio engine both allow a speed change (live streams and speed-less engines reportfalse);speed(float)now refuses instead of scaling the playback clock against audio stuck at 1.0×, which caused unfixable stuttering onFFMediaPlayerunderJSEngine - ⚙️ Added: standalone app — the player screen's speed dropdown locks (not clickable, drawn dimmed) while the current media reports
canSpeed() == false - 🔸 Note: the Java Sound backend has no portable pitch/rate control, so
speed(float)is a documented no-op (playback stays at 1.0×) and there is no per-source spatialization; useALEnginewhen those are required
⚡ MediaAPI — Engines (Software: JavaFX / AWT)
- ✨ Added:
JFXEngine— aGFXEnginethat renders decoded frames into a JavaFXImage; the engine's direct BGRA buffer backs aPixelBufferwith no copy, so bindingimage()to anImageViewshows video after a singleupdateBufferon the FX thread — a pure JavaFX app can embed WaterMedia with no OpenGL/Vulkan context - ✨ Added:
AWTEngine— aGFXEnginethat renders into an AWTBufferedImage(TYPE_INT_ARGB) for Swing/AWT apps; uses onlyjava.desktop, so it needs no extra dependency - ⚙️ Added: shared
SWEnginebase (sealed, permitsJFXEngine/AWTEngine) — accepts only single-plane BGRA/RGBA and declines every YUV/planar format, so the decoder's scaler pre-converts frames to BGRA; each frame is copied into a reusable direct buffer and published, firing an optionalonFramehook for repaint.texture()returns a non-zero sentinel (there is no GPU texture) - ⚙️ Added: JavaFX is a compile-only dependency (openjfx 21 LTS, host-classified — matches the Java 21 runtime; a newer JavaFX would fail with
UnsupportedClassVersionError) — WaterMedia compilesJFXEngineagainst it but never bundles it; a consuming JavaFX app supplies its own runtime, andJFXEngineloads FX classes only when instantiated
⚡ PlatformsAPI — Web platforms (Imgur)
- 🐛 Fixed: most Imgur links failed to resolve even though the API answered HTTP 200 —
/gallery/{id}posts holding a single image (the most common kind on Imgur) carry the image fields directly ondatainstead of animages[]array and were rejected as "empty or unsuccessful"; both response shapes are now detected and resolved - 🐛 Fixed: direct image links (
imgur.com/{id}) crashed during JSON deserialization — the image endpoint answers"ad_type": nulland Gson refusesnullfor a primitive record component; the data records now bind only the fields actually consumed, making them immune to Imgur's endpoint-dependentnulls - 🐛 Fixed: hidden albums (
/a/{id}links never posted to the gallery) returned 404 —/a/links now resolve through the album endpoint instead of the gallery one - ⚙️ Added: tag links (
imgur.com/t/{tag}/{id}) resolve through the gallery flow; the legacy#/t/fragment detection (broken —URI#getFragment()never contains#) was removed - ⚙️ Added:
www.imgur.com/m.imgur.comhosts are claimed, title slugs and file extensions are stripped from ids, and URLs without a media id are ignored with a warning instead of hitting the API - ⚙️ Added: NSFW posts and images now honor
platforms.allowMatureContentand throwMatureContentExceptionwhen disabled — same gate as Kick/Twitch
⚡ PlatformsAPI — Web platforms (Medal)
- ✨ Added:
MedalPlatform— resolves Medal.tv game clips through the publicmedal.tv/api/content/<id>endpoint (no auth); claims every URL shape (/games/<game>/clips/<id>, any locale prefix like/es/, the short/clip|/clips/<id>links, and?contentId=<id>), exposes the HLS ladder (source/540p/360p/240p) as quality variants like Kick, with the progressive source MP4 and the social-share MP4 as ordered fallbacks, full metadata (title, description, author, duration, thumbnail, posted date) and URL-expiry honored; DMCA-takedown and login-walled clips throw a clearPlatformException - 🔸 Note: Medal's
contentUrl<height>pMP4 rungs are ignored — they serve the source file for every rung when no real ladder was transcoded, so the HLS master is used as the single source of truth for qualities
⚡ PlatformsAPI / Tools — Shared JSON tooling
- ⚙️ Changed: Imgur, Kick (channel/VOD/clip/search), Medal, Sendvid, Streamable and Twitter dropped their identical private fetch helpers and resolve through
fetchJson;NetRequest,PornHubPlatform,TwitchPlatformand the standalone app (AppContext) dropped their privatenew Gson()copies and parse/write throughJsonTool
⚡ App — AWT popup player (showcase)
- ✨ Added: opening an MRL can pop out a native AWT/Swing player window (
AWTPlayerWindow) instead of the in-app GPU screen — aMediaPlayerdriven by anAWTEngine, with an aspect-fit video surface and a minimal transport bar (play/pause, seek, volume, time). Showcases embedding WaterMedia with no GPU backend and exercisesAWTEngineend-to-end inside the product - ⚙️ Added:
AppContext.playerTarget(IN_APP/AWT) with a segmented Player output control in the settingsenginessection (persisted inwatermedia-app.toml); thePLAYERnavigation branches to the popup when set toAWT. The window disposes on close without touching the host app, and its blocking player teardown runs off the EDT
⚡ GENERAL
- 🛠️ Minecraft/Sodium GL state can no longer be desynced by video playback — no
GlStateManagerwiring needed anymore, and the overhead stays flat no matter how many screens play at once - 🛠️ Hardened the WebP, PNG/APNG, GIF and JPEG decoders against malicious files — crafted images can no longer force gigabyte memory allocations, decompression bombs or crash threads with unexpected exceptions; every malformed file now surfaces as a clean codec error
- 🐛 Fixed: Imgur links work again — gallery posts, albums (including hidden ones), single images and tag links all resolve now
Метаданные
Канал релиза
Beta
Номер версии
3.0.0.22
Загрузчики
FabricForgeNeoForge
Версии игры
1.18.2–26.2
Загрузок
2.6K
Дата публикации
24.07.2026
