feat(scenes): scene playlists with timed auto-cycling

Add ordered, timed sequences of scene presets that auto-cycle — activating
each preset and holding it for its dwell duration before advancing.

Backend:
- ScenePlaylist / PlaylistItem models + SQLite store (new scene_playlists table)
- PlaylistEngine: cycles ONE playlist at a time (starting one stops any other),
  loop/shuffle, re-reads the playlist each cycle so edits/deletes apply at the
  boundary, skips missing presets, guards against busy-loops; reuses the shared
  apply_scene_state path used by scene presets and automations
- REST API: CRUD + /start, /stop, /state with scene-preset reference validation
- Constructed in the app lifespan with a bounded stop on shutdown

Frontend:
- New "Playlists" sub-tab in the Automations tab with start/stop controls and a
  running indicator; editor modal with ordered scene rows (reorder + per-item
  duration), loop/shuffle toggles, and tags
- Live refresh via the playlist_state_changed WebSocket event
- i18n in en/ru/zh

Tests: new unit + API coverage for the store/model, engine (cycling,
single-active exclusivity, missing-preset skip, shuffle, and the
playlist_state_changed event contract), and routes. Full suite green;
ruff and tsc clean.
This commit is contained in:
2026-06-08 13:48:43 +03:00
parent ca59546711
commit f71e10ee06
27 changed files with 2739 additions and 6 deletions
+20
View File
@@ -42,6 +42,7 @@ Complete REST + WebSocket API reference for the LedGrab server.
- [Weather sources](#weather-sources)
- [Automations](#automations)
- [Scene presets](#scene-presets)
- [Scene playlists](#scene-playlists)
- [Sync clocks](#sync-clocks)
- [Webhooks](#webhooks)
- [HTTP endpoints](#http-endpoints)
@@ -518,6 +519,25 @@ Captured snapshots of target state that can be restored.
| POST | `/api/v1/scene-presets/{preset_id}/recapture` | Re-capture current state into the preset. |
| POST | `/api/v1/scene-presets/{preset_id}/activate` | Activate the preset (restore captured state). |
## Scene playlists
Ordered, timed sequences of scene presets that auto-cycle. The engine drives
**one** playlist at a time — starting a playlist stops any other. Each item
references a scene preset and holds it for its `duration_seconds` (min 1s)
before advancing; `loop` repeats from the start and `shuffle` randomises the
order each cycle.
| Method | Path | Description |
| ------ | ---- | ----------- |
| POST | `/api/v1/scene-playlists` | Create a playlist (items reference scene presets). |
| GET | `/api/v1/scene-playlists` | List all playlists plus the current cycling `state`. |
| GET | `/api/v1/scene-playlists/state` | Get the current cycling state (idle if nothing runs). |
| GET | `/api/v1/scene-playlists/{playlist_id}` | Get a playlist by ID. |
| PUT | `/api/v1/scene-playlists/{playlist_id}` | Update metadata, items, and `loop`/`shuffle`. |
| DELETE | `/api/v1/scene-playlists/{playlist_id}` | Delete a playlist (stops it first if running). |
| POST | `/api/v1/scene-playlists/{playlist_id}/start` | Start cycling (stops any other playlist first). |
| POST | `/api/v1/scene-playlists/stop` | Stop the active playlist (leaves the last scene applied). |
## Sync clocks
Shared clocks that drive linked animations with configurable speed.