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#### artist support links & inline playlist editing (PRs #520-532, Dec 8) 51 52**artist support link** (PR #532): 53- artists can set a support URL (Ko-fi, Patreon, etc.) in their portal profile 54- support link displays as a button on artist profile pages next to the share button 55- URLs validated to require https:// prefix 56 57**inline playlist editing** (PR #531): 58- edit playlist name and description directly on playlist detail page 59- click-to-upload cover art replacement without modal 60- cleaner UX - no more edit modal popup 61 62**platform stats enhancements** (PRs #522, #528): 63- total duration displayed in platform stats (e.g., "42h 15m of music") 64- duration shown per artist in analytics section 65- combined stats and search into single centered container for cleaner layout 66 67**navigation & data loading fixes** (PR #527): 68- fixed stale data when navigating between detail pages of the same type 69- e.g., clicking from one artist to another now properly reloads data 70 71**copyright moderation improvements** (PR #480): 72- enhanced moderation workflow for copyright claims 73- improved labeler integration 74 75**letta-backed status maintenance** (PR #529): 76- automated status maintenance using Letta AI agent 77- agent reviews merged PRs and updates STATUS.md narratively 78 79--- 80 81#### playlist fast-follow fixes (PRs #507-519, Dec 7-8) 82 83**public playlist viewing** (PR #519): 84- playlists now publicly viewable without authentication 85- ATProto records are public by design - auth was unnecessary for read access 86- shared playlist URLs no longer redirect unauthenticated users to homepage 87 88**inline playlist creation** (PR #510): 89- clicking "create new playlist" from AddToMenu previously navigated to `/library?create=playlist` 90- this caused SvelteKit to reinitialize the layout, destroying the audio element and stopping playback 91- fix: added inline create form that creates playlist and adds track in one action without navigation 92 93**UI polish** (PRs #507-509, #515): 94- include `image_url` in playlist SSR data for og:image link previews 95- invalidate layout data after token exchange - fixes stale auth state after login 96- fixed stopPropagation blocking "create new playlist" link clicks 97- detail page button layouts: all buttons visible on mobile, centered AddToMenu on track detail 98- AddToMenu smart positioning: menu opens upward when near viewport bottom 99 100**documentation** (PR #514): 101- added lexicons overview documentation at `docs/lexicons/overview.md` 102- covers `fm.plyr.track`, `fm.plyr.like`, `fm.plyr.comment`, `fm.plyr.list`, `fm.plyr.actor.profile` 103 104--- 105 106#### playlists, ATProto sync, and library hub (PR #499, Dec 6-7) 107 108**playlists** (full CRUD): 109- create, rename, delete playlists with cover art upload 110- add/remove/reorder tracks with drag-and-drop 111- playlist detail page with edit modal 112- "add to playlist" menu on tracks with inline create 113- playlist sharing with OpenGraph link previews 114 115**ATProto integration**: 116- `fm.plyr.list` lexicon for syncing playlists/albums to user PDSes 117- `fm.plyr.actor.profile` lexicon for artist profiles 118- automatic sync of albums, liked tracks, profile on login 119 120**library hub** (`/library`): 121- unified page with tabs: liked, playlists, albums 122- nav changed from "liked" → "library" 123 124**related**: scope upgrade OAuth flow (PR #503), settings consolidation (PR #496) 125 126--- 127 128#### sensitive image moderation (PRs #471-488, Dec 5-6) 129 130- `sensitive_images` table flags problematic images 131- `show_sensitive_artwork` user preference 132- flagged images blurred everywhere: track lists, player, artist pages, search, embeds 133- Media Session API respects sensitive preference 134- SSR-safe filtering for og:image link previews 135 136--- 137 138#### teal.fm scrobbling (PR #467, Dec 4) 139 140- native scrobbling to user's PDS using teal's ATProto lexicons 141- scrobble at 30% or 30 seconds (same threshold as play counts) 142- toggle in settings, link to pdsls.dev to view records 143 144--- 145 146### Earlier December / November 2025 147 148See `.status_history/2025-12.md` and `.status_history/2025-11.md` for detailed history including: 149- unified search with Cmd+K (PR #447) 150- light/dark theme system (PR #441) 151- tag filtering and bufo easter egg (PRs #431-438) 152- developer tokens (PR #367) 153- copyright moderation system (PRs #382-395) 154- export & upload reliability (PRs #337-344) 155- transcoder API deployment (PR #156) 156 157## immediate priorities 158 159### high priority features 1601. **audio transcoding pipeline integration** (issue #153) 161 - ✅ standalone transcoder service deployed at https://plyr-transcoder.fly.dev/ 162 - ⏳ next: integrate into plyr.fm upload pipeline 163 164### known issues 165- playback auto-start on refresh (#225) 166- no AIFF/AIF transcoding support (#153) 167- iOS PWA audio may hang on first play after backgrounding 168 169### new features 170- issue #146: content-addressable storage (hash-based deduplication) 171- issue #155: add track metadata (genres, tags, descriptions) 172- issue #334: add 'share to bluesky' option for tracks 173- issue #373: lyrics field and Genius-style annotations 174- issue #393: moderation - represent confirmed takedown state in labeler 175 176## technical state 177 178### architecture 179 180**backend** 181- language: Python 3.11+ 182- framework: FastAPI with uvicorn 183- database: Neon PostgreSQL (serverless) 184- storage: Cloudflare R2 (S3-compatible) 185- hosting: Fly.io (2x shared-cpu VMs) 186- observability: Pydantic Logfire 187- auth: ATProto OAuth 2.1 188 189**frontend** 190- framework: SvelteKit (v2.43.2) 191- runtime: Bun 192- hosting: Cloudflare Pages 193- styling: vanilla CSS with lowercase aesthetic 194- state management: Svelte 5 runes 195 196**deployment** 197- ci/cd: GitHub Actions 198- backend: automatic on main branch merge (fly.io) 199- frontend: automatic on every push to main (cloudflare pages) 200- migrations: automated via fly.io release_command 201 202**what's working** 203 204**core functionality** 205- ✅ ATProto OAuth 2.1 authentication 206- ✅ secure session management via HttpOnly cookies 207- ✅ developer tokens with independent OAuth grants 208- ✅ platform stats and Media Session API 209- ✅ timed comments with clickable timestamps 210- ✅ artist profiles synced with Bluesky 211- ✅ track upload with streaming 212- ✅ audio streaming via 307 redirects to R2 CDN 213- ✅ play count tracking, likes, queue management 214- ✅ unified search with Cmd/Ctrl+K 215- ✅ teal.fm scrobbling 216- ✅ copyright moderation with ATProto labeler 217 218**albums** 219- ✅ album CRUD with cover art 220- ✅ ATProto list records (auto-synced on login) 221 222**playlists** 223- ✅ full CRUD with drag-and-drop reordering 224- ✅ ATProto list records (synced on create/modify) 225- ✅ "add to playlist" menu, global search results 226 227**deployment URLs** 228- production frontend: https://plyr.fm 229- production backend: https://api.plyr.fm 230- staging: https://stg.plyr.fm / https://api-stg.plyr.fm 231 232### technical decisions 233 234**why Python/FastAPI instead of Rust?** 235- rapid prototyping velocity during MVP phase 236- trade-off: accepting higher latency for faster development 237 238**why Cloudflare R2 instead of S3?** 239- zero egress fees (critical for audio streaming) 240- S3-compatible API, integrated CDN 241 242**why async everywhere?** 243- I/O-bound workload: most time spent waiting on network/disk 244- PRs #149-151 eliminated all blocking operations 245 246## cost structure 247 248current monthly costs: ~$35-40/month 249 250- fly.io backend (prod + staging): ~$10/month 251- fly.io transcoder: ~$0-5/month (auto-scales to zero) 252- neon postgres: $5/month 253- audd audio fingerprinting: ~$10/month 254- cloudflare pages + R2: ~$0.16/month 255- logfire: $0 (free tier) 256- domain: ~$1/month 257 258## admin tooling 259 260### content moderation 261script: `scripts/delete_track.py` 262 263usage: 264```bash 265uv run scripts/delete_track.py <track_id> --dry-run 266uv run scripts/delete_track.py <track_id> 267uv run scripts/delete_track.py --url https://plyr.fm/track/34 268``` 269 270## for new contributors 271 272### getting started 2731. clone: `gh repo clone zzstoatzz/plyr.fm` 2742. install dependencies: `uv sync && cd frontend && bun install` 2753. run backend: `uv run uvicorn backend.main:app --reload` 2764. run frontend: `cd frontend && bun run dev` 2775. visit http://localhost:5173 278 279### development workflow 2801. create issue on github 2812. create PR from feature branch 2823. ensure pre-commit hooks pass 2834. merge to main → deploys to staging 2845. create github release → deploys to production 285 286### key principles 287- type hints everywhere 288- lowercase aesthetic 289- ATProto first 290- async everywhere (no blocking I/O) 291- mobile matters 292- cost conscious 293 294### project structure 295``` 296plyr.fm/ 297├── backend/ # FastAPI app & Python tooling 298│ ├── src/backend/ # application code 299│ ├── tests/ # pytest suite 300│ └── alembic/ # database migrations 301├── frontend/ # SvelteKit app 302│ ├── src/lib/ # components & state 303│ └── src/routes/ # pages 304├── moderation/ # Rust moderation service (ATProto labeler) 305├── transcoder/ # Rust audio transcoding service 306├── docs/ # documentation 307└── justfile # task runner 308``` 309 310## documentation 311 312- [deployment overview](docs/deployment/overview.md) 313- [configuration guide](docs/configuration.md) 314- [queue design](docs/queue-design.md) 315- [logfire querying](docs/logfire-querying.md) 316- [moderation & labeler](docs/moderation/atproto-labeler.md) 317- [unified search](docs/frontend/search.md) 318- [keyboard shortcuts](docs/frontend/keyboard-shortcuts.md) 319- [lexicons overview](docs/lexicons/overview.md) 320 321--- 322 323this is a living document. last updated 2025-12-08.