@ciphore/radiocli 0.1.9 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +107 -411
  3. package/SECURITY.md +6 -3
  4. package/dist/activity/stats.js +14 -2
  5. package/dist/cli.js +53 -3
  6. package/dist/player/airplay-discovery.js +16 -4
  7. package/dist/player/airplay-worker-protocol.js +1 -0
  8. package/dist/player/airplay-worker.js +4 -1
  9. package/dist/player/command.js +37 -14
  10. package/dist/player/mpv-ipc-client.js +194 -0
  11. package/dist/player/player-controller.js +179 -98
  12. package/dist/providers/cache.js +77 -13
  13. package/dist/providers/provider-manager.js +28 -8
  14. package/dist/providers/radio-browser.js +141 -57
  15. package/dist/safety.js +44 -0
  16. package/dist/storage/store.js +264 -149
  17. package/dist/types.js +2 -53
  18. package/dist/ui/AdaptiveContent.js +332 -0
  19. package/dist/ui/App.js +489 -54
  20. package/dist/ui/AppContent.js +11 -12
  21. package/dist/ui/app-state.js +26 -16
  22. package/dist/ui/ascii.js +42 -1
  23. package/dist/ui/components/Logo.js +6 -1
  24. package/dist/ui/components/ScreenHeader.js +2 -2
  25. package/dist/ui/components/StationList.js +8 -6
  26. package/dist/ui/components/TopTabs.js +8 -7
  27. package/dist/ui/cosmo-land-data.js +1 -1
  28. package/dist/ui/exit-confirmation.js +7 -0
  29. package/dist/ui/explore-map-layout.js +3 -1
  30. package/dist/ui/format.js +56 -2
  31. package/dist/ui/help-content.js +10 -2
  32. package/dist/ui/layout.js +26 -9
  33. package/dist/ui/page-footer.js +36 -13
  34. package/dist/ui/playback-footer.js +15 -2
  35. package/dist/ui/screen-items.js +60 -21
  36. package/dist/ui/screen-meta.js +35 -0
  37. package/dist/ui/screens/AirPlaySettingsScreen.js +4 -2
  38. package/dist/ui/screens/CountriesScreen.js +8 -5
  39. package/dist/ui/screens/ExploreScreen.js +1 -1
  40. package/dist/ui/screens/HelpScreen.js +17 -3
  41. package/dist/ui/screens/HomeScreen.js +2 -3
  42. package/dist/ui/screens/MapScreen.js +2 -2
  43. package/dist/ui/screens/NowPlayingScreen.js +31 -51
  44. package/dist/ui/screens/SearchScreen.js +1 -1
  45. package/dist/ui/screens/SettingsScreen.js +109 -10
  46. package/dist/ui/screens/StationScreen.js +14 -1
  47. package/dist/ui/screens/StatsScreen.js +35 -44
  48. package/dist/ui/system-actions.js +10 -3
  49. package/dist/ui/terminal-mouse.js +44 -0
  50. package/dist/ui/theme.js +0 -1
  51. package/dist/ui/use-app-input.js +37 -6
  52. package/dist/ui/use-command-executor.js +34 -1
  53. package/dist/ui/visualizers/receiver-style-registry.js +101 -0
  54. package/dist/ui/visualizers/receiver-visualizers.js +2856 -745
  55. package/dist/update-check.js +122 -0
  56. package/docs/THIRD_PARTY_NOTICES.md +7 -0
  57. package/package.json +2 -2
  58. package/dist/ui/screens/screen-render.test.js +0 -214
