Music Sync over the Internet for Pixel Light Shows
  • JavaScript 82.8%
  • Vue 14.6%
  • CSS 2.1%
  • HTML 0.5%
Find a file
Brian McGonagill 55d2e49638 Build Get Pixel v1: FPP-synced mobile show companion
- Server: Express + ws — polls FPP status, extrapolates show clock,
  broadcasts sync snapshots @1Hz, serves show/tracks APIs + range audio
- Mock FPP simulator with admin control endpoints for dev/CI
- Vue 3 SPA: join live mid-song, drift correction (hard seek >350ms,
  rate nudge 0.98-1.02), About + Music List pages
- Tests: 29 server (Vitest/supertest), 21 web (Vue Test Utils),
  2 Playwright e2e — all green
- Docs: README architecture/config/deploy, Requirements, Build plan
2026-08-22 14:26:44 -05:00
e2e Build Get Pixel v1: FPP-synced mobile show companion 2026-08-22 14:26:44 -05:00
mock-fpp Build Get Pixel v1: FPP-synced mobile show companion 2026-08-22 14:26:44 -05:00
server Build Get Pixel v1: FPP-synced mobile show companion 2026-08-22 14:26:44 -05:00
web Build Get Pixel v1: FPP-synced mobile show companion 2026-08-22 14:26:44 -05:00
.gitignore Build Get Pixel v1: FPP-synced mobile show companion 2026-08-22 14:26:44 -05:00
AGENTS.md Build Get Pixel v1: FPP-synced mobile show companion 2026-08-22 14:26:44 -05:00
Build_plan.md Build Get Pixel v1: FPP-synced mobile show companion 2026-08-22 14:26:44 -05:00
LICENSE Initial commit 2026-08-22 12:04:44 +00:00
package-lock.json Build Get Pixel v1: FPP-synced mobile show companion 2026-08-22 14:26:44 -05:00
package.json Build Get Pixel v1: FPP-synced mobile show companion 2026-08-22 14:26:44 -05:00
README.md Build Get Pixel v1: FPP-synced mobile show companion 2026-08-22 14:26:44 -05:00
Requirements.md Build Get Pixel v1: FPP-synced mobile show companion 2026-08-22 14:26:44 -05:00

Get Pixel

Music Sync over the Internet for Pixel Light Shows.

Get Pixel is a mobile-first web app for guests at a pixel light show. Scan a QR code, tap Play Live, and your phone plays the show's music in sync with the lights driven by a Falcon Pi Player (FPP) controller — plus About and Music List pages for show context.

Features

  • Join the live show mid-song — audio starts at the controller's current playlist position
  • Continuous drift correction — hard seek beyond ±350 ms, gentle playbackRate nudges (0.981.02) inside 80350 ms
  • Live metadata — song title/artist, track progress, time until next sequence, up-next title
  • Show stats — total show length and song count
  • About this Show — creator story, build specs, links (from server/data/show.json)
  • Show Music List — ordered tracks with purchase/download links (from server/data/tracks.json)
  • Auto-reconnect — WebSocket client reconnects with backoff; server keeps extrapolating through brief FPP dropouts

Architecture

[FPP controller or mock-fpp]  --REST /api/fppd/status-->  [Get Pixel server]
                                                              |  polls (~500 ms), derives authoritative clock
[Guest phones] --HTTP--> static Vue SPA + /api/show*          |
        <-------WebSocket /ws sync snapshots @1Hz ------------+
        <-------HTTP range audio from server/media/ ---------+

Sync model

  1. Server polls FPP GET /api/fppd/status every ~500 ms → {playing, positionSec, songName}.
  2. Between polls the server extrapolates: position = fppPositionSec + (now - lastGoodPoll).
  3. The WS hub broadcasts {playing, trackIndex, track, positionSec, showElapsedSec, serverTimeMs, ...} every 1 s (and immediately on play-state/track changes).
  4. Each client estimates its clock offset from WS ping/pong round trips (median of last 5 samples) and computes the target playhead.
  5. The audio engine corrects drift: > 350 ms → hard seek; 80350 ms → playbackRate nudge clamped to [0.98, 1.02]; otherwise untouched.
  6. Tapping Play joins the live show at the current position — no waiting for the next song.

