# Party Player A YouTube playlist player for parties. Two browser windows run side by side: - **Player** (`http://localhost:3000/`) — fullscreen poster background with a small video frame that moves around, switching animation on every track (see [Animations](#animations)). Consecutive videos overlap in a 5 second audio/video crossfade, so there is never silence between tracks. - **Queue** (`http://localhost:3000/queue`) — add videos by URL, drag to reorder, delete, and see how much playtime is left. ## Run ```sh npm install npm start ``` Put the player on the projector/TV screen (F11 for fullscreen) and open the queue on a laptop or phone. On the same network, other devices reach both pages at `http://:3000`. **The player page opens behind a "Start" button.** Browsers block unmuted autoplay, so the first click is what unlocks audio. One click per session. ## Notes - No YouTube API key is needed. Titles come from YouTube's public oEmbed endpoint; durations are measured by a hidden muted player on the player page, which is why a freshly added video shows `–:––` for a moment (and keeps showing it while the player page is closed). Totals are prefixed with `≥` whenever a length is still unknown. - The queue is saved to `queue.json` on every change and restored at startup. - Settings live in `config.js`: overlap length, frame size, drift speed, corner screenshot interval and margin, port, and the selectable backgrounds. Anything the browser needs must also be mirrored in the `/config.js` route in `server/index.js`. ## Animations The video frame's movement changes with every track. Two animations ship today: - **bounce** — diagonal drift, reflecting off the screen edges. - **corners** — the frame parks in a random corner and every 5 seconds a screenshot of it appears in the next of the three other corners, going clockwise. The frame never moves, so a screenshot can never cover it. **The corners animation asks to share your screen.** The decks are cross-origin YouTube iframes, so nothing on the page can read their pixels — capturing the tab and cropping out the frame is the only way to photograph what is playing. The prompt appears once, on the first click of **Start**; choose *this tab*. Declining is fine: the frame still parks in its corner, just without the screenshots. The prompt is skipped entirely on a plain-HTTP origin (i.e. when you open the player from another machine over the network), for the same reason. ## Adding animations `#frameMover` carries only the motion transform (see `public/player/motion.js`). `#frameSkin` inside it is the cosmetic layer, and the two video decks sit inside that. New effects should nest a fresh wrapper rather than sharing a transform with the bounce loop. An animation is `start(context) => stop()`, where `context` is `{ element, capture }` — `capture` may be null, and is read late, so an animation must cope with it being absent. `stop()` has to undo everything: timers, listeners, and any DOM it added, because the next animation takes over the same element immediately. Add it to the `ANIMATIONS` array and it joins the rotation. Selection uses a shuffle bag (`createPicker`): every animation comes up exactly once per pass, so they stay evenly used, and the same one never runs twice in a row — including across the seam between two passes.