The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗A free music player with full songs, live lyrics, karaoke, story cards and your library on every device.
Spindle is built like an after-hours record store: a living colour field behind everything, a crate of records to dig through, album sleeves you can turn over, and a turntable you can scratch. Every song plays in full, it's free, and there are no ads.
The name is the pin at the centre of a turntable that every record spins on. In the wordmark, the dot of the i is a tiny record that spins while music plays.
Listening
Your library
Sharing
Everywhere
| Layer | What |
|---|---|
| App | React 18, TypeScript, Vite, Framer Motion, Zustand |
| Graphics | WebGL colour field, Canvas story cards, Web Audio visuals and vocal remover |
| Accounts & sync | Supabase (Postgres, Auth, Realtime) with row-level security |
| Server | Vercel functions: importer, share pages, link-preview images |
| Music | YouTube IFrame API, iTunes Search, Audius, LRCLIB lyrics |
src/
api/ iTunes, YouTube lookup, Audius and lyrics clients
components/ player, deck, share sheet, story maker, sign-in gate…
lib/ sync engine, recommendations, autoplay, history & stats, story cards, offline
pages/ Home, Browse, Search, Library, playlists, Stats, Settings, About…
store/ the player and library state (persisted, synced)
server/ importer and share-page logic, shared by dev and Vercel
api/ Vercel functions: /api/import, /api/share (/s/:code), /api/og
supabase/ database schema and branded email templates
public/ brand assets, service worker, privacy and terms pages
src/lib/sync.ts): each library is one JSON document of keyed entries. Devices send only what changed; the database merges entry by entry (newest wins, deletions carry over), and Realtime tells the other devices to pull.src/components/AudioEngine.tsx): finds the YouTube upload for each song, scoring title, artist and length, and falls back to the iTunes preview. Audius tracks play as audio files.src/lib/recommend.ts): the popular songs of the artists a playlist leans on, with name lookups for songs from YouTube or Audius.npm install
npm run dev
Open http://localhost:5173. Without Supabase keys, accounts are switched off and everything is saved on the device, which is handy for working on the UI.
npm test
Vitest covers stats, recommendations, share pages (including escaping), the importer's link checks and the database rules, which run against a real Postgres in memory (PGlite). GitHub Actions runs the type-check, tests and a build on every push.
Create .env.local (never committed):
VITE_SUPABASE_URL=https://<project>.supabase.co
VITE_SUPABASE_ANON_KEY=<publishable key>
VITE_AUTH_PROVIDERS=github # sign-in buttons to show: github, google
VITE_DISCORD_INVITE=<optional> # invite link for the community Discord, shown on Feedback and Settings
VITE_YT_API_KEY=<optional> # YouTube Data API, only used if the free mirrors fail
The Supabase URL and publishable key are public by design; row-level security keeps every library private to its owner.
This is the beginning of the README. Read the rest at the original. Read the full README ↗