Skip to content

About

A tiny command-line player for deep-focus internet radio — a single bash script around mpv

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

focusradio

A tiny command-line player for deep-focus internet radio. No UI, no daemon config, no library to maintain — a single bash script around mpv. Start a station, get your terminal back, work.

$ focusradio play drone 50m
▶ drone (for 50m, fading out at the end)

$ focusradio now
▶ drone — Chronotope Project - Mind's I

Features

  • 14 curated focus stations — SomaFM ambient classics (Drone Zone, Groove Salad, Deep Space One…), beat-free drone (Cryosleep, Ambient Sleeping Pill, 9128.live), NTS Slow Focus, freeCodeCamp Code Radio. Every stream verified working; the list is one obvious block at the top of the script.
  • Discover more — focusradio discover <tag> searches the Radio Browser community database (by tag or keyword, most-played first), skips stations you already have, plays each pick through mpv to confirm it actually works, and saves the keepers to your own list. The built-in stations stay untouched.
  • Gentle start and stop — playback swells in from silence and eases out again on focusradio stop (and when switching stations), instead of cutting in and out abruptly. A short 3-second fade by default; set FOCUSRADIO_FADE to change it (FOCUSRADIO_FADE=0 disables it entirely).
  • Timed sessions with a fade-out — focusradio play drone 50m plays for 50 minutes, then fades to silence over the last stretch and stops itself. No alarm, no notification: the fading music is the signal that the session is over.
  • Play log and favorites — everything you hear is scrobbled to a local TSV file. Heard something great? focusradio fav saves it; focusradio favs and focusradio history read your collection back.
  • Offline fallback — if the stream dies mid-session (network blip, station outage), focusradio switches to locally generated brown noise instead of yanking you out of flow with silence, then returns to the station automatically once it is reachable again.

Requirements

mpv, socat, jq, curl — all standard packages:

# Debian/Ubuntu
sudo apt install mpv socat jq curl

# macOS (Homebrew)
brew install mpv socat jq curl

Installation

git clone https://github.com/kprzybys/focusradio.git && cd focusradio
ln -sf "$(pwd)/focusradio" ~/.local/bin/focusradio

Make sure ~/.local/bin is on your PATH (most distros add it for you); then focusradio list works from anywhere. To uninstall, remove the symlink (rm ~/.local/bin/focusradio) and, if you like, your data (rm -r ~/.local/share/focusradio).

Tab completion

Optional shell completion for subcommands and station names — the station list is pulled live from the script, so it stays in sync when you edit STATIONS. Files live in completions/. All three shells need focusradio on your PATH (the symlink above) so completion can query the stations.

bash (needs the bash-completion package and bash 4+ — note stock macOS ships bash 3.2, so brew install bash if you want it there):

mkdir -p ~/.local/share/bash-completion/completions
ln -sf "$(pwd)/completions/focusradio.bash" \
    ~/.local/share/bash-completion/completions/focusradio

Open a new shell, then focusradio play <Tab> lists the stations.

zsh — put _focusradio on your $fpath:

mkdir -p ~/.zsh/completions
ln -sf "$(pwd)/completions/_focusradio" ~/.zsh/completions/_focusradio

then, before compinit in ~/.zshrc:

fpath=(~/.zsh/completions $fpath)
autoload -Uz compinit && compinit

fish — loaded automatically:

mkdir -p ~/.config/fish/completions
ln -sf "$(pwd)/completions/focusradio.fish" \
    ~/.config/fish/completions/focusradio.fish

Usage

focusradio list            # available stations (▶ = playing now)
focusradio discover lofi   # search Radio Browser by tag/keyword and add stations
focusradio play drone      # play; play again to switch stations
focusradio play drone 50m  # timed session: fade-out + auto-stop at the end
focusradio now             # what's playing
focusradio stop            # stop
focusradio fav             # save the current track to favorites
focusradio favs            # show favorites
focusradio history [n]     # last n scrobbled tracks (default 20)

Durations: 50m, 1h, 90s, or a bare number (minutes). The end-of-session fade takes one third of the session, capped at two minutes.

The gentle start/stop fade is 3 seconds by default. Override it per invocation with FOCUSRADIO_FADE (seconds), e.g. FOCUSRADIO_FADE=5 focusradio play drone, or FOCUSRADIO_FADE=0 for the old instant on/off.

Data

  • Play log and favorites: ~/.local/share/focusradio/ (played.log, favs.log — plain TSV, yours to grep).
  • Discovered stations: ~/.local/share/focusradio/stations.local (name|url|description, one per line — edit a line to rename a station, delete a line to remove it).
  • Runtime state (mpv IPC socket, pidfiles): $XDG_RUNTIME_DIR/focusradio/, gone after reboot.

How it works

focusradio play starts mpv detached with a JSON IPC socket and a small watcher process alongside it. The watcher polls the socket every few seconds: it scrobbles title changes, runs the end-of-session fade, and — if mpv dies while the state says "playing" — starts a brown-noise generator (lavfi anoisesrc, no files needed) and probes the station URL every 30 s until it can switch back. focusradio stop tears the whole thing down.

Adding stations

Edit the STATIONS block at the top of the script — one name|URL|description line per station. Anything mpv can play works (.pls playlists, direct stream URLs).

Tests

Tests use bats:

bats test/                  # everything
bats test/focusradio.bats   # fast unit tests only
  • test/focusradio.bats — unit tests for the pure-logic helpers (duration parsing, station lookup, slug generation, URL validation, run-dir hardening). They source the script — its main runs only when the file is executed, not when sourced.
  • test/e2e.bats — end-to-end tests that drive the real binary and spawn mpv against hermetic local resources: an av://lavfi source for playback, and a throwaway HTTP server standing in for both Radio Browser and the stream host, so discover runs without the network. Needs ffmpeg and python3 on top of the runtime deps; slower.

Both use throwaway data and runtime dirs, so they never touch your real stations or a running session.

License

MIT — see LICENSE.

About

A tiny command-line player for deep-focus internet radio — a single bash script around mpv

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages