Skip to content

Latest commit

Β 

History

631 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Music Player (written in Rust)

License: MIT GitHub all releases release rust-clippy discord-server

An extensible music player daemon, server and client, written in Rust β€” like mpd or Mopidy.

Audio decoding and playback are powered by the Rockbox firmware's battle-tested engine, via the rockbox-playback, rockbox-dsp and rockbox-metadata crates: 40+ audio formats, gapless-grade buffering, EQ/crossfade/ReplayGain DSP, and native HTTP streaming. The daemon indexes your library into SQLite (with FTS5 full-text search) and exposes it over gRPC, GraphQL and a web UI β€” controllable from the terminal UI, the browser, or the Tauri desktop app.

Note

Looking for more? If you're interested in this project, you might want to check out Rockbox Zig, a music player daemon built on the Rockbox Open Source Firmware. It offers advanced audio playback features, bringing the best of Rockbox to modern platforms with the power of Zig and Rust.

Table of Contents

Features

  • 🎡 Rockbox playback engine β€” 40+ formats (MP3, FLAC, Vorbis, Opus, MP4/AAC/ALAC, WavPack, APE, WMA, chiptunes, …) with the Rockbox DSP chain (EQ presets, crossfade, ReplayGain)
  • πŸ”Ž Instant full-text search backed by SQLite FTS5, kept in sync automatically by database triggers
  • πŸ–₯️ Terminal UI (ratatui) with an fzf-style fuzzy finder, neovim-inspired status line and ? help overlay
  • 🌐 Web UI (React 18 + TanStack Query + Jotai) with live progress and seek/fast-forward
  • πŸ–±οΈ Desktop apps β€” a skinnable Slint app with an embedded daemon, and a Tauri 2 version
  • πŸ“‘ gRPC + GraphQL APIs (tonic 0.14, grpc-web enabled) for building your own clients
  • ☁️ Browse & stream from Subsonic/Navidrome and Jellyfin servers
  • πŸ“» Cast to Chromecast and UPnP/DLNA renderers, or control another music-player daemon
  • 🎧 Rocksky scrobbling β€” scrobble your plays to Rocksky on the AT Protocol
  • πŸ“» Internet radio β€” search and browse thousands of stations (Radio Browser + TuneIn), bookmark them, with a fullscreen now-playing player
  • πŸ›°οΈ AT Protocol sync β€” radio bookmarks and liked songs restored from your atproto repo, written back to your PDS, and kept live over Jetstream
  • πŸ”Œ Flexible audio output: system device (cpal), stdout, FIFO, Unix or TCP socket

Installation

With curl (downloads the prebuilt binary for your platform from GitHub releases):

curl -fsSL https://raw.githubusercontent.com/tsirysndr/music-player/master/install.sh | sh
# pin a specific release:
curl -fsSL https://raw.githubusercontent.com/tsirysndr/music-player/master/install.sh | MUSIC_PLAYER_VERSION=v0.2.1 sh
# also install the Slint desktop app (music-player-desktop):
curl -fsSL https://raw.githubusercontent.com/tsirysndr/music-player/master/install.sh | MUSIC_PLAYER_DESKTOP=1 sh

Using npm (downloads the prebuilt binary from GitHub releases):

npm install -g @tsiry/music-player   # or: npx @tsiry/music-player
# pin a specific release: MUSIC_PLAYER_VERSION=v0.2.1 npx @tsiry/music-player

Using Homebrew (macOS/Linux):

brew install tsirysndr/tap/musicplayer

On Debian, Ubuntu, and other APT-based systems (amd64 and arm64):

echo "deb [trusted=yes] https://apt.fury.io/tsiry/ /" \
  | sudo tee /etc/apt/sources.list.d/music-player.list
sudo apt-get update
sudo apt-get install music-player

On Fedora, RHEL, Rocky Linux, AlmaLinux, and other DNF-based systems (x86_64 and aarch64):

sudo tee /etc/yum.repos.d/music-player.repo <<'EOF'
[music-player]
name=Music Player
baseurl=https://yum.fury.io/tsiry/
enabled=1
gpgcheck=0
EOF
sudo dnf install music-player

The Debian and RPM packages include both music-player and the Slint desktop application, music-player-desktop. Launch the desktop app from your application menu or run music-player-desktop.

