# atradio
[](https://github.com/tsirysndr/atradio.fm/actions/workflows/nix.yml)
[](https://flakehub.com/flake/tsirysndr/atradio.fm)
[](https://discord.gg/WA9hq9Tmkz)
`atradio.fm` in your terminal — a TUI radio player on the AT Protocol.
A native Rust client for [atradio.fm](https://atradio.fm): browse trending /
popular / recently-played stations, fuzzy-search the whole radio-browser
directory, play live streams with a full Rockbox DSP/equalizer chain, and —
when signed in — favorite stations, add your own, and post comments to your PDS.

## Contents
- [Install](#install)
- [Build (from a checkout)](#build-from-a-checkout)
- [Usage](#usage)
- [Signing in](#signing-in)
- [Keybindings (TUI)](#keybindings-tui)
- [Equalizer & DSP](#equalizer--dsp)
- [atradio Connect (remote control)](#atradio-connect-remote-control)
- [Run as a service (systemd, Linux only)](#run-as-a-service-systemd-linux-only)
- [Platform notes](#platform-notes)
- [Lexicon bindings](#lexicon-bindings)
## Install
Prebuilt release tarballs, `.deb`, and `.rpm` packages are attached to every
[GitHub release](https://github.com/tsirysndr/atradio.fm/releases), named
`atradio-<version>-<os>-<arch>.tar.gz` (`macos-amd64`, `macos-aarch64`,
`linux-amd64`, `linux-aarch64`, `freebsd-amd64`, `freebsd-aarch64`,
`netbsd-amd64`, `netbsd-aarch64`) — each contains the binary, README, and
LICENSE. The BSD builds run in emulated VMs and are attached to the release
shortly after it's published (aarch64 ones can take hours).
### macOS / Linux — Homebrew
```bash
brew install tsirysndr/tap/atradio
```
### Linux — Debian / Ubuntu
Direct `.deb`:
```bash
# amd64
curl -LO https://github.com/tsirysndr/atradio.fm/releases/latest/download/atradio_0.3.0_amd64.deb
sudo apt install ./atradio_0.3.0_amd64.deb
# arm64 (Raspberry Pi 4/5, Apple-silicon VM, …)
curl -LO https://github.com/tsirysndr/atradio.fm/releases/latest/download/atradio_0.3.0_arm64.deb
sudo apt install ./atradio_0.3.0_arm64.deb
```
Or via the Gemfury apt repo (auto-updates with `apt upgrade`):
```bash
echo "deb [trusted=yes] https://apt.fury.io/tsiry/ /" \
| sudo tee /etc/apt/sources.list.d/tsiry.list
sudo apt update && sudo apt install atradio
```
### Linux — Fedora / RHEL / openSUSE
Direct `.rpm`:
```bash
sudo dnf install \
https://github.com/tsirysndr/atradio.fm/releases/latest/download/atradio-0.3.0-1.x86_64.rpm
```
Or via the Gemfury dnf/yum repo:
```bash
sudo tee /etc/yum.repos.d/tsiry.repo <<'EOF'
[tsiry]
name=tsiry
baseurl=https://yum.fury.io/tsiry/
enabled=1
gpgcheck=0
EOF
sudo dnf install atradio
```
### Nix
```bash
# Optional: use the binary cache to skip building.
cachix use atradio
# One-off run:
nix run github:tsirysndr/atradio.fm
# Install into your user profile:
nix profile install github:tsirysndr/atradio.fm
# Dev shell (rust toolchain + build deps):
nix develop github:tsirysndr/atradio.fm
```
### From source (Cargo)
```bash
# Runtime/build deps: a C toolchain (for the Rockbox codecs) + ALSA on Linux.
sudo apt-get install -y build-essential pkg-config libasound2-dev # Debian/Ubuntu
cargo install --git https://github.com/tsirysndr/atradio.fm --bin atradio
```
## Build (from a checkout)
```bash
cd cli
cargo build --release
./target/release/atradio # launch the TUI
```
Building compiles the vendored Rockbox codecs, so a C toolchain is required
(clang/gcc). macOS uses CoreAudio; Linux needs ALSA dev headers
(`libasound2-dev`).
> **License note:** this crate links `rockbox-playback` (GPL-2.0-or-later), so
> the compiled `atradio` binary is GPL-2.0-or-later.
## Usage
```bash
atradio # interactive TUI (default)
atradio --no-tui # headless Connect device (remote-controllable)
atradio search lofi # search radio-browser, print results
atradio play "jazz" # headless: play the top hit for a query…
atradio play https://…/stream # …or a stream URL directly
atradio trending # trending stations from the AppView
atradio login # sign in with an app password (env), or:
atradio login --oauth # sign in via the browser (OAuth)
atradio whoami # show the signed-in account
atradio logout
atradio push # upload your local EQ/DSP settings to your PDS
atradio pull # download your EQ/DSP settings from your PDS
atradio service install # Linux: run the headless daemon as a systemd user service
```
## Signing in
Reads to the AppView are public; **favoriting, commenting, adding stations,
appearing in recently-played, and [atradio Connect](#atradio-connect-remote-control)
require a session.** Two ways to authenticate:
- **App password** — set env vars, then `atradio login`:
```bash
export ATPROTO_IDENTIFIER="you.bsky.social"
export ATPROTO_APP_PASSWORD="xxxx-xxxx-xxxx-xxxx"
```
Its session stays signed in the longest and refreshes silently — **recommended
for a long-running [headless daemon](#headless-daemon---no-tui).**
- **OAuth** — `atradio login --oauth you.bsky.social`, or press `s` in the TUI
to open the sign-in modal, which completes the flow in your browser. Convenient
for interactive use, but its session expires sooner than an app password, so an
always-on daemon may need the occasional re-login.
The session + a small profile cache are stored under `~/.config/atradio/`
(also `settings.toml` for volume + DSP).
## Keybindings (TUI)
| `↑`/`↓` `j`/`k` | move selection |
| `←`/`→` `Tab` | switch home tab |
| `1` … `5` | tabs: Trending / Popular / Recent / Favorites / Yours |
| `Enter` | play the selected station |
| `Space` | play / pause · `m` mute · `+`/`-` volume (or adjust DSP) |
| `/` | fuzzy station search |
| `f` | favorite the selected/current station |
| `A` | add a custom station (when signed in) |
| `c` / `a` | comments / add a comment |
| `d` | Connect: pick a device to play/control (see below) |
| `n` | notifications |
| `p` | your profile (with playable recently-played) |
| `e` | equalizer & DSP settings |
| `s` | sign in (OAuth) / sign out |
| `h` · `?` | home · help |
| `q` / `Esc` | quit / close overlay |
## Equalizer & DSP
Press `e` for the full Rockbox chain: a 10-band equalizer, bass/treble tone,
crossfeed, perceptual bass, Haas surround, a compressor, and channel mode /
stereo width. Changes apply live and persist to `settings.toml`.
### Syncing settings (`push` / `pull`)
When you're signed in, the whole DSP chain also **syncs to your PDS** as the
`fm.atradio.audio.settings` singleton record — the CLI's EQ bands (32 Hz–16 kHz)
now match the web build, so your EQ + DSP follow your account across the web app
and other devices. The TUI syncs automatically: on startup a signed-in session
**pulls** the record and applies it (remote wins), then **pushes** the current
chain back when you quit.
You can also sync on demand (both require [signing in](#signing-in)):
```bash
atradio push # upload settings.toml → your fm.atradio.audio.settings record
atradio pull # download the record → overwrite the DSP chain in settings.toml
```
> `pull` replaces the local DSP chain with the synced record (it keeps your local
> `volume`). If you have no record yet, `push` (or quitting the TUI) creates it.
## atradio Connect (remote control)
Like Spotify Connect: when signed in, every atradio client you have open — this
CLI, the web app, other terminals — shows up as a **device** on your account,
and any of them can control the selected player. Requires a session (it's keyed
to your DID and authenticated with an atproto service-auth token); logged-out
clients don't participate.
- Press **`d`** to open the device picker. Pick **This device** to play here, or
pick another device to **control it from here** — pressing `Enter` on a station,
`Space`, `m`, and `+`/`-` are then sent to that device instead of your local
audio. The player bar shows a `◉ Controlling <device>` indicator with the
remote's now-playing and volume.
- Selecting a device **transfers** playback to it (Spotify-style): what you're
playing follows you to the device you pick; picking **This device** pulls it
back and stops the remote.
- Your **listening status** (`fm.atradio.actor.status`) is now driven by Connect:
it's cleared automatically once none of your devices are playing.
### Headless daemon (`--no-tui`)
```bash
atradio --no-tui # stay online as a controllable device; Ctrl-C to stop
```
Runs with no TUI — just an online player you drive from the web app or another
client (great for a Raspberry Pi or a always-on box wired to your speakers).
> **Sign in with an app password for a daemon.** OAuth refresh tokens are
> short-lived, so an OAuth-authenticated daemon eventually drops offline and
> prints `session expired — run atradio login to reconnect` until you sign in
> again. An [app-password](#authentication-optional) session stays signed in far
> longer and refreshes on its own — set `ATPROTO_IDENTIFIER` +
> `ATPROTO_APP_PASSWORD` and run `atradio login` (no `--oauth`). See
> [Signing in](#signing-in).
The device name shown to your other clients defaults to a hostname-based label;
set a custom one in `~/.config/atradio/settings.toml`:
```toml
device_name = "Living Room"
```
### Run as a service (`systemd`, Linux only)
On Linux you can install the headless daemon as a **`systemctl --user` service**
so it starts on login and restarts on failure — ideal for a Raspberry Pi or an
always-on box. Sign in with an [app password](#signing-in) first so the daemon
stays online unattended.
```bash
atradio service install # write the unit, enable + start it under systemctl --user
atradio service status # show the running service (wraps `systemctl --user status`)
atradio service uninstall # stop, disable, and remove the unit
```
`install` drops a unit at `~/.config/systemd/user/atradio.service` whose
`ExecStart` points at the current `atradio` binary running `--no-tui`, then runs
`daemon-reload`, `enable`, and `start`. Follow its logs with:
```bash
journalctl --user -u atradio -f
```
> To keep the service running after you log out (e.g. on a headless Pi), enable
> lingering once: `sudo loginctl enable-linger $USER`.
The `service` subcommand is **Linux-only** — it is compiled out entirely on
macOS, FreeBSD, NetBSD, and other platforms, where systemd isn't available.
## Platform notes
- **Linux:** the player is exposed over **MPRIS** (D-Bus), so media keys and
desktop panels / `playerctl` can see now-playing and drive play/pause/stop.
## Lexicon bindings
The typed `fm.atradio.*` records/queries in `src/fm_atradio/` and
`src/builder_types.rs` are **generated** from the lexicon JSON in
`packages/lexicons/lexicons/atradio` via jacquard's codegen. Regenerate with:
```bash
cargo install jacquard-lexgen # provides `jacquard-codegen`
bash scripts/gen-lexicons.sh
```