Repo layout

Path What it is
server/ Express API, FPP REST client, sync engine, WS hub, /media audio with HTTP range support
server/data/show.json Show title, creator, about text, build specs, links
server/data/tracks.json Ordered tracks: title, artist, file, length, purchase URL
server/media/ Audio files served to guests (01-aurora.wav, …)
mock-fpp/ FPP API simulator with admin control endpoints for dev/testing
web/ Vue 3 + Vite SPA (/ show, /about, /music)
e2e/ Playwright specs driving the full stack

Quick start

npm install                      # installs all workspaces
node server/scripts/generate-sample-audio.mjs   # demo WAVs into server/media/
npm run dev                      # mock-fpp :8350 + server :4000 + vite :5173

Open http://localhost:5173 on your phone/desktop. Start the simulated show:

curl -X POST http://localhost:8350/admin/play \
  -H 'content-type: application/json' -d '{"positionSec": 0}'

Then tap Play Live — the page joins at the live position.

Production-style single-origin serving:

npm run build                    # builds web/dist
PORT=4000 WEB_DIST=web/dist npm start   # server serves SPA + API + media + ws

Pointing at real FPP hardware

Set FPP_BASE_URL to your controller's HTTP endpoint (FPP serves its REST API on port 80):

FPP_BASE_URL=http://192.168.1.50 PORT=4000 npm run start --workspace @get-pixel/server

The client normalizes common FPP status payloads (numeric or named status, seconds_played or time_elapsed, current_song/current_sequence). Track titles, artists, lengths, and purchase links come from your own tracks.json; match is by media file name (extension-insensitive) falling back to sequence/title.

Configuration (env vars)

Variable Default Purpose
PORT 4000 Get Pixel server port
HOST 0.0.0.0 Bind address
FPP_BASE_URL http://localhost:8350 FPP (or mock) base URL
FPP_TIMEOUT_MS 2000 FPP request timeout
FPP_POLL_INTERVAL_MS 500 Status poll cadence
BROADCAST_INTERVAL_MS 1000 WS snapshot cadence
DATA_DIR server/data JSON content directory
MEDIA_DIR server/media Audio files directory
WEB_DIST (none) Serve built SPA from this path

Mock-FPP admin endpoints (dev/tests only): POST /admin/play {positionSec}, /admin/pause, /admin/seek {positionSec}, /admin/load {playlist:[{file,lengthSec,…}]}, GET /admin/state.

Editing show content

  • server/data/show.json — title, creator, tagline, about (string or array of paragraphs), buildSpecs [{label,value}], links [{label,url}].
  • server/data/tracks.json — ordered array of {id, title, artist, file, lengthSec, purchaseUrl}. lengthSec drives progress bars and show totals; keep it accurate.
  • Drop matching audio files into server/media/. Any browser-playable format works; files are served with range requests so seeking is instant.

Testing

npm test                # server (29) + web (21) Vitest suites
npm run test:e2e        # Playwright: full-stack guest flow incl. mid-show join

E2e automatically builds web/dist, boots mock-fpp + server (SPA served single-origin), and runs headless Chromium. Requires browsers once via npx playwright install chromium.

Deploying

Any host with Node 20+ works (DigitalOcean, Linode, Hetzner, or on-site mini PC):

  1. npm ci && npm run build
  2. Run WEB_DIST=web/dist node server/src/index.js behind your reverse proxy (nginx/Caddy) with WebSocket upgrade support for /ws.
  3. Set FPP_BASE_URL to your controller. Guests need network reachability to this host — port-forward over HTTPS if you want access from outside your LAN.
  4. Put a QR code on the yard sign pointing at your URL.

Bandwidth: each guest pulls ~12 Mbps while playing. A 200 Mbit uplink comfortably serves 100+ simultaneous phones.

Requirements & plan