A browser-based video wall for playing many local videos at once. Drop in files or whole folders and the wall lays them out into rows, plays everything automatically, and swaps in fresh clips from your session catalog as videos finish.
Live demo: https://redlamp.github.io/video-wall/
Everything runs in the browser. Videos never leave your machine — files are read locally, played from in-memory object URLs, and your session is cached in IndexedDB. There is no server and no upload.
- Drag-and-drop ingest — drop individual video files or entire folders onto the
window. Supports
.mp4,.mov,.webm,.m4v,.mkv,.avi. - Self-filling wall — videos are packed into a configurable number of rows; when a clip ends it is replaced from the catalog so the wall keeps playing.
- Letterbox / crop detection — auto-detects black bars per video; choose
fit,fill, ordetectedcrop modes. - Aspect filtering — show a mixed wall, or restrict to landscape / portrait clips.
- Per-tile controls — pin, mute, seek, step to next/previous, zoom, remove.
- Multi-select — shift-drag a marquee or ctrl/cmd-click to act on many tiles at once.
- Reorder by drag — drop tiles between others to rearrange the wall.
- Scroll modes — scroll the whole wall horizontally, or scroll each row independently.
- Global playback — play/pause all, seek, master volume, playback rate, shuffle.
- Preference persistence — playback settings (rows, sort, volume, speed, crop, shuffle) and theme are restored on reload via IndexedDB / localStorage. Video files must be re-added — browsers don't retain access to local files across reloads — but per-file metadata (duration, dimensions, detected crop) is cached so re-adding is fast.
- Light / dark theme and a draggable, auto-hiding control panel.
| Key | Action |
|---|---|
Space |
Play / pause targets |
← / → |
Seek selected (or zoomed: previous / next video) |
↑ / ↓ |
Master volume up / down |
` / ~ |
Toggle control panel |
Esc |
Close zoom → close panel → clear selection |
- Next.js 16 (App Router, static
output: "export") - React 19, TypeScript
- Tailwind CSS v4 + shadcn/ui (base-ui) components
- GSAP for layout/FLIP animations
idbfor IndexedDB session storage
npm install
npm run devOpen http://localhost:3000 and drop some videos onto the page.
| Script | Purpose |
|---|---|
npm run dev |
Start the dev server |
npm run build |
Static export to out/ |
npm run lint |
ESLint |
npm run smoke |
Playwright smoke test (scripts/smoke-video-wall.mjs) |
This repo follows a main ← dev ← feature/* model:
feature/*/fix/*— branch offdev, open a PR intodev. Merged with squash (clean, lineardevhistory).dev— integration branch; promoted tomainvia PR merged with a merge commit (not squash). This keepsmaincontainingdev's history, so the two branches never diverge. Squash-promotingdev→mainwould put an unrelated commit onmaineach time and eventually cause conflicts.main— production. Pushes trigger the Deploy GitHub Pages workflow (.github/workflows/deploy-pages.yml), which builds the static export and publishes to GitHub Pages.
Both main and dev are protected: PRs required, no force-push or deletion, and the
CI workflow (.github/workflows/ci.yml — lint + build + smoke) must pass before merge.
The Pages build sets GITHUB_PAGES=true so next.config.ts applies the
/video-wall base path and asset prefix.