How it is built
Architecture overview
The three parts of Nuvo, how they talk, and where each kind of data lives.
Nuvo has three parts.
| Part | Technology | Job |
|---|---|---|
| Window | Electron renderer, React, TypeScript | Screens, the playback engine, the equalizer, lyrics display |
| Main process | Electron main | Starts the daemon, owns the tray, the Notch, file dialogs, the security policy |
Daemon nuvo-core |
Rust, axum, SQLCipher | Library, playlists, history, caches, lyrics, ranking, imports, local files |
Window Electron renderer, React
The renderer (React) holds three things: the screens, the engine (decks, equalizer, queue) and the YouTube access (youtubei.js). The screens feed the engine. The screens talk to Electron main over IPC in both directions, and reach the daemon through the nuvo-core:// protocol that main handles. Electron main talks to nuvo-core (axum) in both directions, over HTTP on a unix socket with a bearer token. The daemon writes to the library database library.db (SQLCipher), the audio cache (XChaCha20-Poly1305) and the cover cache, and is linked to the keyring. It reaches the outside services (YouTube Music, SoundCloud, LRCLIB and the link APIs) and spawns the optional helpers yt-dlp and ffmpeg. The YouTube access in the renderer reaches the same services directly.
Startup
- Main generates a random token, then spawns
nuvo-corewith it. - The daemon binds a unix socket in a private directory and answers
/healthwithstarting. - It reads the library key from the keyring and opens the database. When it finishes,
/healthsaysok. - If the key is missing for an existing database, the daemon starts locked and waits for the recovery phrase.
Main polls health every 50 ms at first. If the keyring shows an unlock prompt, the daemon keeps answering starting and main waits up to two minutes. A daemon that never answers is treated as dead after five seconds. After a crash main restarts it after 0.5, 2 and 5 seconds, and gives up after a fourth crash within a minute. Playback keeps going while the daemon restarts.
When Electron dies, the daemon notices that its standard input closed and exits.
How the window reaches the daemon
The window never sees the token. It calls nuvo-core:// URLs. Main checks them and forwards them to the unix socket with the token. See daemon API.
Where the work happens
- Playback runs in the renderer. Two audio elements (decks) feed one audio graph: normalization gain, preamp, ten filters, a reverb send, and the output.
- The daemon fetches streams only from allowed playback hosts and stores them in the encrypted cache. See audio cache.
- Search, related tracks and stream URLs for YouTube come from the renderer’s YouTube client. SoundCloud’s public catalog is read by the daemon.
- Ranking of recommendations runs in the daemon over candidates the renderer fetched.
- Lyrics come from LRCLIB through the daemon.
yt-dlpandffmpegare optional helpers the daemon spawns for link inspection, recognition and saving. Playback does not need them.
Crates in the daemon
| Crate | Purpose |
|---|---|
nuvo-server |
The HTTP API, state, scheduling |
nuvo-store |
SQLCipher database, migrations, queries |
nuvo-crypto |
Key derivation, chunk encryption, recovery phrase, keyring |
nuvo-cache |
Encrypted audio files |
nuvo-covers |
Cover art at display size |
nuvo-lyrics |
LRCLIB client |
nuvo-recs |
Rewards, ranking, mixes, suppressions |
nuvo-match |
Decides whether a catalog track is the same recording |
nuvo-import |
Parsers and exporters for playlist files |
nuvo-local |
Folder walking, tags, saved copies |
nuvo-fetch |
yt-dlp, ffmpeg, proxy handling, link safety |
nuvo-soundcloud |
SoundCloud’s public catalog and streams |
nuvo-recognize |
Shazam signatures and the listening plan |
nuvo-lastfm |
Last.fm client |
nuvo-net, nuvo-contracts |
Network helpers, shared types |