- Background media worker: polls media_jobs table, runs ffprobe+ffmpeg
with concurrency limit, generates video posters, emits socket events
- Non-blocking video upload: stores file + enqueues job, returns immediately
- Poster hydration on board load: GET /boards/:id injects poster/dimensions
from DB into canvas_state video objects
- SpatialGrid: fixed-cell (512px) spatial hash for O(nearby) culling instead
of O(all) item scanning, eliminates setTimeout violations
- Canvas-based video rendering: draws video frames to offscreen canvas then
uploads to GPU, completely eliminates GL_INVALID_OPERATION errors from
PixiJS VideoSource auto-update mechanism
- Server poster upgrade path: culling ticker and applyProcessedMedia() both
upgrade client-captured posters to server posters when available
- Pause restores server poster (paused video behaves like an image)
- Selection drag-end persistence: onObjectDragEnd broadcasts + saves + undo
- Live media:job:update socket handler patches scene data + VideoSprite
dimensions without broadcast fanout
1. Server posters no longer auto-load in VideoSprite constructor.
loadServerPoster() is now public and called by the culling system
only when within MAX_POSTER_VIDEOS budget. Videos outside the
budget stay as placeholders regardless of server poster availability.
2. video-utils cleanup() now uses statSync to distinguish files from
directories, unlinks files first, then rmdirs. Previously tmpDir
was passed through path.dirname() which resolved to /tmp instead
of the created temp directory.
Upload pipeline now extracts poster frame (JPEG) and metadata
(width, height, duration) from videos at upload time using ffmpeg.
Server:
- Add ffmpeg to Docker image (Alpine)
- video-utils.js: probeVideo() and extractPoster() using ffprobe/ffmpeg
- Upload response includes poster_asset_key and duration
Frontend:
- VideoObject gains poster and duration fields in scene format
- VideoSprite accepts posterAssetKey + TextureManager, loads poster
as a regular image texture on construction (no <video> needed)
- Server poster loaded/released via TextureManager ref counting
- addVideoFromUpload passes poster and duration through to scene data
- image-drop.ts forwards poster_asset_key from upload response
Result: videos with server posters render as images by default.
Zero <video> elements needed for thumbnails. Only explicit play
creates a media element.
Video memory:
- Lazy <video> creation: constructor makes placeholder only, initVideo()
creates element with preload='metadata' when near viewport
- Tiered budgets: 1 playing / 6 initialized / 4 poster textures max
- Aggressive teardown: offscreen videos lose <video> element, poster,
and all textures — zero memory for offscreen videos
- Poster textures dropped while playing (don't keep both resident)
- Dimension caching in scene data (nativeW/nativeH) avoids re-init
Server:
- HTTP Range support (206 Partial Content) for video seeking
- Proper end clamping and invalid range rejection (416)
Sync:
- Remove redundant broadcastSceneDebounced() from broadcastTransform()
- Remove duplicate broadcastElements() from drag-end handler
VideoControls:
- Event-driven (timeupdate/play/pause/seeked) instead of rAF polling
- Tracks actual HTMLVideoElement reference, rebinds on init/teardown
- Seek uses ref-based commit on pointerUp, not conditional onChange
Remove server-side Sharp resize and texture dimension cap — PixiJS
handles large textures natively. Keep the proper Assets.unload() fix
to prevent "TextureSource destroyed instead of unloaded" warnings.
- Add mm_file_id column to images table for dedup tracking
- mm-pull fetches posts from MM API, downloads media files, uploads with LOD to MinIO
- Register bridge routes in server.js under /api/boards
When a board is loaded via GET /api/boards/:id, if the canvas_state
lacks a v2 marker, it is converted in-place and persisted back to DB
so subsequent loads skip conversion.
Queries DB for images where asset_key IS NULL, downloads each from
MinIO, generates LOD tiers (thumb/medium/full), uploads them, and
updates the DB record. Skips LOD for videos, SVGs, and GIFs. Logs
per-image progress and continues on individual failures.
- Upload route now generates 3-tier LOD (thumb/medium/full) for images via
lod-generator service, storing each tier at boards/{boardId}/{imageId}/
- Video uploads (mp4, webm, quicktime) supported as single-file storage
- New DB columns: asset_key (prefix path) and media_type ('image'/'video')
- Backward compatible: minio_path and public_url still populated (point to full tier)
- SVG and GIF skip LOD processing (single file stored)
- Added putBuffer() and MIME_TO_EXT export to minio.js
Sharp-based service that produces thumb (256px), medium (1024px),
and full (pass-through) tiers from an uploaded image buffer.
Skips upscaling when the source is smaller than a tier's width.
- Auto-generate board thumbnail from canvas content on save (webp, 400px)
- Collection cards show stacked/fanned board thumbnails with random offsets
- Three-dot menu on all cards with Rename, Share, Delete actions
- Inline rename for collections (header + card) and boards
- Collection cards show public/private status and member count
- Share button on collection cards for quick access
- Add updateCollection/updateBoard API functions
- Add thumbnail + object_count columns to boards table with migration
- Fix zoom drift: use element-relative coords (offsetX/Y) not getScenePoint
- Fix thumbnail generation breaking viewport: use finally block for restore
- Fix thumbnail bounds: use scene-space obj coords not screen-space getBoundingRect
- Fix object counter: live count from canvas instead of stale DB images table
- Fix three-dot menu clipping by removing overflow:hidden from card containers
- Status bar shows "objects" instead of "images", updates on add/delete
Full-featured PureRef-style collaborative canvas for game dev teams:
- Layer panel with visibility, lock, drag reorder, group/ungroup (Ctrl+G/Shift+G)
- Arrangement tools (grid, row, column) via right-click context menu
- Copy to system clipboard (Ctrl+C writes PNG for external paste in Paint etc.)
- Number shortcuts (1-5) for tool selection with visible shortcut badges
- Premium dark UI across all pages (Login, Collections, Boards, Editor)
- Socket.IO rooms for cursors, transforms, and presence notifications
- MinIO image storage with backend proxy, drag/drop and paste upload