crkayes
← Blog

Viora

Building Viora: my own music streaming service

Why I built a private Spotify for my own music, how a song gets from an upload to your headphones, and what running it taught me.

Design mockup of the Viora web app home screen, with Continue listening and Recently added shelves

I have a lot of music, and I wanted to listen to it the way I listen to Spotify: on my phone, in the browser, offline on a flight, with lyrics and playlists that follow me from one device to the next. The files are mine, so I wanted the service to be mine too. Viora is that: a music streaming service running on my own servers, for me and my family.

A platform, not just a player

The first decision was to treat Viora as a media platform that starts with music. The backend is one NestJS app split into modules: accounts, catalog, media, streaming, playlists, lyrics, analytics. Nothing in the core assumes the content is a song, so podcasts or audiobooks can be added later without a redesign.

From upload to headphones

Everything starts in the admin dashboard. I drop in a song, or a whole folder, and Viora:

  1. Stores the original file untouched.
  2. Queues a background job that runs FFmpeg to make the streaming versions: AAC at three bitrates plus adaptive HLS, and FLAC when the source is lossless.
  3. Measures loudness, draws the waveform and pulls out the cover art.
  4. Reads the file's tags, cleans up messy titles, and fingerprints the audio to match it against MusicBrainz. Confident matches are applied automatically, and the rest wait in a review queue.
  5. Fetches synced lyrics from LRCLIB and adds the song to search.

Playback never goes through the API. The player asks the API for permission, gets a short-lived signed link, and streams the audio straight from storage, an S3-compatible Garage server on another of my machines. The API only answers small requests, so it stays quick however many songs are playing.

Now Playing in the Viora phone app
Design mockup of Now Playing on the phone. The waveform is also the seek bar.

Three apps, one backend

  • Web: a Next.js app at vioramusic.com. On a phone it installs like an app, with a tab bar and a full-screen player. On a computer it supports keyboard shortcuts and media keys.
  • Phone: a Flutter app for iOS and Android, with background playback, lock-screen controls and offline downloads.
  • Admin: a separate Next.js dashboard for uploads, metadata, artwork, lyrics and users.

The apps share the same features: favorites, playlists with folders, smart playlists, listening history and stats, typo-tolerant search with filters, browsing by genre, mood and decade, and synced lyrics. Start a song on the phone, and the laptop offers to carry on from the same spot.

An album page in the Viora web app
Design mockup of an album page, with other editions of the album underneath.

How I built it

I built Viora with AI, in a set order. Claude, acting as the architect, wrote the architecture, a feature list of about 200 items, and then one detailed build prompt at a time. Claude Code in VS Code built each one. I ran it, the architect reviewed the code, and only then did we move on. Each prompt ended as a tagged release.

That rhythm was fast. The first commit was on 16 July, Viora was live on vioramusic.com on 19 July, and three weeks in it had reached version 0.41. The architecture document and a running build log kept every session pointed in the same direction.

What running it taught me

Two problems only showed up once real music was flowing through it:

  • A full disk looks like an app bug. A big batch of imports filled the server's 24 GB disk. Postgres couldn't write, and signing in failed with "An unexpected error occurred". I grew the disk, and a timer now pauses incoming downloads when it passes 90% full.
  • Slow storage can freeze everything. When I reprocessed a few hundred songs at once, the storage server fell behind and some requests took seconds. The transcoding jobs waited on them forever. Every storage call now has a timeout and retries, so slow storage slows Viora down instead of stopping it.

What's next

The roadmap is long: an equalizer, crossfade and gapless playback, a sleep timer, sign-in with Google, Apple and passkeys, casting to speakers and TVs, CarPlay and Android Auto, shared playlists, and AI playlists and recommendations. Podcasts and audiobooks come after that.

You can follow along on the Viora project page.

  • viora
  • nestjs
  • flutter
  • self-hosting