mirror of https://github.com/Nezreka/SoulSync.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4.0 KiB
4.0 KiB
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 0–2 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)
- 0a. Extract radio selection logic into testable
core/radio/. DONE (commitcbc001e2).core/radio/selection.pyowns 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 inmedia-player.js→ onePlayerStateobject. Seam for every later frontend phase. No behavior change.
Phase 1 — Polish / feel (frontend)
- Persistent queue across refresh (localStorage) — commit
a8985b31 - Drag-to-reorder queue; duration + art per queue item —
ffbe669c+3461d923 - Seek tooltip (hover timestamp) —
112ecbb2 - Crossfade via dual-
<audio>swap (library tracks, experimental) —ccfb3fb0+592b68c1 - Full Media Session API (lockscreen / hardware transport keys + position) —
866f2e4a - Fuller keyboard bindings (N/P/M/space/seek/vol) —
65f49cce - Keyboard shortcut OVERLAY/cheatsheet (bindings done; no discoverability UI yet)
- 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)
- Weighted ranking DONE. Each tier now fetches a random POOL (4x, floored) and
core/radio/selection.rank_candidatesorders it byscore_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_playedfromlistening_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)
- 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.pynow bindsstream_stateto 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. - 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/ 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.