All your music, one app. UniTune is a cross-platform music aggregator built with Next.js 15 that unifies Spotify and SoundCloud into a single, elegant player. Search once, play anywhere, and build super-playlists that mix tracks from multiple services.
Status: MVP. Spotify (full) + SoundCloud (public URLs via Widget API). Yandex Music, YouTube Music and Apple Music are on the roadmap.
- 🎧 Unified player across providers with short cross-fade on transitions
- 🔎 Single search that queries every connected provider in parallel
- ❤️ Liked songs and super-playlists stored in Supabase (RLS-protected)
- 🖼️ Modern Spotify-style UI built on Tailwind v4 + shadcn/ui + Radix
- ⌨️ ⌘K command palette, full keyboard navigation
- 📱 Installable PWA with MediaSession controls
- 🌐 i18n: English + Russian out of the box
- 🔐 OAuth tokens encrypted at rest with AES-256-GCM
- Spotify Premium is required for full playback. Free accounts get 30-second
previews via the
preview_urlfallback. - SoundCloud search is not available. SoundCloud closed its developer program to new registrations in 2021 — you can paste any public SoundCloud URL on the Search page to play it via their public Widget API.
- Cross-fade is volume-only between providers. There is no gapless audio mixing because Spotify and SoundCloud don’t expose raw PCM.
- Yandex Music and YouTube Music are not included in the MVP — neither has a public official API. We will not ship unofficial-API integrations.
# 1. Install deps (Node 20+)
npm install
# 2. Copy env vars and fill them in (see sections below)
cp .env.example .env.local
# 3. Apply Supabase schema (one-time, see DEPLOYMENT.md for details)
# psql "$SUPABASE_DB_URL" -f lib/supabase/schema.sql
# 4. Run the dev server
npm run dev
# → http://localhost:3000- Create a free project at https://supabase.com/dashboard.
- Settings → API: copy
Project URL,anon public, andservice_role→ put them into.env.localasNEXT_PUBLIC_SUPABASE_URL,NEXT_PUBLIC_SUPABASE_ANON_KEY, andSUPABASE_SERVICE_ROLE_KEY. - Authentication → Providers: enable Email (magic-link). Optionally enable Google / GitHub. Disable “Confirm email” for a smoother dev flow.
- SQL Editor: paste the contents of
lib/supabase/schema.sqland run it. This createsprofiles,provider_connections,super_playlists,super_playlist_tracks,liked_tracks,play_history, and their RLS policies. - Storage: a
playlist-coversbucket is created by the SQL above.
- Go to https://developer.spotify.com/dashboard and sign in.
- Click Create app:
- App name:
UniTune (local) - App description: anything
- Redirect URI:
http://localhost:3000/api/auth/spotify/callback - APIs used: tick Web API and Web Playback SDK
- Accept the developer terms
- App name:
- Copy the Client ID and Client Secret from the app dashboard into
.env.local. - (Production) Add your production redirect URI
(
https://your-domain/api/auth/spotify/callback) in the Spotify dashboard.
SoundCloud closed new developer registrations in 2021, so UniTune works out of the box using their public oEmbed + Widget API (no key required): paste any public SoundCloud URL on the Search page.
If you have a legacy SOUNDCLOUD_CLIENT_ID, set it in .env.local to unlock
aggregated SoundCloud search.
Generate a 32-byte base64 key for AES-256-GCM token encryption:
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"Put the output into ENCRYPTION_KEY. Never rotate this value after users
have connected providers, or their stored tokens will become unreadable.
app/ # Next.js 15 App Router
(app)/ # authed layout with sidebar + bottom player
(auth)/ # public login pages
api/ # server routes (OAuth, search, library)
components/ # UI + player + layout + providers
lib/
providers/ # MusicProvider abstraction (Spotify, SoundCloud)
player/ # PlayerController singleton + crossfade
supabase/ # SSR client, middleware, schema + RLS
crypto/ # AES-256-GCM token cipher
i18n/ # next-intl config
stores/ # Zustand stores (player, queue, UI)
messages/ # en.json + ru.json
types/ # UnifiedTrack, UnifiedAlbum, …
The core abstraction is the MusicProvider interface in
lib/providers/types.ts. Every provider exposes the same auth, api, and
createPlayer surface, so adding Apple Music, Yandex Music, etc. does not
require touching the UI.
| Script | What it does |
|---|---|
npm run dev |
Next dev server on :3000 |
npm run build |
Production build (with Serwist PWA) |
npm start |
Run the production server |
npm run lint |
ESLint |
npm run typecheck |
TypeScript (strict) type-check |
npm run db:types |
Regenerate types/supabase.ts from remote DB |
Next.js 15 (App Router, Server Actions, RSC) · React 19 · TypeScript (strict) · Tailwind v4 · shadcn/ui · Radix UI · lucide-react · Zustand · TanStack Query v5 · Supabase (auth, Postgres, storage) · next-intl · @serwist/next · Zod.
- Apple Music via MusicKit JS (official SDK)
- Lyrics via Musixmatch (paid API)
- Cross-platform playlist import with ISRC matching
- Electron/Tauri desktop build (PWA covers most of the need today)
- Super-playlist public sharing
- Mobile app on React Native re-using
lib/providers/*
Source-available for personal use. See LICENSE (to be added).