Repository navigation
v2.0.0-beta.1
Pre-release2.0.0-beta.1 (2026-09-16)
β¨ What's new in v2.0.0-beta.1
WaveFlow Server 2.0.0-beta.1 β your listens go somewhere, your library accepts files, and every client was replayed against the real thing. 361 commits since beta.0. Scrobbling to ListenBrainz, Last.fm and Maloja arrives with a queue that survives a restart; a library can now receive uploads; a track can carry a looping visual; and tags can be corrected without ever rewriting your audio files. Still one binary, one database file, one key file.
π§ Your listens leave the server
Connect ListenBrainz, Last.fm or Maloja β each one a named instance, so you can point at your own Maloja and the public one at the same time.
What matters here is what happens when the other end is down. A listen leaves by a durable queue that survives a restart, is claimed before it is sent, retries once, and honours a standard Retry-After. The health it reports cannot read healthy while three thousand listens are waiting since this morning β that silent failure is the whole reason the queue is visible at all.
Last.fm authorises through your browser. A machine with no browser on it has its own journey, from the command line, and needs no public address.
πΌοΈ A track can carry a moving cover
Tracks can hold a canvas β the short looping visual you know from other players. It has its own store, its own routes and its own tickets, and a sweep that collects what a failed upload left behind.
β¬οΈ Send files to your library
A library can be told to accept files. The server decides whether it wants a given file, receives it in chunks under a bounded session, and makes it a track. Uploads land through the web client or the API.
βοΈ Fix a tag without touching the file
Correct a track's tags, artists and genres from the client. Your audio files are never rewritten β corrections live beside them. A correction leaves alone what it does not mention, and an empty one writes nothing and says so.
π‘ Know what changed, without rescanning
/api/v2 gained a library change feed, with a watermark each device acknowledges for itself, a retention trim, and an honest answer when the stream has been cut rather than a silent gap.
π΅ More of what Subsonic clients ask for
Albums now answer originalReleaseDate, releaseDate, releaseTypes[], recordLabels[] and discTitles[] wherever your tags carry them. Search can be scoped to a single library, on both surfaces.
π The web player grew into the screens it was missing
Uploads, canvas placement, a track-tag editor, a live view of a running scan, API tokens, library membership, sort and filter on every browse page, player modes, and the four gestures external scrobbling leaves to a client.
π For client authors β three response shapes changed
None of these break a compile. All three change what arrives on the wire.
1. getPlayQueue and getNowPlaying name their children entry, not song β and playQueue now carries the username the schema requires. The Subsonic schema renames a media item inside those two containers exactly as it does inside a playlist; this server sent song in both until now, and a client that decodes against the schema read an empty queue.
Both names are deliberately not emitted together: a response carrying
entryandsongconforms to neither contract, and would need a second wire change later to undo. A beta is the moment to do this properly β the contract freeze protects clients that were validated, not a divergence from the specification.
2. GET /api/v2/scrobble-destinations β the unavailable field carries a case (no_application_configured, browser_journey_needs_https) instead of an English sentence. That sentence was printed verbatim into a client that ships in two languages, which told a French reader, in English, what to change in their configuration.
3. GET /api/v2/scans/{id}/events β the progress frame now speaks the same shape as GET /api/v2/scans/{id}: id, total_files, processed_files instead of scan_id, total, processed. A watcher bound to total_files used to show undefined from the first frame on.
π Fixed
Four defects found by the client campaigns, all present in beta.0 as well β none is a regression this release introduces.
- A composer credit no longer becomes several artists. A tag reading
Kobee (Melange / INHOUSE), Holy M (Melange / INHOUSE)names two people; the slashes inside the parentheses were cutting it into three entities, each carrying a parenthesis it never opened, and they reached the catalogue as artists that search answered with.Bach/Gounodis still two people andAC/DCis still one band. - A
416no longer claims a resource is empty. A transcode still being produced has no complete length, andbytes */0did not say unknown β it said empty, which a client may believe and never ask about again. - A refusal that outlived its session neither renews nor replays. A sign-out and a sign-in fit inside one round trip, so a
401raised for one account could arrive after another had signed in, spending the new account's refresh token for a request the old one made. - Plus fixes across the scrobbling queue, the canvas store, upload sessions, the event feed's tenancy and the web client's session handling.
Also from earlier in this cycle: the transcode a seek abandons is now cached, artist favourites survive a change of identity spec, and the track relocation hint is dropped wholesale when its spec changes rather than compared across two.
β What was actually verified
Nothing below was read from a client's display.
Four real clients, replayed on 2026-09-16 against the faΓ§ade as it now stands β Symfonium 15.0.1 and DSub 5.5.3 on an Android 17 emulator, Juliet on a physical iPhone, Feishin 1.15.1 on Windows desktop. Playback and seeking on cold transcodes, catalogue browsing, the full playlist cycle, favourites, ratings, scrobbles and the server-side play queue β every result read back from server state. Seeking by byte range was re-established on iOS and on desktop. See the compatibility matrix.
The upgrade from 2.0.0-beta.0, which nothing in the repository would have caught, because every integration test starts from an empty database: the tagged binary built a database from a real scan of 164 files, and the current binary opened it. 16 migrations, all successful, schema 22 β 38, catalogue and full-text search intact.
π¦ Install
docker run -d --name waveflow \
-p 4533:4533 \
-v waveflow-data:/data \
-v /path/to/music:/music:ro \
ghcr.io/instazdll/waveflow-server:2.0.0-beta.1Or with Compose β set WAVEFLOW_MUSIC_PATH to your music directory, mounted read-only:
WAVEFLOW_MUSIC_PATH=/path/to/music docker compose up -dThen create the admin account and register a library:
docker exec -e WAVEFLOW_ACCOUNT_PASSWORD='at-least-twelve-characters' \
waveflow waveflow-server account create-admin --username admin
docker exec waveflow waveflow-server library add --owner admin --name Music --path /musicPublished for linux/amd64 and linux/arm64, and also tagged beta.
Upgrading from 2.0.0-beta.0
Pull and restart. The 16 migrations run on first boot and were rehearsed on a real catalogue before this tag.
Back up data/waveflow.db and data/instance.key together β they are one unit, and encrypted values cannot be recovered with one without the other. If you have ever lost a key, the way back is now documented in the operations guide.
Known issue
#229 β getPlayQueue with nothing saved answers a bare playQueue, which the schema has no valid empty form for. Conforming means removing the element entirely, a further wire change on the one call every client makes at startup; it is pinned by a contract test and left to decide after the beta. No client has been observed to mind.
This is a beta. If something behaves differently on your client, open an issue β the last campaign found four defects that no automated test could reach. π