diff --git a/docs/superpowers/plans/audiobook-player-qol.md b/docs/superpowers/plans/audiobook-player-qol.md new file mode 100644 index 00000000..d0c86ba0 --- /dev/null +++ b/docs/superpowers/plans/audiobook-player-qol.md @@ -0,0 +1,224 @@ +# Audiobook Player QoL Improvements + +Commands assume the repository root is the cwd. All paths are repository-relative. + +## Goal + +Bring the web audiobook player to feature parity with best-in-class audiobook apps +(Audiobookshelf, Audible, BookPlayer, Prologue) on the quality-of-life axis. The current +surface architecture (persistent mini bar + expandable Now Listening view, playback +survives navigation) is already the de-facto standard and stays as-is. + +Explicitly **out of scope**: Media Session / lock-screen integration, bookmarks (needs a +server endpoint — follow-up), offline/download, queue. + +## Features + +1. **Keyboard shortcuts** — space/K play-pause, arrow skips, volume, chapter nav, speed step. +2. **Configurable skip intervals** — asymmetric defaults (back 10s, forward 30s), user-tunable. +3. **Expanded speed control** — 0.5×–3.0× with fine stepping, presets, and per-book memory. +4. **Chapter prev/next buttons** — alongside the seconds-skip buttons. +5. **Volume control** — reuse the shared `VolumeControl`, persisted like the video player. +6. **Smart rewind** — auto-rewind on resume, scaled by how long playback was paused. +7. **Time remaining at current speed** — real-clock remaining display in Now Listening. + +No server/API changes are required; everything is web-frontend only, so no Android/Apple +client coordination is needed. (Per-book speed memory is device-local in v1; syncing it +server-side via profile settings is a noted follow-up.) + +## Existing seams (read these first) + +| File | Relevance | +| --- | --- | +| `web/src/pages/audiobooks/player/useAudiobookPlayback.ts` | Playback engine. `togglePlay` (~line 499), `skip` (~534), `setRate` (~541), returned API (~690+). All engine work lands here. | +| `web/src/pages/audiobooks/player/MiniBar.tsx` | Bottom bar. Hardcoded `SKIP_*_SECONDS = 30` and `PLAYBACK_RATES` at lines 10–12. | +| `web/src/pages/audiobooks/player/NowListening.tsx` | Full-screen view. Same hardcoded constants; dead "More" button (lines 50–56) becomes the settings menu trigger; remaining-time toggle at lines 33–37. | +| `web/src/pages/audiobooks/player/AudiobookPlayer.tsx` | Wrapper that owns mini/now-listening mode — mount point for the keyboard shortcuts hook. | +| `web/src/player/components/VolumeControl.tsx` | Existing slider + `getPersistedVolume`/`persistVolume` helpers (shared `player-volume`/`player-muted` storage keys). Styled for dark video overlay — needs a surface-toned variant. | +| `web/src/player/components/SpeedMenu.tsx` | Current preset-only menu (shared with video player). Audiobook gets a richer `SpeedControl`; video keeps `SpeedMenu` untouched. | +| `web/src/player/hooks/useKeyboardShortcuts.ts` | Video player's shortcut hook — pattern to mirror (input-field guard, single document listener). | +| `web/src/utils/storage.ts` | Typed localStorage wrapper — add new keys here, never call `localStorage` directly. | +| `web/src/player/components/SeekBar.tsx`, `CircleButton.tsx`, `SleepTimerMenu.tsx`, `ChaptersMenu.tsx` | Established player design language; reuse, don't fork. | + +## Design language + +These are additions to an existing, refined system — extend it, don't restyle it: + +- **Surfaces**: mini bar is a themed surface (`bg-background`, `border-t`, token colors). + Popovers anchored to it use the established dark-glass treatment + (`bg-black/90 backdrop-blur-sm rounded-lg shadow-xl`, white/75 text, `data-active` rows) + exactly as `SpeedMenu`/`SleepTimerMenu` do today. +- **Type**: time and rate values are always `font-mono`/`tabular-nums` so nothing shifts as + digits tick. The new speed readout (`1.45×`) must reserve width for two decimals. +- **Buttons**: all transport controls are `CircleButton` (`sm` secondary flanks, `md`/`lg` + primary center). Chapter prev/next use `SkipBack`/`SkipForward` lucide glyphs at the same + stroke weight (1.6) as the rotate icons, so the cluster reads as one family. +- **Micro-interactions**, small and purposeful: + - Skip buttons: a quick ~18° icon flick in the skip direction on press + (CSS transform transition, ~150ms ease-out) — confirms the action without a toast. + - Speed stepper: value crossfades (opacity 120ms) on change; the active preset chip gets + the same `bg-white/5 text-white` treatment as current menu rows. + - Smart rewind: when it fires, the seek bar playhead animates the small jump back rather + than teleporting (SeekBar already animates position; verify it holds for ≤30s deltas). +- **Accessibility**: every new control keeps the existing patterns — `aria-label`, + `aria-expanded`/`aria-haspopup` on triggers, roving focus + Escape in menus (copy from + `SleepTimerMenu`), `role="slider"` semantics from `VolumeControl`/`SeekBar`. + +## Phase 1 — Engine + preferences model + +### 1a. Preferences hook + +New `web/src/pages/audiobooks/player/useAudiobookPrefs.ts`: + +- Add storage keys: `AUDIOBOOK_SKIP_BACK`, `AUDIOBOOK_SKIP_FORWARD`, + `AUDIOBOOK_SMART_REWIND` (bool), `AUDIOBOOK_RATES` (JSON map) in `web/src/utils/storage.ts`. +- Exposes `{ skipBack, skipForward, smartRewind, setSkipBack, setSkipForward, setSmartRewind }`. +- Defaults: back **10s**, forward **30s** (the asymmetric convention — small "what did she + say?" hops back, bigger hops forward), smart rewind **on**. +- Allowed skip values: 5, 10, 15, 30, 45, 60, 90 (two digits max keeps `SkipIcon`'s inset + number legible). +- Per-book rate map: single JSON object `{ [contentId]: rate }` under `AUDIOBOOK_RATES`, + LRU-capped at 50 entries (store `{ rate, at }` and evict oldest on insert). + Helpers `getBookRate(contentId)` / `setBookRate(contentId, rate)` live here too. + +### 1b. Engine extensions (`useAudiobookPlayback.ts`) + +- **Volume/mute**: initialize from `getPersistedVolume()`; apply to the `