package/README.md CHANGED
@@ -6,160 +6,32 @@
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
7
  [![Node.js](https://img.shields.io/badge/node-%3E%3D22-brightgreen.svg)](package.json)
8
8
 
9
- RadioCLI is a terminal-first world radio receiver for exploring live public stations, tuning real streams, and keeping your listening history close to the command line.
9
+ RadioCLI brings live radio to your terminal. Browse stations around the world,
10
+ save favorites, and listen through `mpv`—without an account or cloud library.
10
11
 
11
- It is built with [Ink](https://github.com/vadimdemedes/ink), [React](https://react.dev/), [Radio Browser](https://api.radio-browser.info/), and `mpv`. The goal is not a thin wrapper around a station list. The goal is a polished TUI product: fast discovery, resilient stream handling, local-first library state, and the kind of engineering surface that can grow without turning into terminal spaghetti.
12
+ Built with [Ink](https://github.com/vadimdemedes/ink),
13
+ [React](https://react.dev/), and the
14
+ [Radio Browser](https://api.radio-browser.info/) directory.
12
15
 
13
- ## Features
16
+ ## Highlights
14
17
 
15
- - Explore public radio from around the world through a cosmo-style braille world map beside the station list, with click-to-place mouse support and WASD keyboard movement backed by a cached geotagged station atlas. Country lists, global station search, a country-density map, and opt-in nearby discovery round out the discovery surface.
16
- - Tune stations with `mpv` first for full playback controls, with `ffplay` and VLC (`cvlc`/`vlc`) as playback-only fallbacks. RadioCLI also probes common install locations (Homebrew, Scoop, WinGet, Chocolatey, VLC app bundle) when a backend is not on `PATH`, so GUI-launched terminals still find it.
17
- - Use a receiver-style Now Playing screen with 50 selectable receiver visualizers, backend status, cleaned ICY track metadata and persisted track history, stream diagnostics, sleep timer, favorite state, `mpv`-backed volume, pause, mute, station skipping, and zero-signal graphics whenever playback is idle, paused, stopped, or not backend-ready.
18
- - Open the in-app Help screen (`?`) for every keybinding and `:` command, with command-name tab completion and search-history recall. Open a station homepage (`O`) or copy its stream URL (`y`).
19
- - Adapt to any terminal: ASCII-safe display, transparent background (and `NO_COLOR`) for light terminals, reduce-motion for SSH and low-power devices, and opt-in resume of the last station on launch.
20
- - Keep shortcuts in a fixed adaptive footer: a compact live station and track row appears above page-specific and global controls while playback is active.
21
- - Move previous/next through the exact station list you tuned from, even after navigating to another screen.
22
- - Browse dense station lists with inline location/codec metadata and yellow favorite stars next to station names.
23
- - Search by station name, place, language, tag, codec, or minimum bitrate.
24
- - Keep local recents, favorites, imported stations, listening activity, playback settings, learned media keys, and provider cache.
25
- - Review listening stats with a GitHub-style local-calendar contribution graph, favorite station, thresholded stations listened, sessions, streaks, active days, and total hours listened from persisted sessions.
26
- - Import `.m3u`, `.pls`, and `.xspf` playlists, including nested local playlists.
27
- - Export favorites and imports as `.m3u`.
28
- - Survive ordinary internet-radio failure modes with provider mirror fallback, stale cache fallback, corrupt-file backups, tune timeouts, and skip-broken-stream behavior.
29
- - Resize with the terminal. The app listens for terminal resize events and recomputes list row counts, map density, receiver width, and compact-mode fallback from the current dimensions.
18
+ - Discover stations through Explore, Search, Countries, or approximate-location Nearby.
19
+ - Listen with a receiver-style Now Playing screen, live metadata, sleep timer, diagnostics, and selectable visualizers.
20
+ - Keep favorites, recents, imports, track history, and listening stats on your machine.
21
+ - Move through the exact station list you tuned from with previous and next controls.
22
+ - Use `mpv` for complete playback control, with `ffplay` and VLC as limited fallbacks.
23
+ - Resize freely: full, compact, and micro layouts preserve navigation and essential controls.
30
24
 
31
- ## Visual Tour
25
+ ## Quick start
32
26
 
33
- The GIFs below are recorded from the real built TUI with `npm run demo:assets`.
34
- The top GIF cycles multiple Now Playing receiver styles and display colors while
35
- a real stream is playing through `mpv`.
36
-
37
- ### Library: Favorites And Recents
38
-
39
- ![RadioCLI library showing favorites and recent stations](apps/docs/public/demo/radiocli-library.gif)
40
-
41
- ### Explore: World Map Discovery
42
-
43
- ![RadioCLI Explore world map with nearby station results](apps/docs/public/demo/radiocli-explore-map.gif)
44
-
45
- ### Search: Query Public Station Directories
46
-
47
- ![RadioCLI search results for tokyo jazz](apps/docs/public/demo/radiocli-search.gif)
48
-
49
- ### Nearby: Opt-In Local Stations
50
-
51
- ![RadioCLI Nearby station list](apps/docs/public/demo/radiocli-nearby.gif)
52
-
53
- ### Stats: Listening History And Display Colors
54
-
55
- ![RadioCLI stats screen cycling display colors](apps/docs/public/demo/radiocli-stats-colors.gif)
56
-
57
- ## Demo
58
-
59
- The interactive TUI opens directly into the product, not a marketing screen:
60
-
61
- ```text
62
- ┌ RADIOCLI ──────────────────────────────────────────────────────────┐
63
- │ Overview │ Playing │ Library │ Explore │ Search │ Countries │ … │ idle │
64
- └──────────────────────────────────────────────────────────────────────┘
65
- RADIOCLI ██████████████████
66
- Live public radio from around the world
67
-
68
- > 1 Playing · Receiver display and controls
69
- 2 Library · Favorites, recent stations, imported streams
70
- 3 Explore · Move a map cursor through geotagged stations
71
- 4 Search · Find stations by name, genre, language, place
72
- 5 Countries · Browse by country list with a world-map toggle
73
- 6 Nearby · Opt-in approximate location for local stations
74
- 7 Stats · Listening graph, sessions, streaks, hours
75
- 8 Settings · Audio output, colors, providers
76
-
77
- 3 recent · 2 favorites · 1 imported
78
-
79
- ↑/↓ move · Enter open · number jump · l location · : command
80
- ←/→ tabs · F7/F9 or ,/. station · F8 pause · t/v display · +/- volume · ? help · q quit
81
- ```
82
-
83
- The Now Playing screen is a framed receiver panel with **50 selectable receiver styles**. The sample below shows the default pulse-grid display; press `v` to cycle through the catalog, which spans several families:
84
-
85
- - **Classic receiver** — ultracode ripple, LEDs, and goniometer.
86
- - **High-resolution braille** — smooth waveform, radial EQ, spectrogram, nebula, silk, ripple tank, phyllotaxis, harmonograph, bloom bars, moiré, galaxy, caustics.
87
- - **Generative & motion** — matrix, hologram, spinning ASCII cube, generated fire, fireworks, plasma, spinning donut, starfield, Lorenz attractor, Barnsley fern, Chladni plate, rotating tesseract, torus knot, fractal tree, Julia sets, and lava-lamp motion.
88
-
89
- Visualizers animate only while playback is actually playing and backend-ready; paused, stopped, idle, loading, and error states render a flat zero-signal display instead of a frozen waveform:
90
-
91
- ```text
92
- Now playing ──────────────────────────────────────────────── mpv · playing
93
- ╭────────────────────────────────────────────────────────────────────────╮
94
- │ FM 128.M RADIOCLI PLAYING │
95
- │ KEXP 90.3 FM │
96
- │ UNITED STATES · WASHINGTON │
97
- │ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ │
98
- │ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ │
99
- │ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ │
100
- │ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ │
101
- │ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ │
102
- │ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ ▌ │
103
- │ MP3 · 128 kbps · english │
104
- │ alternative, indie, seattle │
105
- │ Waiting for ICY track metadata │
106
- │ Backend mpv · Vol 70 ☆ Add favorite (f) Sleep off │
107
- ╰────────────────────────────────────────────────────────────────────────╯
108
-
109
- Playing: KEXP 90.3 FM · Now: Artist - Track · Seattle, Washington, United States · MP3 / 128 kbps / english · mpv · playing · vol 70 · Nearby 4/90
110
- space/F8 pause · f favorite · m mute · s sleep · d diagnostics · b home
111
- ←/→ tabs · F7/F9 or ,/. station · F8 pause · t/v display · +/- volume · ? help · q quit
112
- ```
113
-
114
- For the exact non-interactive demo transcript:
115
-
116
- ```bash
117
- npm run demo:script
118
- ```
119
-
120
- Recording instructions live in [the demo guide](apps/docs/content/docs/demo.mdx).
121
-
122
- ## Documentation Website
123
-
124
- The public website and the manual now live together in `apps/docs`, a Fumadocs-powered Next app. It renders the homepage at `/`, the documentation tree at `/docs`, docs search at `/api/search`, generated page images, and LLM-readable text routes.
125
-
126
- Docs content is written as MDX in `apps/docs/content/docs`, with the navigation tree defined by the local `meta.json` files.
127
-
128
- Useful commands from the repo root:
129
-
130
- ```bash
131
- npm run docs:dev
132
- npm run docs:check
133
- npm run docs:build
134
- ```
135
-
136
- Set `NEXT_PUBLIC_SITE_URL` for the canonical public docs URL. Preview builds also read Vercel's `VERCEL_URL` and Cloudflare Pages' `CF_PAGES_URL`.
137
-
138
- ## Install
139
-
140
- Requirements:
141
-
142
- - Homebrew on macOS: installs RadioCLI, Node.js, `mpv`, and FFmpeg
143
- - npm on macOS, Linux, and Windows: Node.js 22 or newer
144
- - `mpv` for playback, pause, mute, volume, media keys, metadata, and readiness checks
145
- - FFmpeg on macOS for experimental AirPlay and optional `ffplay` fallback
146
- - `ffplay` from FFmpeg as an optional playback-only fallback
147
-
148
- Recommended macOS install:
27
+ macOS with Homebrew:
149
28
 
150
29
  ```bash
151
30
  brew install ciphore/tap/radiocli
152
31
  radiocli
153
32
  ```
154
33
 
155
- The Homebrew formula depends on `node`, `mpv`, and FFmpeg, so the native playback
156
- and AirPlay prerequisites come from the native package manager.
157
-
158
- AirPlay playback on macOS is experimental, but the sender bridge is bundled with
159
- RadioCLI. The app discovers AirPlay/RAOP receivers with Bonjour and decodes
160
- streams with FFmpeg; passcode-protected receivers prompt in the TUI.
161
-
162
- Universal npm install:
34
+ macOS, Linux, or Windows with npm:
163
35
 
164
36
  ```bash
165
37
  npm install -g @ciphore/radiocli
@@ -167,324 +39,148 @@ radiocli doctor
167
39
  radiocli
168
40
  ```
169
41
 
170
- The npm package is `@ciphore/radiocli`, and the installed executable is
171
- `radiocli`. npm installs RadioCLI and its JavaScript dependencies only; it does
172
- not install system playback tools.
42
+ RadioCLI requires Node.js 22 or newer. The Homebrew formula installs `mpv` and
43
+ FFmpeg; npm installs JavaScript dependencies only, so install `mpv` with your
44
+ system package manager for pause, volume, mute, media keys, and metadata.
173
45
 
174
- Debian/Ubuntu:
46
+ See the [installation guide](apps/docs/content/docs/getting-started/install.mdx)
47
+ for Windows, Linux distributions, AirPlay prerequisites, and fallback players.
175
48
 
176
- ```bash
177
- sudo apt install mpv
178
- npm install -g @ciphore/radiocli
179
- radiocli
180
- ```
49
+ ## Visual tour
181
50
 
182
- Fedora, Arch, Alpine, and openSUSE users can install the distro `mpv` package
183
- first, then install `@ciphore/radiocli` with npm.
51
+ These recordings come from the built TUI. Generate them locally with
52
+ `npm run demo:assets`.
184
53
 
185
- Native Windows with Windows Terminal or PowerShell:
54
+ ### Library
186
55
 
187
- ```powershell
188
- winget install --id OpenJS.NodeJS.LTS -e
189
- winget install --id shinchiro.mpv -e
190
- npm install -g @ciphore/radiocli
191
- radiocli doctor
192
- radiocli
193
- ```
56
+ ![Favorites and recent stations in the RadioCLI Library](apps/docs/public/demo/radiocli-library.gif)
194
57
 
195
- Scoop users can use `scoop bucket add extras` and `scoop install mpv` instead
196
- of the `winget` mpv command. WSL remains supported through the Linux path.
58
+ ### Explore
197
59
 
198
- Optional `ffplay` fallback support:
60
+ ![World map discovery in RadioCLI Explore](apps/docs/public/demo/radiocli-explore-map.gif)
199
61
 
200
- macOS/Linux:
62
+ ### Search
201
63
 
202
- ```bash
203
- brew install ffmpeg # macOS
204
- sudo apt install ffmpeg # Debian/Ubuntu
205
- ```
64
+ ![Station search results in RadioCLI](apps/docs/public/demo/radiocli-search.gif)
206
65
 
207
- Windows:
66
+ ### Nearby
208
67
 
209
- ```powershell
210
- winget install --id Gyan.FFmpeg -e
211
- ```
68
+ ![Nearby stations in RadioCLI](apps/docs/public/demo/radiocli-nearby.gif)
212
69
 
213
- `ffplay` can keep streams playable when `mpv` is not installed, but it does not
214
- provide reliable pause, mute, volume, media-key, or metadata control. In that
215
- mode RadioCLI labels the backend as `ffplay fallback`, shows limited-control
216
- footer text, and `radiocli doctor` reports `controls=limited`.
70
+ ### Stats
217
71
 
218
- CI covers command-mode typecheck, tests, builds, package checks, and fresh
219
- install smoke checks on Ubuntu, macOS, and Windows.
72
+ ![Local listening stats with selectable display colors](apps/docs/public/demo/radiocli-stats-colors.gif)
220
73
 
221
- Local checkout:
74
+ ## Essential controls
222
75
 
223
- ```bash
224
- git clone https://github.com/Ciphore/RadioCLI.git
225
- cd RadioCLI
226
- npm ci
227
- npm run build
228
- npm link
229
- radiocli
230
- ```
231
-
232
- If you do not want to link the package globally:
233
-
234
- ```bash
235
- npm run dev
236
- ```
237
-
238
- ## CLI Usage
76
+ | Key | Action |
77
+ | --- | --- |
78
+ | `←` / `→` or `Tab` / `Shift+Tab` | Switch screens |
79
+ | `↑` / `↓` or `n` / `p` | Move the selection |
80
+ | `Enter` | Open or tune the selection |
81
+ | `space` or `F8` | Pause or resume with `mpv` |
82
+ | `,` / `.` or `F7` / `F9` | Previous or next station |
83
+ | `+` / `-` | Change volume |
84
+ | `f` | Save or remove a favorite |
85
+ | `?` | Open all shortcuts and commands |
86
+ | `q` or `Ctrl+C` twice | Quit cleanly |
87
+
88
+ The footer always shows controls for the current screen. Press `:` to open the
89
+ command palette. The [TUI controls guide](apps/docs/content/docs/getting-started/tui-controls.mdx)
90
+ covers filters, playback, media-key learning, AirPlay, and every command.
91
+
92
+ ## CLI
239
93
 
240
94
  ```bash
241
95
  radiocli # Start the TUI
242
- radiocli check # Show local store path, playback backends, provider health
243
- radiocli doctor # Show playback setup status and install guidance
244
- radiocli countries # Print top countries by station count
96
+ radiocli check # Check providers, playback tools, and the local store
97
+ radiocli doctor --json # Create a redacted support report
245
98
  radiocli search "japan hits"
99
+ radiocli countries
246
100
  radiocli import stations.m3u
247
101
  radiocli export favorites.m3u
248
102
  radiocli add-url <stream-url> [station name]
249
103
  ```
250
104
 
251
- `radiocli export` writes `radiocli-favorites.m3u` when no output path is provided.
252
-
253
- After a local build, the same commands can be run with:
254
-
255
- ```bash
256
- node dist/cli.js check
257
- node dist/cli.js search "lagos talk"
258
- ```
259
-
260
- ## TUI Controls
261
-
262
- RadioCLI keeps shortcuts at the bottom of the terminal. When playback is active, a compact live row sits above the shortcuts with station, cleaned track metadata, volume or mute state, and an active sleep timer. The page shortcut row changes with the current screen, and the global transport row stays global:
263
-
264
- - `←` / `→` or `Tab` / `Shift+Tab`: move across the top screen tabs.
265
- - `F7` / `F9`, `,` / `.`, or `Shift+←` / `Shift+→`: tune previous or next station from the source list, wherever you are in the TUI.
266
- - `space` / `F8`: pause or resume.
267
- - `t`: cycle display color.
268
- - `v`: cycle receiver style.
269
- - `+` / `-`: volume.
270
- - `q` or `Ctrl+C`: quit cleanly.
271
-
272
- Pause, mute, volume, and play/pause media-key control require `mpv`. When only
273
- `ffplay` is active, RadioCLI keeps playback and station skipping available but
274
- labels interactive playback controls as limited and shows an `Install mpv`
275
- warning instead of pretending the control worked.
276
-
277
- Page-specific footer controls:
278
-
279
- | Screen | Controls |
280
- | --- | --- |
281
- | Home | `↑` / `↓` move, `Enter` open, number jump, `:` command |
282
- | Search input | type query, `Backspace` edit, `Up` / `Down` move results, `Ctrl+Up` / `Ctrl+Down` recall search history, `Enter` search or tune, `Esc` finish |
283
- | Search results | `/` edit query, `↑` / `↓` or `n` / `p` move, `Enter` tune, `f` favorite, `b` home |
284
- | Explore | click map, `WASD` fine move, `Shift+WASD` jump, `↑` / `↓` station, `Enter` tune, `f` favorite, `[` / `]` page, `b` home |
285
- | Countries | `/` filter, `↑` / `↓` move, `Enter` open stations, `w` map, `b` home |
286
- | World map | `/` filter, `↑` / `↓` move, `Enter` open country, `w` list, `b` home |
287
- | Station lists | `↑` / `↓` or `n` / `p` move, `Enter` tune, `f` favorite, `[` / `]` page, `b` home |
288
- | Now Playing | `space` / `F8` pause, `f` favorite, `m` mute, `s` sleep, `d` diagnostics, `b` home |
289
- | Settings | `Enter` change selected, `g` Radio Garden, `l` location, `x` skip broken streams, `o` audio output, `a` AirPlay, `r` health, `b` home |
290
- | AirPlay | `↑` / `↓` choose, `Enter` select receiver, `c` code entry, `r` refresh, `b` settings |
291
- | AirPlay Code | type receiver code, `Backspace` edit, `Enter` submit, `Esc` AirPlay |
292
- | Stats | `b` home |
293
-
294
- Other active shortcuts:
295
-
296
- - `Enter`: open the selected item or tune the selected station without leaving the current list.
297
- - `:`: command palette.
298
- - `/`: edit search or country filter on screens that support it.
299
- - `[` / `]`: page through long station and country lists.
300
- - `m`: mute.
301
- - `o`: cycle audio output. If a station is playing, RadioCLI retunes it on the new output immediately.
302
- - `g`: toggle the experimental Radio Garden adapter.
303
- - `l`: toggle nearby location lookup.
304
- - `x`: toggle skip-broken-stream behavior.
305
- - `r`: refresh provider health.
306
- - `f`: favorite the current or selected station.
307
- - `n` / `p`: move selection; on Now Playing, tune next or previous station from the source list.
308
- - `s`: cycle the sleep timer on Now Playing through off, 15 minutes, 30 minutes, 60 minutes, then off again.
309
- - `d`: stream diagnostics on Now Playing.
310
- - `b`: back home.
311
-
312
- When you tune a station from Library, Explore, Search, Countries, or Nearby, that list becomes the playback queue. Previous/next keeps moving through that source list from any screen until you tune from another list.
313
-
314
- Hardware media keys depend on the OS and terminal. RadioCLI maximizes compatibility by enabling enhanced keyboard reporting where supported, recognizing common F7/F8/F9 sequences, Kitty consumer/media-key codes, modified-arrow sequences, comma/dot transport fallback, and learned custom bindings. Previous/next media actions stay app-level; play/pause needs the `mpv` backend. Learn keys from Settings or with `:learn previous`, `:learn play`, and `:learn next`; clear them with `:keys reset`.
315
-
316
- Explore mouse clicks use terminal mouse reporting while the Explore tab is active. If your terminal or tmux setup does not pass those events through, the WASD cursor controls stay fully available.
317
-
318
- Useful command palette entries:
319
-
320
- ```text
321
- :search lagos jazz
322
- :country japan
323
- :codec MP3
324
- :language spanish
325
- :bitrate 128
326
- :clear
327
- :volume 60
328
- :mute
329
- :favorite
330
- :sleep 15
331
- :sleep off
332
- :timeout 15
333
- :skip off
334
- :location on
335
- :learn previous
336
- :learn play
337
- :learn next
338
- :keys reset
339
- :airplay
340
- :airplay-code 1234
341
- :map
342
- :library
343
- :stats
344
- :settings
345
- :stop
346
- ```
347
-
348
- Settings persist display colors and receiver styles without editing config files. The fourteen display colors are green, amber, blue, ruby, ice, teal, violet, copper, cyan, lime, coral, rose, slate, and mono, cycled with `t`. The 50 receiver styles span classic receiver displays, high-resolution braille visuals, and generative motion scenes; cycle them with `v` (see the [Demo](#demo) for the full family breakdown). The stats graph and legend follow the selected display color, and the selected Now Playing style is restored on the next launch.
105
+ RadioCLI imports `.m3u`, `.pls`, and `.xspf` playlists. It exports favorites
106
+ and imported streams as `.m3u`.
349
107
 
350
- On macOS, Settings opens a dedicated AirPlay receiver picker for Bonjour
351
- receivers. Change Audio output to AirPlay with `o`, open AirPlay with `a` or
352
- `:airplay`, and choose a visible receiver. If a station is already playing,
353
- RadioCLI moves it to the selected receiver immediately; switch Audio output back
354
- to `This device (mpv)` to leave AirPlay. RadioCLI does not auto-pick a receiver.
355
- AirPlay is a current-session output: restarting RadioCLI returns to automatic
356
- local playback while remembering the last receiver for the next manual switch.
357
- If the receiver asks for a code, RadioCLI opens the AirPlay Code screen; you can
358
- also press `c` from AirPlay settings or use `:airplay-code 1234`.
108
+ See the [CLI guide](apps/docs/content/docs/getting-started/cli.mdx) for all
109
+ arguments and examples.
359
110
 
360
- ## Architecture
111
+ ## Playback and reliability
361
112
 
362
- RadioCLI is split around four seams:
113
+ RadioCLI prefers `mpv` because it handles redirects, HLS, real-world codecs,
114
+ ICY metadata, and interactive controls reliably. `ffplay` and VLC can keep a
115
+ stream playing when `mpv` is unavailable, but the UI labels their controls as
116
+ limited.
363
117
 
364
- - TUI state and screens in `src/ui`
365
- - provider adapters in `src/providers`
366
- - playback lifecycle and metadata in `src/player`
367
- - local JSON persistence in `src/storage`
118
+ Station providers use mirror fallback, bounded caches, and stale-cache recovery.
119
+ Playback waits for backend readiness, applies tune timeouts, and can skip broken
120
+ streams. Corrupt library and cache files are backed up instead of silently
121
+ overwritten.
368
122
 
369
- ### Audio Pipeline
123
+ Read the [reliability notes](apps/docs/content/docs/reliability.mdx) for failure
124
+ handling and troubleshooting.
370
125
 
371
- Station lists keep provider metadata and a resolvable stream URL. When you tune
372
- a station, `ProviderManager.resolve()` follows the provider-specific path, then
373
- `PlayerController` starts `mpv` with `--no-video`, `--force-window=no`, a local
374
- JSON IPC endpoint, and the configured volume. The endpoint is a Unix socket on
375
- macOS/Linux and a named pipe on native Windows. `ffplay` is available as a
376
- playback-only fallback, but `mpv` is the intended backend because it handles
377
- redirects, HLS, codecs, metadata, pause, mute, volume, media keys, and readiness
378
- checks more reliably than a JavaScript stream client.
379
-
380
- RadioCLI waits for the backend to become ready before marking playback as
381
- `playing`. With `mpv`, it polls playback state every 500ms and ICY metadata every
382
- 2.5s, then cleans the metadata before showing it in the receiver and live footer.
383
-
384
- ### FFT Processing
385
-
386
- RadioCLI does not currently tap decoded PCM audio or run a real FFT in the Node
387
- process. The receiver visuals are procedural signal displays driven by playback
388
- truth, the selected style, terminal dimensions, theme, and a small `pulse`
389
- counter. That keeps the TUI lightweight and avoids duplicating audio decoding
390
- work already handled by `mpv`. The spectrum-like modes are generated from
391
- deterministic samples, so they should be read as receiver visualizers rather than
392
- measurement-grade audio analysis.
393
-
394
- ### Terminal Rendering And Refresh Rate
395
-
396
- Ink renders React components into ANSI terminal frames. The app recomputes a
397
- terminal layout from the current row/column size, then each screen gets stable
398
- row budgets for station lists, the map, receiver panels, and the adaptive
399
- footer. Visualizers return text rows or colored text segments; the Now Playing
400
- screen frames those rows inside the receiver panel.
401
-
402
- Live receiver animation advances every 80ms, about 12.5 frames per second, only
403
- on the Now Playing screen while playback is `playing` and backend-ready.
404
- Ambient/idle-style animation uses a slower 140ms interval, and the loading
405
- spinner uses 120ms. Inactive playback states render zero-signal frames instead
406
- of animating.
407
-
408
- ### CPU Usage
409
-
410
- CPU cost is intentionally bounded: audio decode stays in the native backend,
411
- there is no JavaScript FFT worker, metadata polling is infrequent, and the pulse
412
- timer does not run outside live Now Playing. In practice the terminal renderer
413
- does string and color-segment generation for the current screen only; `mpv` does
414
- the stream work, and idle/library/search/map screens do not pay the visualizer
415
- animation cost.
416
-
417
- Radio Browser is the primary provider. Its own docs recommend using a speaking user agent, resolving station clicks through `/json/url`, and retrying with other servers when one fails; RadioCLI follows that shape with mirror fallback and durable cache. Explore and Nearby use a cached geotagged Radio Browser atlas, then compute local distance in the app so map movement is not biased toward the most-clicked stations worldwide. Radio Garden support is experimental because the useful endpoints are publicly discoverable but unofficial, and they can be blocked or changed independently of this project.
418
-
419
- Playback prefers `mpv` because it handles real-world streams, redirects, HLS, codecs, and metadata better than a hand-rolled stream client. RadioCLI controls `mpv` through JSON IPC for readiness, pause, mute, volume, and metadata polling, using Unix sockets on macOS/Linux and named pipes on Windows. `ffplay` remains a playback-only fallback and is intentionally labeled with limited controls in the UI and doctor output. Experimental AirPlay output is available on macOS when Bonjour discovery and FFmpeg are available.
420
-
421
- The npm package is `@ciphore/radiocli`, and the installed executable is `radiocli`. Current installs store data under RadioCLI paths such as `radiocli.json` and `radiocli-cache.json`. Existing Radio Atlas data is still discovered when a new RadioCLI store does not exist, and legacy `RADIO_ATLAS_HOME` / animation environment variables remain supported as migration fallbacks. New automation should use `RADIOCLI_HOME` and `RADIOCLI_DISABLE_ANIMATION`.
422
-
423
- Read more:
126
+ ## Privacy
424
127
 
425
- - [Architecture](apps/docs/content/docs/architecture.mdx)
426
- - [Design notes](apps/docs/content/docs/design.mdx)
427
- - [Reliability](apps/docs/content/docs/reliability.mdx)
428
- - [Roadmap](apps/docs/content/docs/roadmap.mdx)
429
- - [Release packaging](apps/docs/content/docs/release-packaging.mdx)
128
+ RadioCLI does not require an account, proxy audio, or upload your listening
129
+ history. Favorites, recents, imports, settings, track history, and activity stay
130
+ in a local JSON library.
430
131
 
431
- These also render as a browsable site run `npm run docs:dev` (see `apps/docs`).
132
+ Nearby contacts `ipapi.co` only when you open that screen, then uses an
133
+ approximate location to sort the local station atlas. Disable the lookup with
134
+ `l`, Settings, or `:location off`.
432
135
 
433
- ## Engineering Highlights
136
+ Favoriting a Radio Browser station sends a best-effort public directory vote by
137
+ default. Turn off **Share favorite votes with Radio Browser** in Settings to
138
+ keep favorites local-only.
434
139
 
435
- This repo is intentionally small, but it is built like production software:
140
+ See [Privacy and security](apps/docs/content/docs/privacy-security.mdx) for the
141
+ complete data-flow description.
436
142
 
437
- - provider boundary instead of UI-coupled fetch calls
438
- - cached geotagged station atlas for true distance-first Explore and Nearby results
439
- - Zod schemas at public API and persistence boundaries
440
- - stale-cache fallback for directory outages
441
- - corrupt store/cache backup instead of silent overwrite
442
- - `mpv` readiness checks before reporting playback as active
443
- - tune timeout and skip-broken-stream behavior
444
- - cleaned ICY metadata, including key/value payloads such as `title="..." artist="..."` and station-specific `text="..."` fields
445
- - source-list playback queues for previous/next transport
446
- - enhanced terminal keyboard parsing with learned media-key bindings
447
- - local-calendar activity bucketing for late-night listening sessions
448
- - local-first privacy posture for history, favorites, imports, and settings
449
- - responsive terminal layout utility with an adaptive footer and focused tests
450
- - smoke tests that exercise live provider data and real playback
451
- - package smoke test that packs the npm artifact, installs it into a fresh temp project, and runs the installed binary
143
+ ## Project structure
452
144
 
453
- ## Privacy
145
+ - `src/ui` — screens, input, layout, and terminal rendering
146
+ - `src/providers` — station directories, resolution, and caches
147
+ - `src/player` — playback backends, metadata, and AirPlay
148
+ - `src/storage` — local library persistence and migration
149
+ - `apps/docs` — documentation website and manual
454
150
 
455
- Nearby station discovery is off by default. If you enable it, RadioCLI requests approximate IP-based location from `ipapi.co` and uses the returned city/region/country/coordinates to sort the local geotagged station atlas. Explore uses only the cursor coordinate you move in the terminal. The app does not require an account, does not store secrets, and does not proxy audio. It stores recents, favorites, imports, settings, and provider cache data locally on your machine.
151
+ More detail lives in the [architecture guide](apps/docs/content/docs/architecture.mdx)
152
+ and [design notes](apps/docs/content/docs/design.mdx).
456
153
 
457
154
  ## Development
458
155
 
459
156
  ```bash
157
+ git clone https://github.com/Ciphore/RadioCLI.git
158
+ cd RadioCLI
460
159
  npm ci
461
- npm run check
462
- npm run lint
463
- npm run test
464
- npm run build
465
- npm run docs:check
466
- npm run docs:build
467
- npm run smoke:data
468
160
  npm run verify
469
- npm run smoke:playback
470
- npm run pack:check
471
- npm run fresh:check
472
- npm run verify:release
161
+ npm run dev
473
162
  ```
474
163
 
475
- `npm run smoke:playback` briefly opens a public stream through your local playback backend.
164
+ `npm run verify` checks types, lint, tests, the production build, and package
165
+ contents. Playback and live-data smoke tests are available separately because
166
+ they contact public services or start a local player.
476
167
 
477
168
  ## Contributing
478
169
 
479
- Contributions are welcome when they keep the app practical, reliable, and honest about public radio streams. Start with [CONTRIBUTING.md](CONTRIBUTING.md), and include `radiocli check` output for playback issues.
170
+ Contributions are welcome. Start with [CONTRIBUTING.md](CONTRIBUTING.md), and
171
+ include `radiocli check` output with playback reports.
172
+
173
+ ## Documentation
480
174
 
481
- ## References
175
+ - [Getting started](apps/docs/content/docs/index.mdx)
176
+ - [Installation](apps/docs/content/docs/getting-started/install.mdx)
177
+ - [Controls](apps/docs/content/docs/getting-started/tui-controls.mdx)
178
+ - [Architecture](apps/docs/content/docs/architecture.mdx)
179
+ - [Roadmap](apps/docs/content/docs/roadmap.mdx)
180
+ - [Release packaging](apps/docs/content/docs/release-packaging.mdx)
482
181
 
483
- - [Ink README](https://github.com/vadimdemedes/ink)
484
- - [Radio Browser API docs](https://api.radio-browser.info/)
485
- - [mpv JSON IPC manual](https://mpv.io/manual/stable/#json-ipc)
486
- - [Unofficial Radio Garden OpenAPI notes](https://github.com/jonasrmichel/radio-garden-openapi)
182
+ Run the documentation site locally with `npm run docs:dev`.
487
183
 
488
184
  ## License
489
185
 
490
- MIT. See [LICENSE](LICENSE).
186
+ [MIT](LICENSE)
package/SECURITY.md CHANGED
@@ -17,13 +17,16 @@ information, or unexpected command execution.
17
17
 
18
18
  ## Privacy Notes
19
19
 
20
- - Nearby station discovery is opt-in.
21
- - Location lookup uses approximate IP-based location from `ipapi.co` when
22
- enabled.
20
+ - Nearby location is enabled by default for new libraries, but no location
21
+ request is made until the Nearby screen is opened.
22
+ - Location lookup uses approximate IP-based location from `ipapi.co`; disable it
23
+ with `l`, Settings, or `:location off`.
23
24
  - The app stores recents, favorites, settings, imports, and provider cache data
24
25
  locally under the user data directory.
25
26
  - RadioCLI does not proxy audio. It resolves public stream URLs and hands
26
27
  playback to `mpv` or `ffplay`.
28
+ - Favoriting a Radio Browser station sends a best-effort vote to that public
29
+ directory by default; the Settings screen can disable vote sharing.
27
30
 
28
31
  ## Supported Versions
29
32
 
@@ -48,7 +48,13 @@ export function computeListeningStats(sessions, now = new Date()) {
48
48
  }
49
49
  function sessionSeconds(session, now = new Date()) {
50
50
  const started = Date.parse(session.startedAt);
51
- const ended = session.endedAt ? Date.parse(session.endedAt) : now.getTime();
51
+ const ended = session.endedAt
52
+ ? Date.parse(session.endedAt)
53
+ : session.lastActiveAt
54
+ ? Date.parse(session.lastActiveAt)
55
+ : Number.isFinite(started)
56
+ ? started + Math.max(0, session.listenedSeconds) * 1000
57
+ : now.getTime();
52
58
  if (!Number.isFinite(started) || !Number.isFinite(ended) || ended <= started) {
53
59
  return Math.min(maxContinuousListeningSeconds, Math.max(0, Math.round(session.listenedSeconds)));
54
60
  }
@@ -65,7 +71,13 @@ function splitSessionByDay(session, seconds, firstDay, lastDayEnd, now) {
65
71
  if (!Number.isFinite(started)) {
66
72
  return [];
67
73
  }
68
- const recordedEnd = session.endedAt ? Date.parse(session.endedAt) : now.getTime();
74
+ const recordedEnd = session.endedAt
75
+ ? Date.parse(session.endedAt)
76
+ : session.lastActiveAt
77
+ ? Date.parse(session.lastActiveAt)
78
+ : Number.isFinite(started)
79
+ ? started + seconds * 1000
80
+ : now.getTime();
69
81
  const rawEnd = Number.isFinite(recordedEnd) && recordedEnd > started
70
82
  ? recordedEnd
71
83
  : started + seconds * 1000;