NixPlayer is a self-hosted music streaming server inspired by Navidrome, designed for folder-based organization and high-performance music playback. It supports multi-user access, persistent queues, smart playlists, and is fully Dockerized for easy deployment.
- Web-based music player
- Mobile-friendly UI
- Multi-user support
- Persistent playback queue
- Folder-based browsing with database hierarchy
- Tag system and playlists
- Smart playlists (query-based)
- Streaming via HTTP range requests
NixPlayer uses a 4-component architecture:
- API Server – Handles authentication, streaming, metadata, and playlists.
- Music Scanner/Indexer – Scans filesystem, extracts metadata, and updates the database.
- Web Client – React-based frontend for browsing, playback, and administration.
- PostgreSQL Database – Stores all music metadata, users, queues, and playlists.
- Node.js
- Fastify
- Prisma ORM
- PostgreSQL
- JWT authentication
- Node.js service
- music-metadata for reading tags
- chokidar for filesystem watching
- Incremental scanning using file size + mtime fingerprinting
- Parallel metadata parsing for large libraries
- React
- Vite
- Tailwind CSS
- Zustand for player state management
- API supports HTTP range requests
- Endpoint:
/stream/:trackId
- Users
- Sessions
- Tracks
- Folders
- Artists
- Albums
- Tags
- TrackTags
- Playlists
- PlaylistTracks
- QueueItems
- PlaybackState
Example library structure:
/music/Rock/Pink Floyd/Time.flac
- Folder hierarchy is stored in the database
- Supports browsing by folder, artist, album, or tags
- Server-side queue persists across sessions
- Supports reorder, remove, append
- Playback state includes:
- Current track
- Playback position
- Play/pause state
- Stored per user
- Initial full scan on startup
- Incremental updates
- Filesystem watching for new, modified, or deleted tracks
- Cover art extraction
- Efficient for libraries of up to 100k tracks
- Home
- Folders
- Artists
- Albums
- Tags
- Playlists
- Queue
- Search
- Persistent bottom player
- Seek bar, shuffle, repeat
- Next/previous track controls
- Queue panel
docker-compose.yml includes:
api– Backend serverscanner– Music scannerweb– Frontend clientpostgres– Database
Music files should be mounted as a Docker volume for persistent storage.
- Fast scanning and incremental updates
- Indexed database queries for large libraries
- Streaming with minimal latency
nixplayer/ ├── api/ │ ├── src/ │ │ ├── controllers/ │ │ ├── routes/ │ │ └── services/ │ ├── prisma/ │ │ └── schema.prisma │ └── Dockerfile ├── scanner/ │ ├── src/ │ │ ├── scanner.ts │ │ └── metadata-parser.ts │ └── Dockerfile ├── web/ │ ├── src/ │ │ ├── components/ │ │ ├── pages/ │ │ ├── store/ │ │ └── App.jsx │ └── Dockerfile ├── docker-compose.yml └── README.md
model User {
id Int @id @default(autoincrement())
username String @unique
password String
sessions Session[]
queue QueueItem[]
}
model Track {
id Int @id @default(autoincrement())
title String
path String
folder Folder? @relation(fields: [folderId], references: [id])
folderId Int?
artist Artist? @relation(fields: [artistId], references: [id])
artistId Int?
album Album? @relation(fields: [albumId], references: [id])
albumId Int?
tags TrackTag[]
}
model Folder {
id Int @id @default(autoincrement())
name String
parent Folder? @relation("FolderHierarchy", fields: [parentId], references: [id])
parentId Int?
tracks Track[]
}
model Playlist {
id Int @id @default(autoincrement())
name String
tracks PlaylistTrack[]
}
API Routes (Example)
Method Route Description
GET /tracks List all tracks
GET /tracks/:id Get track metadata
GET /stream/:trackId Stream track with range support
POST /playlists Create a new playlist
GET /playlists/:id Get playlist details
POST /queue Add track to queue
GET /queue Get user queue
POST /auth/login User login
POST /auth/register User registration
Scanner Implementation Outline
Initial full scan of /music folder
Extract metadata using music-metadata
Store track info and folder hierarchy in the database
Detect incremental changes:
New files → add to DB
Deleted files → remove from DB
Modified files → update DB
Watch filesystem using chokidar for real-time updates
Extract cover art and associate with tracks/albums
Frontend Architecture
React + Vite for fast development
Tailwind for styling
Zustand for global player state
Pages: Home, Folders, Artists, Albums, Tags, Playlists, Queue, Search
Persistent player at bottom of the viewport
Components:
TrackList
FolderBrowser
PlayerControls
QueuePanel
Dockerfiles (Example)
API
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["node", "src/server.js"]
Scanner
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["node", "src/scanner.js"]
Web
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]
docker-compose.yml (Example)
version: '3.9'
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: ntmusic
POSTGRES_PASSWORD: ntmusic
POSTGRES_DB: ntmusic
volumes:
- pgdata:/var/lib/postgresql/data
api:
build: ./api
image: ghcr.io/nixbin-engineering/nixplayer-api:latest
depends_on:
- postgres
environment:
DATABASE_URL: postgresql://ntmusic:ntmusic@postgres:5432/ntmusic
ports:
- "3000:3000"
volumes:
- ./music:/music:ro
scanner:
build: ./scanner
image: ghcr.io/nixbin-engineering/nixplayer-scanner:latest
depends_on:
- api
- postgres
volumes:
- ./music:/music:ro
web:
build: ./web
image: ghcr.io/nixbin-engineering/nixplayer-web:latest
depends_on:
- api
ports:
- "8085:80"
volumes:
pgdata:
Getting Started
# Clone repository
git clone <repo-url>
cd nixplayer
# Start all services
docker-compose up --build
Access the web client at: http://localhost:8085
API available at: http://localhost:3000
Performance Targets
Scalable to 100k tracks
Fast incremental scanning
Indexed DB queries
Low-latency streaming