gostations/todo/queued/per-station-volume.md
Greg Gauthier 6ed2225a4f
All checks were successful
CI / Test (push) Successful in 55s
CI / Build (push) Successful in 40s
chore(todo): add todo/ directory modeled on grokkit; seed with per-station-volume as first queued item
2026-06-06 11:46:24 +01:00

72 lines
4.2 KiB
Markdown

# Per-station volume savings
**Description**: Persist the last-used volume level on a per-station basis (keyed by stream URL), so that returning to a favorite or previously-played station restores the volume the user last set for *that specific station*, rather than always falling back to the global last-volume.
## Problem It Solves
Currently only a single global `player.last_volume` is saved in `radiostations.ini`. When a user fine-tunes the volume while listening to Station A (e.g. to 35), stops, then plays Station B, the volume is reset to whatever was last saved globally (or the default 70). Users have to re-adjust the volume every time they switch stations.
## Benefits
- **Natural UX**: Volume preference is station-specific (a quiet classical station vs. a loud rock station).
- **Seamless resumption**: Pick a favorite → volume is already where you left it.
- **Low friction**: No extra UI; the existing volume controls + persistence just become smarter.
- **Backward compatible**: Falls back to global last-volume when no per-station entry exists.
- **Leverages existing patterns**: Same atomic JSON storage + XDG path as `favorites.json`.
## High-Level Implementation
1. **New data store** (`internal/data/volumes.go`):
- `type Volumes struct { ... }` (map[url]volume, mutex, dirty flag, path to `volumes.json`).
- `NewVolumes()`, `Load()`, `Save()` (exact same atomic tmp+rename pattern as Favorites).
- `Get(url string) (int, bool)`, `Set(url string, vol int)`, `Remove(url string)`.
2. **Wire into TUI / playback start** (in `internal/ui/ui.go`):
- On entering playback for a station:
- `if vol, ok := volumes.Get(station.Url); ok { desired = vol } else { desired = config.LastVolume() }`
- Pass `desired` as `--volume=...` extra arg (or call a new `player.SetVolume(desired)` once IPC is ready).
- On every volume change (in `volumeMsg` handler or on keypress):
- `volumes.Set(currentStation.Url, newVol)`
- `volumes.Save()` (or mark dirty and save on stop/quit for batching).
- On `s`/`x` stop or app quit: ensure pending volume for the current station is saved.
3. **Player interface extension** (optional but clean):
- Add `SetVolume(v int) error` to `Player` interface.
- `mpvPlayer` implements it with `set_property volume X` + local cache update.
- `legacyPlayer` is a no-op (or could try command-line args on next Play).
4. **Config / init**:
- Load volumes in `NewApp()` or lazily when first needed (same as favorites).
- On station removal from favorites (if desired): optionally prune the volume entry.
5. **Persistence file**: `~/.config/gostations/volumes.json` (array of `{url, volume}` or map for simplicity).
## Flags / Config
| Key | Description |
|-----|-------------|
| (none yet) | Could add `player.per_station_volume=true` (default on) in future |
## Implementation Notes
- **Key choice**: Use the station's `Url` (same as Favorites). Stable enough for the use-case.
- **When to persist**: Save on every change (cheap) or only on stop/quit. Current global volume already saves on change; we can do the same for per-station or batch on exit.
- **mpv timing**: The `--volume=XX` extra arg passed to `Play()` is the simplest reliable way (command-line wins). IPC `set_property` after connect is a good fallback/override.
- **Legacy player**: Per-station volumes will be ignored for now (documented limitation).
- **First-time migration**: Existing global last-volume becomes the fallback; no automatic per-station entries are created until user actually changes volume on a station.
- **Tests**: Add to `stations_test.go` or new `volumes_test.go`; mock the volumes store.
- **Effort**: Medium (~200-300 LOC new + wiring). Mostly data layer + 3-4 call sites in the TUI.
## ROI
**High**. Volume is one of the most frequently adjusted controls in a radio player. Making it "stick" per station removes a constant micro-friction and makes the TUI feel more thoughtful and polished.
| Stage | Covered |
|-------|---------|
| Global last-volume | ✓ (already shipped in 2.1) |
| Per-station | **new** |
| Favorites | ✓ (existing) |
| TUI playback | ✓ |
Users will notice it immediately the second time they return to a station they previously tuned.