# 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.