liquidWave ← Home
On this page

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 print CN=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.

  1. Download EmbysonicPlugin_<version>.zip from the Releases page.

  2. 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      # Linux
    
    Or do it by hand

    Copy EmbysonicPlugin.dll into …/Emby-Server/programdata/plugins/ and restart Emby.

  3. In Emby, go to Dashboard → Plugins → Emby Sonic and set:

    • Python Service URLhttp://<librarian's-address>:8765
    • API Key → your Emby API key

    …then Save.

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 URLhttp://<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.org called over and over rather than once? The model isn't persisting between restarts — check that your panns_data volume is actually mounted and hasn't been recreated (e.g. by docker 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 :8765 to 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 https so credentials aren't sent in the clear by accident (a plain-HTTP LAN server still works if you type http:// explicitly).
  • allowBackup=false — your Emby session token isn't swept into cloud or adb backups.
  • 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. 🌊