Synchronized music playback for listening to the same music with friends in real-time. Built as an extension to Navidrome. Features a retro Windows 98 / GeoCities aesthetic.
Live at jam.zhgnv.com
Spotify Jam lets you listen to music together, but it requires Spotify Premium and doesn't support your own music library (FLAC files). This project enables synchronized playback of your personal music collection with friends while gaming or hanging out.
┌──────────────────┐
│ Jam Sync Server │
│ (WebSocket) │
└──────────────────┘
▲ ▲
sync cmds │ │ sync cmds
│ │
┌─────┴─────┐ ┌────┴──────┐
│ Client 1 │ │ Client 2 │
│ (Web UI) │ │ (Web UI) │
└─────┬─────┘ └────┬──────┘
│ │
audio HTTP │ │ audio HTTP
▼ ▼
┌──────────────────┐
│ Navidrome │
│ (Your server) │
└──────────────────┘
- Navidrome - Your existing music server (self-hosted or managed). Clients stream audio directly from it via the Subsonic API — the sync server never touches audio data.
- Jam Sync Server - Lightweight WebSocket server that broadcasts playback commands (play/pause/seek/timestamp). Room state snapshots to a persistent volume every 30s and on shutdown, so sessions survive server restarts.
- Web Client - React SPA that handles Navidrome auth, audio playback, and room UI. Connects to both Navidrome (for music) and the sync server (for coordination).
- Synchronized play/pause/seek across all participants (<500ms drift)
- Shared queue with reordering, auto-play, repeat mode, and album auto-queue
- Host controls with co-host delegation
- Library browser — Artists, Albums A-Z, Recently Added, Recently Played, Favorites
- Music search integrated with Navidrome library
- User uploads — Upload music through the web client (streams via SFTP to Navidrome, auto-indexed)
- Likes — Like tracks to save them to your Navidrome favorites (persists across rooms/sessions)
- Liked uploads are protected from auto-cleanup
- Supports FLAC and all formats Navidrome handles
- Playlist browsing — Load Navidrome playlists into the queue
- Invite-code-based self-service registration
- Room resilience — 5-minute grace period on disconnect + state snapshots to persistent volume
- Mobile-friendly layout (Queue/People tabs on ≤1024px screens)
- Windows 98 / GeoCities retro UI theme
- Jam With Boo — Valentine's edition with kawaii avatars and synchronized paw hold
- Navidrome: Go-based music server (existing)
- Sync Server: Node.js + Express + Socket.io
- Client: React + Vite
- Protocol: WebSocket for real-time communication, Subsonic API for music streaming
Choose your deployment method:
Fastest deployment, ~$0-5/month
- Deploy Server: Railway.app — deploy from GitHub, set env vars (see
server/.env.example) - Deploy Client: Vercel.com — import project, set
VITE_NAVIDROME_URLandVITE_JAM_SERVER_URL - Set invite codes: Add
INVITE_CODES,NAVIDROME_ADMIN_USER,NAVIDROME_ADMIN_PASSon Railway for self-service registration
See VERCEL_QUICKSTART.md for details.
Full control, ~$5-10/month
curl -fsSL https://raw.githubusercontent.com/zhiganov/navidrome-jam/main/install.sh | bash-
Navidrome - Install and configure Navidrome
# See: https://www.navidrome.org/docs/installation/ -
Node.js 18+ - Required for sync server and client
-
Clone the repository:
git clone https://github.com/zhiganov/navidrome-jam.git cd navidrome-jam -
Install server dependencies:
cd server npm install cp .env.example .env # Edit .env if needed
-
Install client dependencies:
cd ../client npm install cp .env.example .env # Edit .env to point to your Navidrome instance
-
Start the sync server:
cd ../server npm run dev -
In a new terminal, start the client:
cd client npm run dev -
Open http://localhost:5173 in your browser
See QUICKSTART.md for detailed testing instructions.
This project implements several security measures:
- Input validation and sanitization to prevent XSS attacks
- Rate limiting to prevent abuse
- Token-based authentication with Navidrome
- Session validation on restore
For detailed security considerations, see SECURITY.md.
navidrome-jam/
├── server/ # WebSocket sync server (Node.js + Socket.io)
│ ├── src/
│ │ ├── index.js # Main server with validation & rate limiting
│ │ ├── roomManager.js # Room state management & cleanup
│ │ └── sftpUploader.js # SFTP upload pipeline to PikaPods
│ └── test-client.html # HTML test client
├── client/ # React web client
│ ├── src/
│ │ ├── components/ # React components
│ │ │ ├── SyncedAudioPlayer.jsx # Audio player with volume control
│ │ │ ├── ErrorBoundary.jsx # Error handling wrapper
│ │ │ ├── catData.js # Avatar definitions + paw SVG (Boo)
│ │ │ ├── CatPicker.jsx # Avatar selection overlay (Boo)
│ │ │ ├── CatDanceFloor.jsx # Animated avatar strip (Boo)
│ │ │ └── PawButton.jsx # Hold-to-activate paw button (Boo)
│ │ ├── contexts/ # React contexts
│ │ │ ├── NavidromeContext.jsx # Navidrome client provider
│ │ │ └── JamContext.jsx # Jam client provider
│ │ ├── services/ # API clients
│ │ │ ├── navidrome.js # Navidrome Subsonic API client
│ │ │ └── jamClient.js # WebSocket client wrapper
│ │ └── App.jsx # Main app with loading states
│ └── public/
│ ├── favicon.svg # Win98 music note favicon
│ ├── og-image.svg # OG image source (Win98 scene)
│ └── og-image.png # Rasterized OG image for social previews
├── docs/ # Documentation
├── QUICKSTART.md # Quick start guide
└── SECURITY.md # Security considerations
Test the sync server with the HTML test client:
cd server
npm run dev
# Open test-client.html in two browser windowsTest with the full stack:
# Terminal 1: Sync server
cd server && npm run dev
# Terminal 2: Web client
cd client && npm run dev
# Open http://localhost:5173 in two browsers- Room settings — Private/public rooms, password protection, permission levels
- Automated tests — Jest for sync server, Vitest + React Testing Library for client
- TypeScript migration — Full codebase migration (server + client)
- My Community integration — Embed shared listening tab in My Community extension
- Playlist browsing: Browse and queue Navidrome playlists from the library browser.
- Room grace period: Rooms stay alive for 5 minutes after the last user disconnects (e.g., LTE handoff while driving). Queue, playback position, and room code are preserved for seamless rejoin.
- Room state snapshots: Active rooms snapshot to persistent volume every 30s and on shutdown. Sessions survive server restarts and redeploys.
- Community selector: Moved from room creation dialog to room header bar for easier access.
- User uploads: Upload audio files through the web client. Files stream to Navidrome via SFTP and are auto-indexed. 30-day auto-cleanup with permanent flag (50/user).
- Persistent likes: Like button syncs to Navidrome favorites via
star.view/unstar.viewSubsonic API. Likes persist across rooms and sessions — if you liked a track before, the button stays active when you encounter it again. - Liked upload protection: Uploaded files with at least one like are exempt from 30-day auto-cleanup.
- Favorites browse mode: New "Favorites" option in the library browser dropdown — shows all your starred tracks from Navidrome.
- Recently Played: Replaced "Random" with "Recently Played" in the browse dropdown (random shuffle button still available on album views).
- SVG transport icons: Replaced CSS pixel art with SVG mask-image icons (Bootstrap Icons for like, Lucide-style for repeat). Monochrome by default, colored when active.
- Jam With Boo: Valentine's Day edition at boo.zhgnv.com. Separate branch (
feature/jam-with-boo) with its own domain, OG images, and favicon. - Kawaii avatars: 9 characters powered by react-kawaii (Cat, Ghost, Planet, IceCream, Mug, Backpack, SpeechBubble, Chocolate, Browser). Avatar picker on join, visible in user list and dance strip.
- Paw hold climax: Hold the paw button for 8 seconds — when 2+ users hold simultaneously, avatars converge into a heart burst with screen flash. Climax persists as long as everyone keeps holding.
- Dance strip: Animated avatar row above the now-playing bar. Avatars bounce when music plays, converge during paw hold, and burst apart on climax.
- Valentine theme: Pink/rose accent colors layered over the Win98 base. Custom OG image and favicon for social sharing.
- Multi-origin CORS: Server
CLIENT_URLnow supports comma-separated origins (e.g.,https://jam.zhgnv.com,https://boo.zhgnv.com).
- Browse modes: Library browser now supports four modes via dropdown — Artists (default), Albums A-Z, Recently Added, and Random (with shuffle button). Albums fetched via
getAlbumList2.view. - Compilation album grouping: Albums with the same name and year are merged into a single entry showing "Various Artists". Clicking opens a combined tracklist from all sub-albums, sorted by disc/track number.
- Artist names in tracklists: Album song view shows per-track artist when it differs from the album artist — essential for compilations and soundtracks.
- Mobile tabs: Queue and People tabs appear on screens ≤1024px, rendering full queue management and user list inline (desktop sidebar panels unchanged).
- License: Changed from MIT to Apache-2.0.
- Sync fixes: Fixed three interrelated bugs — no playback on join, wrong track on host change, race condition when sync arrives before audio element mounts. Server now sends sync event on join; client detects track changes and applies deferred sync via
pendingSyncRef. - Repeat mode: Toggle auto-repeat so the room plays forever. Finished tracks re-append to queue tail. Empty queue + repeat = single-track loop. State persisted in localStorage.
- Album auto-queue: Playing a track from album browse view now queues all remaining album tracks, so next/prev buttons work within the album.
- Active rooms: Room selection screen shows currently active rooms with host name, listener count, and current track. Auto-refreshes every 10 seconds.
- Admin dashboard: Server-rendered Win98-styled page at
/adminfor invite code management — view code status (available/used/who used it), generate new codes, delete codes. Protected by admin password. - Social sharing: OG meta tags, Twitter Cards, custom Win98 favicon (SVG), and OG image with Winamp player scene for rich link previews in messengers.
- GitHub link: Repo link added to login and room selection screens.
- Co-host system: Host can promote/demote users to co-host. Co-hosts get full playback and queue control. Server validates with
canControl()(host OR co-host). Co-host status cleaned up on user leave. - Library browser: Browse tab with artist/album/song navigation. Breadcrumb navigation (Library > Artist > Album). "Queue All" button on album view. Win98 folder icons and album thumbnails.
- Transport controls: Winamp-style prev/play-pause/next buttons with CSS-drawn icons in a dark recessed panel. Play/pause updates reactively via audio element callbacks.
- Queue reordering: Move tracks up/down or remove them. Unicode arrow buttons.
- Play history: Previous track button navigates actual history (3-second threshold — restart vs go back).
- Bug fixes: Queue All only adding last track (stale state closure), invisible username in users list (CSS color inheritance), queue disconnected from player (auto-play on first add).
- Synchronized music playback rooms with WebSocket sync
- Navidrome Subsonic API integration (search, stream, metadata)
- Invite-code-based self-service registration
- Windows 98 / GeoCities retro UI theme
- Deployed to Vercel (client) + Railway (server)
Contributions welcome! Open an issue or PR.
Apache-2.0
- Navidrome - The excellent music server this builds upon
- Inspired by Spotify Jam