liquidWave — Setup Guide
liquidWave is a music player for your own Emby server. On top of normal playback it can do the clever stuff you might know from Plexamp — "play me more songs that sound like this one", auto-curated mixes, a journey from one song to another — by listening to your library once and learning what each track actually sounds like.
All of it runs on your own hardware. Nothing about your music, your listening, or your login ever leaves your network. There's no account and no cloud service. Full detail here.
Which version is for you?
liquidWave is an Android app — that's where the full experience lives, and where almost all the testing is happening right now.
- You have an Android phone. Everything on this page is for you, and Part 1 gets you a working player in about five minutes — no backend required.
- No Android phone (a PC, an iPhone, an iPad). There's also a web app that runs
in any browser. But unlike the Android app it is not standalone — the coordinator
serves it, so you have to do the Part 2 backend setup first before you can use
it at all. There's no 5-minute path for you; you're going straight to Part 2, and the
web app becomes your player once the coordinator is running.
On iPhone/iPad specifically, Safari won't let a web page control audio volume, so crossfade and volume-levelling fall back to a plain track change there. Playback, browsing and the sonic features all still work — just that pair of niceties don't.
iOS testers: a proper native iOS app is a later phase, not this beta. For now the web app (with a coordinator) is the way in.
Read this bit first — there are two stages
Setting this up is not one big job. It's two, and you only have to do the first one.
| What you do | How long | What you get | |
|---|---|---|---|
| Part 1 | Install the app, log into Emby | ~5 minutes | A complete Emby music + audiobook player |
| Part 2 | Set up the analysis backend | An afternoon, plus an overnight scan | The smart features (sonic mixes, radio, similar, adventures) |
Part 2 is entirely optional and you can do it weeks later. The app is genuinely useful on its own — plenty of testers stop after Part 1. So don't let the length of this page put you off: start with Part 1, it's five minutes.
Part 1 — Get it playing (~5 minutes)
What you need
- An Emby server you can log into — version 4.8 or newer.
- An Android phone, Android 8 or newer.
- Both on the same Wi-Fi.
That's it. No API keys, no Docker, nothing to install on your server.
Steps
1. Get the app. Download liquidWave-<version>.apk from the
Releases page, or use the link I
sent you.
2. Install it. Copy the file to your phone and tap it. Android will ask you to allow "install from unknown sources" — that's normal for an app that doesn't come from the Play Store. Allow it.
3. Open the app and log in. Enter:
- Emby server URL — e.g.
http://192.168.1.50:8096 - Your Emby username and password
4. Leave "Coordinator URL" empty. ← This is the important one.
That box is for Part 2. Leaving it blank is correct and expected — the app works perfectly without it. You can fill it in later, any time, in Settings.
5. That's you done. Your library should be there.
What works right now, with nothing else installed
Browsing artists, albums, tracks, genres and audiobooks · full playback with queue, shuffle and repeat · crossfade · the built-in equaliser · mini player · home-screen widget · Google Cast · Android Auto · audiobook resume and speed control · your Emby playlists · Recent Plays · offline downloads (grab a playlist or audiobook and play it with no signal) · search · and the Stations on Home (Library, Random Album, Decade radios).
Want it in the car? (Android Auto needs one extra tap)
Android hides sideloaded apps from Android Auto by default. To switch it on, once:
Android Auto settings → tap Version repeatedly until Developer settings unlock → enable Unknown sources.
liquidWave then appears in your Android Auto app list.
Is this APK safe to install?
Fair question — you should ask that about any app you sideload.
- It's a release build, the same optimised code a Play Store build would run.
- Since beta.19 it's signed with a private release key (
CN=liquidguru), not a throwaway debug key. You can check this yourself, no password needed:apksigner verify --print-certs <apk>— it should printCN=liquidguru. - Only install builds you got directly from me.
The app asks for very few permissions: internet and network access (to reach Emby), permission to keep playing in the background, and notifications. No location, no contacts, no microphone. More detail under Privacy & security.
If Part 1 is all you wanted — you're finished. Enjoy it. 🌊 Everything below is optional.
Part 2 — Add the smart features (optional)
What you get for the effort
- Sonic Mixes — mixes auto-curated from how your music actually sounds.
- Track Radio — an endless queue that flows on from any song.
- Similar tracks, artists and albums.
- Sonic Adventure — pick a start song and an end song; it morphs between them.
- Artist Mix Creator and the Guest DJ.
- Volume normalisation — evens out loudness so a quiet track and a loud one sit at the same level.
- Smarter crossfade — crossfade itself works without any of this, but once your library is analysed it also knows where each song's music actually starts and stops. Blends then skip a quiet intro or a long fade-out and land on real music, instead of spending the blend mixing near-silence.
Without these, the app just doesn't show them. Nothing breaks.
The three pieces, in plain English
You'll run three small things. None of them need a fast machine, and none of them touch your music files.
| Piece | Think of it as | What it actually does |
|---|---|---|
| Coordinator | the librarian | Keeps the notes on what each song sounds like, and answers the app's questions. Tiny — happily runs on a NAS. |
| Worker | the listener | Listens to each song once and does the heavy maths. This is the slow bit, and it's a one-time job. |
| Plugin | the doorbell | A small add-on for Emby that tells the librarian whenever you add new music. |
They talk to each other over your network, so they can all live on one machine or on three different ones — any mix of Windows, Linux, macOS or a NAS.
You should never have to compile anything. The fiddly parts (audio libraries, the right PyTorch build, a ~327 MB model file) are handled for you by the defaults.
Where should each piece go?
Find yourself in this table:
| Your situation | Run the librarian on | Run the listener on |
|---|---|---|
| Synology / QNAP / NAS runs Emby | Docker on the NAS | Your desktop or gaming PC |
| One Windows box does everything | That box | That same box |
| Linux server + a separate GPU rig | The Linux server | The GPU box |
| No GPU anywhere | Either | Any box — just slower |
A graphics card makes the listening roughly twice as fast. It is not required.
Before you start
- You administer your Emby server (can install a plugin and restart it).
- An Emby API key — Emby Dashboard → Advanced → API Keys → new key.
- Somewhere to run the librarian: Docker, or Python 3.11+.
- Everything on the same network. None of this should be exposed to the public internet.
- Patience for the first scan — see How long will this take?
Step 1 — Get the code and configure it
git clone https://github.com/liquidguru/emby-sonic.git
cd emby-sonic
cp .env.example .env
Now open .env in any text editor and fill in three lines:
EMBY_URL=http://<your-emby-host>:8096
EMBY_API_KEY=<the API key you made above>
WORKER_SECRET=<make up any long random string>
WORKER_SECRET is just a password you invent, so only your own listeners can talk to
your librarian. Any long random string is fine.
Do you use the web app from outside your home?
If you reach Emby from outside your network — a public domain, a reverse proxy, or a tunnel such as Cloudflare Tunnel — also set:
EMBY_URL_EXTERNAL=https://<your-public-emby-domain>
The web app streams audio straight from Emby to your browser, so when you're away from home it needs Emby's externally-reachable address, or playback will fail.
Step 2 — Start the librarian (coordinator)
Pick the one that matches your setup:
🐳 Docker — easiest, and best on a NAS
There's a guided installer. It works out whether you have a graphics card, pulls the right prebuilt images, and starts everything:
chmod +x ./install.sh
./install.sh
Or, if you'd rather do it by hand:
docker compose up -d coordinator
🪟 Windows — no Docker needed
There's a wrapper script that sets everything up and runs the tests. It builds its own isolated environment, so it won't disturb any Python you already have:
.\dev.ps1 bootstrap # first time
.\dev.ps1 test # check it's happy
🐧 macOS / Linux without Docker (plain Python 3.11+)
pip install torch --index-url https://download.pytorch.org/whl/cpu
pip install -r requirements.txt
python main.py
Check it worked. However you started it, the librarian listens on port 8765. Open this in a browser:
http://<that-machine's-address>:8765/docs
You should get a page of interactive API documentation. If you do, it's running. ✅
Step 3 — Install the Emby plugin (the doorbell)
Emby has no "upload a plugin" button, so a small script drops the file in for you. You don't need to compile anything.
Download
EmbysonicPlugin_<version>.zipfrom the Releases page.Unzip it on the machine running Emby, and run the installer inside. It finds Emby, copies the file in, and restarts it:
./install.ps1 # Windows./install.sh # LinuxOr do it by hand
Copy
EmbysonicPlugin.dllinto…/Emby-Server/programdata/plugins/and restart Emby.In Emby, go to Dashboard → Plugins → Emby Sonic and set:
- Python Service URL →
http://<librarian's-address>:8765 - API Key → your Emby API key
…then Save.
- Python Service URL →
Step 4 — Let it listen to your library
First, tell the librarian to make a list of every track. This is instant — no listening happens yet:
curl -X POST http://<librarian-host>:8765/sonic/library/scan \
-H "X-Emby-Token: <your-emby-api-key>"
Now start a listener. This is the slow part — run it on your beefiest machine:
COORDINATOR_URL=http://<librarian-host>:8765 \
WORKER_SECRET=<the same secret from your .env> \
WORKER_ID=worker-1 \
python worker.py
You can run several listeners at once on different machines to speed things up.
Check progress any time:
curl http://<librarian-host>:8765/sonic/status -H "X-Emby-Token: <your-emby-api-key>"
You don't have to wait for 100%. Mixes, radio and similar start working as soon as a chunk of your library is done. Leave it running overnight and have a look in the morning.
Make it run automatically from now on
Once you're happy, install the listener as a background service (a Scheduled Task on Windows, or systemd on Linux) so new music gets analysed without you doing anything. See README → Automatic analysis.
Step 5 — Point the app at it
In the app: Settings → Coordinator URL → http://<librarian-host>:8765
The smart features appear as your library finishes analysing. Done. 🌊
Want the smart features when you're away from home?
The Coordinator URL doesn't have to be a LAN address. If you'd like mixes, radio and similar to keep working when you're out, there are two good ways to do it:
- A VPN back home (Tailscale, WireGuard, or your router's own VPN). Nothing is exposed publicly at all — your phone is simply "on" your home network.
- A tunnel — e.g. Cloudflare Tunnel. This gives the coordinator a hostname you
can reach from anywhere without opening any inbound port on your router. Set
the app's Coordinator URL to that hostname (
https://sonic.example.com) and it behaves away from home exactly as it does on the sofa. You can do the same for Emby itself.
Either way, don't simply port-forward :8765 — that puts a plain-HTTP service
directly on the internet. A tunnel or VPN avoids that entirely.
Every coordinator route already requires a token checked against your own Emby server, so it isn't open to anyone who finds it. A tunnel is still the better option because it also lets you put your own access control in front (Cloudflare Access, for instance) and terminates HTTPS for you.
How long will this take?
The listening is a one-time job, and it's the only slow part:
- With a graphics card: ~10–15 seconds per track
- CPU only: ~20–30 seconds per track
A few thousand tracks is an overnight run. After that it's automatic and incremental — new music gets picked up as you add it.
Audiobooks are skipped on purpose. Only your music library is analysed, so spoken word never turns up in Track Radio or Similar. You still browse and play audiobooks as normal.
Analysed your library before beta.18? (optional crossfade improvement)
Crossfade can now skip a song's silent tail so the blend lands on actual music. That needs one extra measurement per track. New music picks this up automatically — this is only to fill in a library analysed before beta.18.
Docker — run it in the worker container, with the database attached:
docker compose run --rm -v emby-sonic-data:/app/data \
worker python tools/backfill_edges.py
# try a small batch first:
docker compose run --rm -v emby-sonic-data:/app/data \
worker python tools/backfill_edges.py --limit 50
Without Docker — wherever you installed requirements.txt:
python tools/backfill_edges.py # whole library, resumable
python tools/backfill_edges.py --limit 50 # try a small batch first
This is not a re-analysis — the neural model never loads, so it's 10× cheaper.
On an Intel N100 streaming over the network: **42 tracks/minute** (~1,000 tracks ≈
25 min, ~5,000 ≈ 2 h, ~25,000 ≈ 10 h). Safe to stop and re-run, and safe to run while
everything else is live.
Entirely optional — without it, crossfade behaves as it did before.
Why the worker and not the coordinator? It needs both the audio library and the database, and only the worker container can have both (the coordinator image ships without librosa on purpose — that's what keeps it small enough for an ARM NAS). If your coordinator and worker are on different machines, run this on the coordinator's machine, since that's where the database lives.
Privacy & security
It's your music, your server, your network. Nothing about your library, your listening, or your credentials is sent to me or to any third party.
Your files are never modified. The analysis only ever reads audio. Results go into the librarian's own separate database — never back into your files or Emby's metadata. The only things ever written are the normal play counts and resume positions any Emby app writes, plus an Emby playlist only when you explicitly tap "save" on a mix.
The full detail — no cloud, no telemetry, what talks to what
Fully self-hosted:
- All three pieces run on your own hardware. There is no Emby Sonic account, no SaaS backend, no analytics and no telemetry.
- Analysis happens locally. The results and the search index live in a database file on your own coordinator and never leave it.
- Outbound internet is a small, fixed set of domains — worth knowing if you run a firewall, Pi-hole or router that flags new outbound traffic:
| Domain | When | Why |
|---|---|---|
pypi.org, pythonhosted.org |
install/build only | Downloading the Python dependencies. Not called once it's running. |
download.pytorch.org |
install/build only | The PyTorch wheel. |
zenodo.org |
worker, first run only | The one-time ~327 MB analysis model, from its author's own hosting. Cached afterwards. |
ghcr.io |
only if you use the prebuilt images | Pulling the published Docker images. |
After that first run the analysis stack can run fully air-gapped. No audio and no metadata is ever uploaded, to me or anyone else.
Seeing
zenodo.orgcalled over and over rather than once? The model isn't persisting between restarts — check that yourpanns_datavolume is actually mounted and hasn't been recreated (e.g. bydocker compose down -v).
On the phone — one honest exception. The table above covers the coordinator and worker. On the phone itself, liquidWave includes Google's Cast library so that "Cast to a Chromecast" works, and casting is handled by Google Play Services on your device. So if you're watching your phone's traffic you may well see Play Services talking to Google — that's Google's own component doing what it does in any Cast-enabled app, not liquidWave reporting anything about you.
liquidWave itself bundles no analytics, crash-reporting or telemetry SDK of any kind. Apart from that Google component, the only server the app talks to is yours.
Stays on your network:
- Workers fetch audio straight from Emby's own API over your LAN — no file shares, no extra ports opened.
- The coordinator speaks plain HTTP and is meant to sit inside your network.
Don't port-forward
:8765to the internet. For remote access use a VPN (Tailscale, WireGuard) or a tunnel such as Cloudflare Tunnel — a tunnel needs no inbound port open, terminates HTTPS for you, and lets you add your own access control in front. Protect it exactly as you'd protect Emby itself.
Logins & secrets:
- Every coordinator route requires a token validated against your Emby server — the coordinator trusts Emby, not its own user list.
- Workers use a separate
WORKER_SECRET, so it can be rotated independently of your Emby API key. - Your Emby API key is never embedded in audio download URLs.
On the phone:
- Login defaults to
httpsso credentials aren't sent in the clear by accident (a plain-HTTP LAN server still works if you typehttp://explicitly). allowBackup=false— your Emby session token isn't swept into cloud oradbbackups.- The token is stored in app-private storage, encrypted at rest via Android Keystore.
- The APK is minified and shrunk like a Play Store build, and release-signed
(
CN=liquidguru, RSA 4096) since beta.19.
Permissions, and why:
- Only internet + network state (reach Emby), foreground service + wake lock (keep playback and the media notification alive), and notifications. No location, no contacts, no microphone.
- Notifications are only requested when you start a download, so the app can tell you it finished. Say no and downloads still work. (The playback controls on your lock screen and in the car appear either way.)
- On Android 16 you may see a "Local network" permission. That's the system's new local-network access, used to reach Emby on your LAN. It is not GPS — the app declares no location permission at all.
Honest beta caveat: the coordinator's CORS policy is currently permissive. Harmless for a LAN-only service, and another reason not to expose it.
What to try, and what to report
After Part 1 — just use it as your player. Playback, the queue, crossfade, the equaliser, Android Auto in the car, offline downloads (grab a playlist, switch on airplane mode, check it still plays).
After Part 2, once some of your library is analysed:
- Open a track → Track Radio, and the Similar tab.
- Mixes tab — try "refresh" on one, and "save as playlist".
- Sonic Adventure — pick a start and end track, hear it morph between them.
- Artist Mix Creator — pick an artist, add a few of the similar ones it suggests, then build it.
- Guest DJ queue injection.
- Volume normalisation — play a quiet track then a loud one, toggle it off in Settings and replay. With it on they sit at a similar level; off, the loud one jumps.
- Crossfade with Skip silent endings on (the default) — the blend lands on where the music actually stops, rather than mixing near-silence.
- Customize Home (sliders icon, top-right) — reorder or hide any row.
- Cast to a Chromecast / Android TV / SHIELD.
Please report: what you tried, your device and Android version, what you expected versus what happened, and any crash. Screenshots help enormously. "This felt slow" and "this felt weird" are exactly the kind of note I want — rough edges are the point.
Known limitations
- Android is the full experience. Other platforms use the browser web app, which needs the coordinator (Part 2) and is slightly limited on iOS Safari — see Which version is for you? A native iOS app is a later phase.
- The first library analysis is slow. It's a one-time cost.
Thanks again — your feedback shapes what ships. 🌊