Using Nix (macOS/Linux):

cachix use tsirysndr
nix profile install --experimental-features "nix-command flakes" github:tsirysndr/music-player

Or download the latest release for your platform here.

Compiling from source

Without Nix:

# Install dependencies
brew install protobuf # macOS
sudo apt-get install -y libasound2-dev protobuf-compiler # Ubuntu/Debian
choco install protoc # Windows using Chocolatey Package Manager
# Compile
git clone https://github.com/tsirysndr/music-player.git
cd music-player/webui/musicplayer
nvm install # install node version specified in .nvmrc (optional on windows)
bun install && bun run build # build webui
cd ../..
cargo install --path .

With Nix:

git clone https://github.com/tsirysndr/music-player.git
cd music-player
nix develop --experimental-features "nix-command flakes"
cd webui/musicplayer
bun install && bun run build # build webui
cd ../..
cargo install --path .

πŸ“¦ Downloads

Latest (Desktop):

Latest (CLI):

Other version...

Start the server

music-player

The daemon scans your music directory ($HOME/Music by default), serves gRPC on :5051, WebSocket events on :5052, and the web UI + GraphQL on :5053.

Usage

USAGE:
    music-player [SUBCOMMAND]

OPTIONS:
    -h, --help       Print help information
    -V, --version    Print version information

SUBCOMMANDS:
    albums      List all albums
    artists     List all artists
    help        Print this message or the help of the given subcommand(s)
    next        Play the next song
    pause       Pause the current song
    open        Open audio file
    playlist    Manage playlists
    prev        Play the previous song
    queue       Manage the queue
    play        Resume the current song
    scan        Scan music library: $HOME/Music
    search      Search for a song, album, artist or playlist
    stop        Stop the current song
    tracks      List all tracks

Terminal UI

Run music-player while a daemon is running (or connect to a remote one with music-player connect -s <host>) to open the TUI. It ships with:

  • an fzf-style fuzzy finder (/) over tracks, albums and artists, ranked as you type with match highlighting
  • a neovim-inspired status line: mode indicator, now playing, position/duration, volume, and the connected server
  • a context-sensitive keybinding hint bar, and a full help overlay on ?

Main keys:

Key Action
? Help overlay with all keybindings
/ Fuzzy search (Tab switches Tracks/Albums/Artists scope)
Space Play / pause
n / p Next / previous track
< / > Seek βˆ’5s / +5s
+ / - Volume up / down
z Add selected track to the queue
q / Esc Back / quit

Web UI & Desktop

The web UI is served by the daemon at http://localhost:5053 β€” React 18, TanStack Query and Jotai, with live playback position (GraphQL subscriptions) and a seekable progress bar.

The desktop app wraps the same UI with Tauri 2:

cd webui/musicplayer
bun install
bun run tauri dev   # or: bun run tauri build

The Tauri app reuses an existing local music-player daemon when one is available; otherwise it starts the daemon in-process so playback and the GraphQL, gRPC, websocket, and web UI endpoints are available immediately.

GraphQL API

# Start the server
music-player

Open http://localhost:5053/graphiql in your browser.

Search

The library is indexed into SQLite FTS5 virtual tables that are kept in sync by database triggers β€” no separate index to maintain, and search works instantly over tracks (title/artist/album/genre), albums and artists with prefix matching:

music-player search "fire"        # CLI
# GraphQL: query { search(keyword: "fire") { tracks { title } albums { title } artists { name } } }

Configuration

Settings live in ~/.config/music-player/settings.toml (created on first run). Every key can also be set through a MUSIC_PLAYER_* environment variable (e.g. MUSIC_PLAYER_HTTP_PORT=5053).

music_directory = "/home/me/Music"
port = 5051        # gRPC
ws_port = 5052     # WebSocket events
http_port = 5053   # Web UI + GraphQL
device_name = "Music Player"
library_refresh_interval = 30  # rescan the music directory every N minutes (0 = off)
radio_browser_url = "https://de1.api.radio-browser.info"
tunein_url = "https://opml.radiotime.com"
scrobble = true    # Rocksky scrobbling
atproto = true     # AT Protocol sync (bookmarks, likes, listening status)
atproto_car_max_age_hours = 24  # re-download the atproto repo archive at most once a day

