Skip to content

workhardbekind/navidisco

Repository files navigation

navidisco

Shared listening rooms backed by your own Navidrome library. Open a room, share the URL, queue tracks together, chat, send hearts. Synced playback across every device.

What's in the box

  • Synced playback across every connected client (server-driven, sub-second drift, auto-corrects)
  • Lossless audio — Navidrome streams the original file with format=raw; no transcoding
  • Shared queue — anyone in the room can add tracks or queue whole public Navidrome playlists
  • Chat with live presence (see who's listening)
  • Mobile-ready — Media Session integration for lock-screen and Bluetooth controls
  • Heart reactions — tap the heart, pink hearts flood every connected screen
  • Optional admin — one named user can be exempt from rate limits and delete rooms (off by default)

Requirements

  • A running Navidrome instance reachable from wherever you'll host this
  • One Navidrome account that has access to the music you want to share
  • Either Docker (easiest) or Node 24+

Quick start with Docker

git clone https://github.com/YOUR_USERNAME/navidisco.git
cd navidisco
cp .env.example .env.local
# Edit .env.local — fill in NAVIDROME_URL / NAVIDROME_USER / NAVIDROME_PASSWORD
docker compose up --build

Open http://localhost:6969.

The compose stack uses a named volume (navidisco-data) for the SQLite database, so room data survives docker compose down.

Quick start without Docker

git clone https://github.com/YOUR_USERNAME/navidisco.git
cd navidisco
npm install
cp .env.example .env.local
# Edit .env.local
npx prisma migrate deploy
npm run build
npm start

For development with auto-reload:

npm run dev

Configuration

Everything is environment variables. .env.local is gitignored and is the right place to put them locally; in Docker, the compose file reads .env.local automatically.

Variable Required Default Description
NAVIDROME_URL yes URL to your Navidrome instance, e.g. http://192.168.1.10:4533
NAVIDROME_USER yes A Navidrome account with access to the music you want to share
NAVIDROME_PASSWORD yes Password for that account
NAVIDISCO_ADMIN_NAME no Display name granted admin privileges. Case-insensitive.
NAVIDISCO_ADMIN_PASSWORD no Required when claiming the admin name
DATABASE_URL no file:./dev.db SQLite connection string
PORT no 6969 Port to listen on

Admin role

If both NAVIDISCO_ADMIN_NAME and NAVIDISCO_ADMIN_PASSWORD are set:

  • That display name is reserved. Anyone trying to use it has to enter the password.
  • The authenticated admin can delete rooms and is exempt from queue rate limits.

If either is unset, the admin role is disabled. Every user is equal — anyone can pause, skip, queue tracks, but no one can delete rooms.

Per-user rate limits

Non-admin users are limited to 5 tracks added per 5 minutes and 15 per hour (in-memory; resets on server restart). Tweak the constants in src/lib/rateLimit.ts for different numbers.

How sync works

The server is the source of truth. It holds canonical playback state — { trackId, startedAt: wall-clock ms, paused } — and broadcasts updates over Socket.IO. Each client streams the audio independently from a server-side proxy that keeps Navidrome credentials off the wire, computes its target position from Date.now() - startedAt, and re-seeks if its <audio> element drifts more than 600ms. Periodic 3-second drift correction handles tab backgrounding and buffer stalls.

Auto-advance is a server-side setTimeout keyed on track duration. It re-arms on server boot if you restart mid-track. Track changes (auto-advance and lock-screen skip) trigger an immediate optimistic source swap inside the original event tick, which keeps iOS audio focus alive across changes.

Stack

  • Next.js 16 (App Router) with a custom server for Socket.IO
  • Prisma 7 + SQLite (via @prisma/adapter-better-sqlite3)
  • Subsonic API for Navidrome
  • Tailwind v4 for styling

Limitations

  • No real authentication. Display name is just localStorage. Anyone with a room URL gets in. Made for friend-sized groups, not the open web.
  • No transcoding fallback. If a file's codec isn't natively decodable in the browser, it won't play. FLAC, MP3, AAC, OGG, ALAC all work in modern browsers.
  • One process, one database. Not built to scale horizontally.
  • Rate limits are in-memory; restart resets them.

If your use case outgrows any of these, you'll need to fork and extend.

License

MIT

About

Shared listening rooms backed by Navidrome

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages