soulsync/core/streaming
BoulderBadgeDad ca90c6ae6f Player revamp Phase 3a: extract stream state into testable per-session store
Foundation for multi-listener playback. Today web_server.py keeps ONE global
stream_state dict + one lock (web_server.py:747), so the whole server shares a
single 'currently playing' — every tab/device is a remote for the same
playback and two listeners collide. That global is woven through ~22 sites and
isn't unit-testable where it lives.

Lifted into core/streaming/state.py WITHOUT changing behavior:
  - StreamSession: one playback's state, dict-compatible (s['k'], s.get,
    s.update, 'k' in s) so existing call sites work unchanged, each with its
    OWN RLock so distinct sessions never block/clobber each other.
  - StreamStateStore: registry of named sessions; lazy + race-safe create;
    DEFAULT session reproduces today's exact single-global behavior. Also
    drop()/active_ids()/session_ids() for the eventual per-listener wiring.

web_server.py now binds  (DEFAULT) and
. Drop-in: every .update()/[k]/.get()/ site behaves identically. _set_stream_state routes a reassign
through session.replace() so the store's session stays the live object (it's
effectively dead — prepare.py only mutates in place — but safe now).

Honest scope: this is the PROVABLE half of Phase 3. The remaining half (3b:
derive a per-browser session id, per-session Stream/ staging, executor
concurrency, disconnect cleanup) is browser-coupled and can't be verified
without driving 2+ live clients — deferred to a live session. The store API is
already shaped for it.

Tests (tests/streaming/, 33 total):
  - test_stream_state_store.py (19): session dict-compat, isolation, lazy
    create, drop rules, active_ids, concurrent-create race safety.
  - test_stream_state_callsite_compat.py (7): every real web_server access
    pattern (library/play, stream/start, status, audio guard, stop, prepare
    in-place mutation, set->replace) against the exact object web_server binds.
  - test_prepare.py +1: real prepare worker drives an actual StreamSession.
76 streaming+radio tests green; ruff clean; web_server.py parses.
2026-05-30 08:59:15 -07:00
..
__init__.py Lift _prepare_stream_task + playlist_explorer_build_tree to core/ 2026-04-29 11:26:07 -07:00
prepare.py Cin-6: Rename soulseek_client global → download_orchestrator 2026-05-04 23:23:32 -07:00
state.py Player revamp Phase 3a: extract stream state into testable per-session store 2026-05-30 08:59:15 -07:00