- QML 53.1%
- C++ 46%
- CMake 0.8%
- C 0.1%
|
All checks were successful
Build / build (push) Successful in 48s
Mirrors the existing /api/tracks?starred=1; backed by getStarred2 and mapped through albumToJson. Browse-gated (web or api surface). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .forgejo/workflows | ||
| qml | ||
| src | ||
| .gitignore | ||
| CMakeLists.txt | ||
| README.md | ||
| TODO.md | ||
Overtone
A native Qt 6 / QML desktop music client for Navidrome, with a modern, Spotify/Feishin-style dark UI and libmpv audio playback.
It speaks the OpenSubsonic API (the documented, stable protocol Navidrome exposes to clients — the same one Feishin uses in "Navidrome mode").
Status
Indev
Build
Requires Qt 6.5+, libmpv, CMake, Ninja, a C++17 compiler.
On Arch Linux:
sudo pacman -S --needed qt6-base qt6-declarative cmake ninja mpv pkgconf
Then:
cmake -B build -G Ninja
cmake --build build
./build/overtone
Architecture
QML UI (qml/) --> C++ controllers & models (src/) --> OpenSubsonic + libmpv
| Layer | Files |
|---|---|
| API | src/api/SubsonicClient (QtNetwork), SubsonicAuth (token+salt md5) |
| Models | src/models/{Album,Artist,Track}ListModel (QAbstractListModel) |
| Playback | src/player/MpvController (headless libmpv), PlayerController (queue + transport) |
| App | src/app/AppController (session/login, exposed to QML as the App singleton) |
| UI | qml/ — Theme singleton, views, and components |
Playback is audio-only libmpv: no GL render surface is needed. The client hands
mpv an authenticated stream URL and observes time-pos / duration / pause
/ EOF via property observers bridged onto the Qt event loop.
Remote control & scripting API
Overtone can serve two HTTP surfaces on one port (default 8080), each toggled
independently. Both are unauthenticated and intended for a trusted LAN.
| Surface | Command | Setting | What it is |
|---|---|---|---|
| Web remote | :rc on / off |
Settings → Remote control | A self-contained control page to open in a phone browser |
| Scripting API | :api on / off |
Settings → Scripting API | A JSON API for scripts |
The server binds while either is on. The web page (/) is served only when the
remote is on; the scripting routes respond only when the API is on. The address
is shown in Settings (http://<lan-ip>:<port>).
Endpoints
Playback control (available whenever the server is running):
| Method / path | Effect |
|---|---|
GET /api/state |
Current track + playback state (JSON) |
GET /api/cover |
Current cover art (JPEG) |
GET /api/toggle · play · pause · next · previous |
Transport |
GET /api/shuffle · repeat |
Cycle shuffle / repeat |
GET /api/vol?v=0..100 |
Set volume |
GET /api/seek?s=<seconds> |
Seek |
Scripting (require :api on):
| Method / path | Effect |
|---|---|
GET /api/search?q=&type=song|album|artist|any&count=N |
Search (default type any, count 20) |
GET /api/play?id=<songId> |
Replace queue with the song and play it |
GET /api/queue?id=<songId>&mode=next|last |
Enqueue a song (default last) |
GET /api/tracks?album=<id> |
Album track list |
GET /api/tracks?playlist=<id> |
Playlist track list |
GET /api/tracks?genre=<name>&count=N&offset=N |
Songs in a genre (default count 50) |
GET /api/tracks?starred=1 |
Starred songs |
GET /api/genres |
Genres with song/album counts |
GET /api/playlists |
Playlists with metadata |
GET /api/playlist/create?name=&songId=&songId=… |
Create a playlist |
GET /api/playlist/edit?id=&name=&add=<songId>&remove=<index> |
Rename / add / remove (remove is a zero-based index) |
Responses are JSON with an ok boolean; errors carry an error string and a
4xx/5xx status. Example:
# Play the first Pop song found
id=$(curl -s 'http://192.168.1.10:8080/api/tracks?genre=Pop&count=1' | jq -r '.tracks[0].id')
curl -s "http://192.168.1.10:8080/api/play?id=$id"
Notes
- Credentials are stored via
QSettingsin plaintext (needed to regenerate the auth token). A future revision should move this to a secret store (libsecret / KWallet).