The library can also be refreshed manually at any time β€” music-player scan from the CLI, or the scan mutation in GraphQL. Re-scans only pick up what's new; existing entries are untouched.

Audio output

By default audio goes to the system output device. audio_output redirects the decoded stream (raw S16LE stereo) somewhere else:

audio_output = "cpal"              # system audio device (default)
audio_output = "stdout"            # raw PCM to stdout
audio_output = "fifo:/tmp/mp.pcm"  # named pipe
audio_output = "unix:/tmp/mp.sock" # unix socket
audio_output = "tcp:0.0.0.0:9000"  # tcp socket, e.g.: ffplay -f s16le -ar 44100 -ac 2 tcp://host:9000

Subsonic / Navidrome & Jellyfin

Browse and stream your remote library from any Subsonic-compatible server (Navidrome, Airsonic, gonic) or Jellyfin:

subsonic_url = "https://music.example.com"
subsonic_username = "alice"
subsonic_password = "secret"

jellyfin_url = "https://jellyfin.example.com"
jellyfin_username = "alice"
jellyfin_password = "secret"

Restart the daemon: the servers show up as source devices (in the web UI's device picker, or listDevices in GraphQL). Connect to one and its artists/albums/tracks/playlists are browsable, with tracks streamed straight from the server.

Rocksky scrobbling

If you're logged into Rocksky (rocksky login writes ~/.rocksky/token.json), the daemon scrobbles what you play β€” using the classic rule (half the track, or 4 minutes, whichever comes first). Disable it with:

scrobble = false

AT Protocol sync

Your radio bookmarks and liked songs live in your own atproto repo, so they follow you between devices and clients (atradio.fm, Rocksky, music-player).

Nothing here is required: with no account linked, music-player skips all of it and never touches the network for it. Turn the whole integration off with:

atproto = false

Linking an account. Reading your repo only needs your identity, which comes from rocksky login (~/.rocksky/token.json). Writing back needs a session β€” either atradio login, or password credentials in the environment:

export ATPROTO_IDENTIFIER=alice.bsky.social   # handle, DID or email
export ATPROTO_APP_PASSWORD=xxxx-xxxx-xxxx-xxxx

The session file is shared with the atradio CLI, so signing in once covers both. The daemon logs at startup whether it is authenticated, and what is missing if not.

What syncs.

Radio bookmarks fm.atradio.favorite records are imported into your local bookmarks on startup, and bookmarking a station writes the record to your PDS (unbookmarking deletes it). Bookmarks that only existed locally are pushed up.
Liked songs app.rocksky.like records are imported, then matched against your library on title + artist + album (case-insensitive, indexed). A match links the track to the song record through the new aturi column on track / album / artist. A like whose file isn't in the library yet is kept and re-matched after the next scan.
Listening status While a station plays, it is published as your fm.atradio.actor.status record, written straight to your PDS; the record is deleted when playback stops.

How it reads and stays in sync. The initial import downloads your repo once as a CAR archive (com.atproto.sync.getRepo) and walks its Merkle Search Tree, so one request covers every collection β€” listRecords is the fallback. After that the daemon subscribes to several public Jetstream instances at once and de-duplicates events by repo revision, so a like or bookmark added on another device shows up here within seconds without depending on any single instance staying up.

Downloading the repo again. Bookmarks and likes share the same archive, so a start pulls it at most once, and the date of the last successful download is kept in the database: a restart within atproto_car_max_age_hours keeps what is already imported instead of pulling the whole repo again (Jetstream has been applying changes in the meantime). To force a fresh download:

music-player --force-car-sync
atproto_force_car_sync = true   # force it on every start
atproto_car_max_age_hours = 24  # otherwise re-download only once a day (0 = every start)

Casting

Playback isn't limited to the machine running the daemon β€” from the web UI or GraphQL you can cast to:

  • Chromecast devices
  • UPnP/DLNA media renderers
  • another music-player daemon on your network (auto-discovered via mDNS)

✨ Star History

Star History Chart

About

An extensible music server written in Rust , single binary, zero dependency πŸš€πŸŽ΅βœ¨

Topics

Resources

Code of conduct

Contributing

Stars

512 stars

Watchers

8 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages