1# plyr.fm - status 2 3## long-term vision 4 5### the problem 6 7today's music streaming is fundamentally broken: 8- spotify and apple music trap your data in proprietary silos 9- artists pay distribution fees and streaming cuts to multiple gatekeepers 10- listeners can't own their music collections - they rent them 11- switching platforms means losing everything: playlists, play history, social connections 12 13### the atproto solution 14 15plyr.fm is built on the AT Protocol (the protocol powering Bluesky) and enables: 16- **portable identity**: your music collection, playlists, and listening history belong to you, stored in your personal data server (PDS) 17- **decentralized distribution**: artists publish directly to the network without platform gatekeepers 18- **interoperable data**: any client can read your music records - you're not locked into plyr.fm 19- **authentic social**: artist profiles are real ATProto identities with verifiable handles (@artist.bsky.social) 20 21### the dream state 22 23plyr.fm should become: 24 251. **for artists**: the easiest way to publish music to the decentralized web 26 - upload once, available everywhere in the ATProto network 27 - direct connection to listeners without platform intermediaries 28 - real ownership of audience relationships 29 302. **for listeners**: a streaming platform where you actually own your data 31 - your collection lives in your PDS, playable by any ATProto music client 32 - switch between plyr.fm and other clients freely - your data travels with you 33 - share tracks as native ATProto posts to Bluesky 34 353. **for developers**: a reference implementation showing how to build on ATProto 36 - open source end-to-end example of ATProto integration 37 - demonstrates OAuth, record creation, federation patterns 38 - proves decentralized music streaming is viable 39 40--- 41 42**started**: October 28, 2025 (first commit: `454e9bc` - relay MVP with ATProto authentication) 43 44--- 45 46## recent work 47 48### December 2025 49 50#### supporter-gated content (PR #637, Dec 22-23) 51 52**atprotofans paywall integration** - artists can now mark tracks as "supporters only": 53- tracks with `support_gate` require atprotofans validation before playback 54- non-supporters see lock icon and "become a supporter" CTA linking to atprotofans 55- artists can always play their own gated tracks 56 57**backend architecture**: 58- audio endpoint validates supporter status via atprotofans API before serving gated content 59- HEAD requests return 200/401/402 for pre-flight auth checks (avoids CORS issues) 60- `R2Storage.move_audio()` moves files between public/private buckets when toggling gate 61- background task handles bucket migration asynchronously 62- ATProto record syncs when toggling gate (updates `supportGate` field and `audioUrl`) 63 64**frontend**: 65- `playback.svelte.ts` guards queue operations with gated checks BEFORE modifying state 66- clicking locked track shows toast with CTA - does NOT interrupt current playback 67- portal shows support gate toggle in track edit UI 68 69--- 70 71#### supporter badges (PR #627, Dec 21-22) 72 73**phase 1 of atprotofans integration**: 74- supporter badge displays on artist pages when logged-in viewer supports the artist 75- calls atprotofans `validateSupporter` API directly from frontend (public endpoint) 76- badge only shows when viewer is authenticated and not viewing their own profile 77 78--- 79 80#### rate limit moderation endpoint (PR #629, Dec 21) 81 82**incident response**: detected suspicious activity - 72 requests in 17 seconds from a single IP targeting `/moderation/sensitive-images`. added `10/minute` rate limit using existing slowapi infrastructure. 83 84--- 85 86#### end-of-year sprint planning (PR #626, Dec 20) 87 88**focus**: two foundational systems need solid experimental implementations by 2026. 89 90| track | focus | status | 91|-------|-------|--------| 92| moderation | consolidate architecture, add rules engine | in progress | 93| atprotofans | supporter validation, content gating | shipped (phase 1-3) | 94 95**research docs**: 96- [moderation architecture overhaul](docs/research/2025-12-20-moderation-architecture-overhaul.md) 97- [atprotofans paywall integration](docs/research/2025-12-20-atprotofans-paywall-integration.md) 98 99--- 100 101#### beartype + moderation cleanup (PRs #617-619, Dec 19) 102 103**runtime type checking** (PR #619): 104- enabled beartype runtime type validation across the backend 105- catches type errors at runtime instead of silently passing bad data 106- test infrastructure improvements: session-scoped TestClient fixture (5x faster tests) 107 108**moderation cleanup** (PRs #617-618): 109- consolidated moderation code, addressing issues #541-543 110- `sync_copyright_resolutions` now runs automatically via docket Perpetual task 111- removed dead `init_db()` from lifespan (handled by alembic migrations) 112 113--- 114 115#### UX polish (PRs #604-607, #613, #615, Dec 16-18) 116 117**login improvements** (PRs #604, #613): 118- login page now uses "internet handle" terminology for clarity 119- input normalization: strips `@` and `at://` prefixes automatically 120 121**artist page fixes** (PR #615): 122- track pagination on artist pages now works correctly 123- fixed mobile album card overflow 124 125**mobile + metadata** (PRs #605-607): 126- Open Graph tags added to tag detail pages for link previews 127- mobile modals now use full screen positioning 128- fixed `/tag/` routes in hasPageMetadata check 129 130--- 131 132#### offline mode foundation (PRs #610-611, Dec 17) 133 134**experimental offline playback**: 135- storage layer using Cache API for audio bytes + IndexedDB for metadata 136- `GET /audio/{file_id}/url` backend endpoint returns direct R2 URLs for client-side caching 137- "auto-download liked" toggle in experimental settings section 138- Player checks for cached audio before streaming from R2 139 140--- 141 142### Earlier December 2025 143 144See `.status_history/2025-12.md` for detailed history including: 145- visual customization with custom backgrounds (PRs #595-596, Dec 16) 146- performance & moderation polish (PRs #586-593, Dec 14-15) 147- mobile UI polish & background task expansion (PRs #558-572, Dec 10-12) 148- confidential OAuth client for 180-day sessions (PRs #578-582, Dec 12-13) 149- pagination & album management (PRs #550-554, Dec 9-10) 150- public cost dashboard (PRs #548-549, Dec 9) 151- docket background tasks & concurrent exports (PRs #534-546, Dec 9) 152- artist support links & inline playlist editing (PRs #520-532, Dec 8) 153- playlist fast-follow fixes (PRs #507-519, Dec 7-8) 154- playlists, ATProto sync, and library hub (PR #499, Dec 6-7) 155- sensitive image moderation (PRs #471-488, Dec 5-6) 156- teal.fm scrobbling (PR #467, Dec 4) 157- unified search with Cmd+K (PR #447, Dec 3) 158- light/dark theme system (PR #441, Dec 2-3) 159- tag filtering and bufo easter egg (PRs #431-438, Dec 2) 160 161### November 2025 162 163See `.status_history/2025-11.md` for detailed history including: 164- developer tokens (PR #367) 165- copyright moderation system (PRs #382-395) 166- export & upload reliability (PRs #337-344) 167- transcoder API deployment (PR #156) 168 169## immediate priorities 170 171### end-of-year sprint (Dec 20-31) 172 173see [sprint tracking issue #625](https://github.com/zzstoatzz/plyr.fm/issues/625) for details. 174 175| track | focus | status | 176|-------|-------|--------| 177| moderation | consolidate architecture, add rules engine | planning | 178| atprotofans | supporter validation, content gating | shipped | 179 180### known issues 181- playback auto-start on refresh (#225) 182- iOS PWA audio may hang on first play after backgrounding 183 184### backlog 185- audio transcoding pipeline integration (#153) - transcoder service deployed, integration deferred 186- share to bluesky (#334) 187- lyrics and annotations (#373) 188 189## technical state 190 191### architecture 192 193**backend** 194- language: Python 3.11+ 195- framework: FastAPI with uvicorn 196- database: Neon PostgreSQL (serverless) 197- storage: Cloudflare R2 (S3-compatible) 198- background tasks: docket (Redis-backed) 199- hosting: Fly.io (2x shared-cpu VMs) 200- observability: Pydantic Logfire 201- auth: ATProto OAuth 2.1 202 203**frontend** 204- framework: SvelteKit (v2.43.2) 205- runtime: Bun 206- hosting: Cloudflare Pages 207- styling: vanilla CSS with lowercase aesthetic 208- state management: Svelte 5 runes 209 210**deployment** 211- ci/cd: GitHub Actions 212- backend: automatic on main branch merge (fly.io) 213- frontend: automatic on every push to main (cloudflare pages) 214- migrations: automated via fly.io release_command 215 216**what's working** 217 218**core functionality** 219- ✅ ATProto OAuth 2.1 authentication 220- ✅ secure session management via HttpOnly cookies 221- ✅ developer tokens with independent OAuth grants 222- ✅ platform stats and Media Session API 223- ✅ timed comments with clickable timestamps 224- ✅ artist profiles synced with Bluesky 225- ✅ track upload with streaming 226- ✅ audio streaming via 307 redirects to R2 CDN 227- ✅ play count tracking, likes, queue management 228- ✅ unified search with Cmd/Ctrl+K 229- ✅ teal.fm scrobbling 230- ✅ copyright moderation with ATProto labeler 231- ✅ docket background tasks (copyright scan, export, atproto sync, scrobble) 232- ✅ media export with concurrent downloads 233- ✅ supporter-gated content via atprotofans 234 235**albums** 236- ✅ album CRUD with cover art 237- ✅ ATProto list records (auto-synced on login) 238 239**playlists** 240- ✅ full CRUD with drag-and-drop reordering 241- ✅ ATProto list records (synced on create/modify) 242- ✅ "add to playlist" menu, global search results 243 244**deployment URLs** 245- production frontend: https://plyr.fm 246- production backend: https://api.plyr.fm 247- staging: https://stg.plyr.fm / https://api-stg.plyr.fm 248 249### technical decisions 250 251**why Python/FastAPI instead of Rust?** 252- rapid prototyping velocity during MVP phase 253- trade-off: accepting higher latency for faster development 254 255**why Cloudflare R2 instead of S3?** 256- zero egress fees (critical for audio streaming) 257- S3-compatible API, integrated CDN 258 259**why async everywhere?** 260- I/O-bound workload: most time spent waiting on network/disk 261- PRs #149-151 eliminated all blocking operations 262 263## cost structure 264 265current monthly costs: ~$18/month (plyr.fm specific) 266 267see live dashboard: [plyr.fm/costs](https://plyr.fm/costs) 268 269- fly.io (plyr apps only): ~$12/month 270- neon postgres: $5/month 271- cloudflare (R2 + pages + domain): ~$1.16/month 272- audd audio fingerprinting: $0-10/month (6000 free/month) 273- logfire: $0 (free tier) 274 275## admin tooling 276 277### content moderation 278script: `scripts/delete_track.py` 279 280usage: 281```bash 282uv run scripts/delete_track.py <track_id> --dry-run 283uv run scripts/delete_track.py <track_id> 284uv run scripts/delete_track.py --url https://plyr.fm/track/34 285``` 286 287## for new contributors 288 289### getting started 2901. clone: `gh repo clone zzstoatzz/plyr.fm` 2912. install dependencies: `uv sync && cd frontend && bun install` 2923. run backend: `uv run uvicorn backend.main:app --reload` 2934. run frontend: `cd frontend && bun run dev` 2945. visit http://localhost:5173 295 296### development workflow 2971. create issue on github 2982. create PR from feature branch 2993. ensure pre-commit hooks pass 3004. merge to main → deploys to staging 3015. create github release → deploys to production 302 303### key principles 304- type hints everywhere 305- lowercase aesthetic 306- ATProto first 307- async everywhere (no blocking I/O) 308- mobile matters 309- cost conscious 310 311### project structure 312``` 313plyr.fm/ 314├── backend/ # FastAPI app & Python tooling 315│ ├── src/backend/ # application code 316│ ├── tests/ # pytest suite 317│ └── alembic/ # database migrations 318├── frontend/ # SvelteKit app 319│ ├── src/lib/ # components & state 320│ └── src/routes/ # pages 321├── moderation/ # Rust moderation service (ATProto labeler) 322├── transcoder/ # Rust audio transcoding service 323├── docs/ # documentation 324└── justfile # task runner 325``` 326 327## documentation 328 329- [docs/README.md](docs/README.md) - documentation index 330- [runbooks](docs/runbooks/) - production incident procedures 331- [background tasks](docs/backend/background-tasks.md) - docket task system 332- [logfire querying](docs/tools/logfire.md) - observability queries 333- [moderation & labeler](docs/moderation/atproto-labeler.md) - copyright, sensitive content 334- [lexicons overview](docs/lexicons/overview.md) - ATProto record schemas 335 336--- 337 338this is a living document. last updated 2025-12-23.