Files
party-player/README.md
T
simonandClaude Opus 5 a6fd6e802a Add a drag-and-resize animation
A drawn pointer grabs the frame near its top edge, drags it across the
screen, lets go, then takes hold of one corner and pulls the frame to a
new size. Moves ease in and overshoot slightly before settling, and the
paths bow, so the motion reads as a hand rather than a tween.

The resize pins the opposite corner and keeps the aspect ratio locked —
a stretched video would read as broken, not as someone resizing — and
clamps the width to the room left before the corner leaves the screen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tg15tLAuT6NrCE1ZxKVgDB
2026-08-09 22:34:26 +02:00

86 lines
4.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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, see how much playtime is left, and pick the background and
animation.
## 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://<this-machine-ip>: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, the drag animation's size range, port, and
the selectable backgrounds and animations. 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. Three 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.
- **drag** — a drawn mouse pointer travels to the frame, grabs it near its top
edge and drags it across the screen, lets go for a beat, then takes hold of
one corner and pulls the frame to a new size. The aspect ratio stays locked
and the frame stays on screen, so the resize reads as a window resize rather
than a stretch.
The queue page's **Animation** buttons pin the player to one of them, which is
how you look at a single animation without waiting for the cycle to reach it.
**Auto** (the default) hands selection back to the shuffle bag. Switching takes
effect immediately; while pinned, track changes leave the animation running.
**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. Register it under a new name in the `ANIMATIONS`
map in `motion.js` **and** in the one in `config.js` (which supplies the label
for the queue page's button), 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.