soulsync/revamp_plan.md

43 lines
4 KiB
Markdown
Raw Permalink 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.

# Stream / Player / Radio Revamp — Plan
Goal: bring the audio stream + media-player + radio system to Spotify/Apple-level polish and feature set. Target stack: **plain JS** (`webui/static/media-player.js`), not the React migration. Intended architecture direction: **multi-listener** (final call deferred to Phase 3; Phases 02 stay compatible either way).
Rule for every phase: kettui standard — importable/testable logic, seam-level + differential tests, break nothing, ship one reviewable phase at a time.
---
## Phase 0 — Make it provable (foundation, no user-visible change)
- [x] **0a. Extract radio selection logic into testable `core/radio/`.** DONE (commit cbc001e2). `core/radio/selection.py` owns parse_tags/merge_tags/same_artist_cap/build_like_conditions/RadioCollector; DB method delegates. 29 tests, refactor-equivalence proven (behavioral tests pass against old AND new).
- [ ] **0b. Centralize frontend player state.** ~10 scattered `np*` globals in `media-player.js` → one `PlayerState` object. Seam for every later frontend phase. No behavior change.
## Phase 1 — Polish / feel (frontend)
- [x] Persistent queue across refresh (localStorage) — commit a8985b31
- [x] Drag-to-reorder queue; duration + art per queue item — ffbe669c + 3461d923
- [x] Seek tooltip (hover timestamp) — 112ecbb2
- [x] Crossfade via dual-`<audio>` swap (library tracks, experimental) — ccfb3fb0 + 592b68c1
- [x] Full Media Session API (lockscreen / hardware transport keys + position) — 866f2e4a
- [x] Fuller keyboard bindings (N/P/M/space/seek/vol) — 65f49cce
- [ ] Keyboard shortcut OVERLAY/cheatsheet (bindings done; no discoverability UI yet)
- [x] Click-art visualizer, sleep timer, up-next, click-to-seek lyrics, vibrant
art-color glow, crafted entrance, mini-player shuffle/repeat parity,
'Play next' + queue buttons, 'Playing from' context, web-player play logging
- [ ] (Optional) Sleep timer 'end of track' mode; waveform seek bar (flourish)
## Phase 2 — Smart radio (backend algorithm)
- [x] **Weighted ranking** DONE. Each tier now fetches a random POOL (4x, floored) and `core/radio/selection.rank_candidates` orders it by `score_candidate`: play_count + lastfm_playcount (log-damped), recently-played penalty, stable per-id jitter for run variety. Defensive column-probe → still works on a DB predating the play_count/lastfm migration. 43 radio tests; ranking math is deterministic-unit-proven; DB wiring shown via decoy-pool test (probabilistic by nature — documented).
- [ ] **Future (optional deepening):** wire `_recently_played` from `listening_history` (column + scorer support already exist; not yet populated in the query), genre-adjacency graph (currently exact-genre LIKE only).
## Phase 3 — Architecture (deepest, riskiest — multi-listener)
- [x] **3a. Stream-state store extracted + wired (foundation).** DONE. `core/streaming/state.py`: `StreamSession` (dict-compatible, own RLock) + `StreamStateStore` (named-session registry, lazy create, race-safe). `web_server.py` now binds `stream_state` to the store's DEFAULT session — behavior identical to the old single global (proven by call-site-compat + real-session worker tests). 33 streaming tests. This is the provable foundation multi-listener needs.
- [x] **3b. Per-listener session id.** DONE (commit f6174589). _stream_session_id() from the Flask cookie; all 5 stream routes route to the caller's session + lock; per-session background tasks (stream_tasks[sid]); per-session Stream/<sid> staging; executor 1→4 workers. Single-user behavior unchanged. EXPERIMENTAL — route-level two-client no-collision needs Boulder's live multi-client verification (can't boot Flask + 2 cookies in tests). Isolation invariant covered by test_stream_state_store.py.
- [ ] Server-side persistent queue (resume across devices/refresh).
---
## Order of execution
0a (radio extraction) → 2 (smart radio) first: highest *visible* upgrade, backend-only, cleanest to prove, zero playback risk. Then 0b → 1 (polish). Then 3 (architecture) last.