A multi-tenant streaming platform — two branded Apple TV apps on one backend. Built solo.
The Internet Archive holds an enormous, messy, wonderful library of live concert recordings — decades of taped shows uploaded by the people who recorded them. I wanted to watch it on my TV. So I built the thing that makes that pleasant, and then I built it as a platform rather than a one-off app.
Stagecouch is two branded Apple TV apps served from a single FastAPI/MongoDB backend:
Two distinct audiences, one codebase. It's a personal, non-commercial project — but I built it the way I'd build something at work.
Every request carries an X-StageCouch-App header, and the backend enforces per-app collection scoping at the query level — the AJ Tapes app physically can't reach into the main catalog. Adding a third app is a configuration change, not a fork.
Audio streams directly from the Internet Archive to the device; Stagecouch serves only metadata, search, and curation. That single decision means a 2,300+ recording catalog costs almost nothing to host and runs comfortably on a small self-hosted VPS. No egress bills, no CDN, no media storage.
Eight algorithmic row builders — staff picks, on-this-day, deep cuts, anniversaries, top-rated recordings, and a diversity-freshness mixer — so the apps stay fresh without shipping a new build.
Gapless playback via AVQueuePlayer, with a real-time FFT spectrum analyzer hung off MTAudioProcessingTap and the Accelerate framework (48 bands, 512-sample Hann window, 60 Hz–12 kHz), running off the main thread. The spectrum feeds a library of 25+ Metal fragment shaders I wrote — raymarched fractals, procedural animation, painterly fields.
But here's the restraint that matters: the shipped apps surface only a small, deliberately limited rotation of those shaders, plus a burn-in-prevention pass (drift, dimming, palette evolution, vignetting). Audio-reactive visuals on a television are a burn-in risk most people don't think about. I built many and ship few, on purpose.
The Archive's metadata gives you track_01.flac, not song titles. Resolving real titles meant a three-step pipeline: parse the file metadata, then extract setlists from show descriptions, then — when those fail — a Playwright scraper against etree.org, the taper-trading community's database. Bad-title detection flags candidates by filename pattern, length, and character distribution. Ratings are weighted Bayesian, backfilled from Archive review data.
For content QA: a home-screen preview that mirrors the tvOS layout, and a recording-detail view with a live side-by-side etree diff that highlights title mismatches.
Swift 6 SwiftUI AVFoundation Metal Accelerate Python 3.12 FastAPI MongoDB Playwright React 19 TypeScript Tailwind Linux (PM2 + Caddy)
~350 commits across four repos in five months, solo. AJ Tapes is on TestFlight. It started because I wanted to watch old concerts on my TV. It ended up being the most complete thing I've built end-to-end in one project — frontend, real-time DSP, GPU, a data pipeline, a backend platform, and the infra it runs on.