music on atproto
plyr.fm
1# plyr.fm documentation
2
3this directory contains all documentation for the plyr.fm project.
4
5## documentation index
6
7### authentication & security
8- **[authentication.md](./authentication.md)** - secure cookie-based authentication, HttpOnly cookies, XSS protection, environment architecture, migration from localStorage
9
10### frontend
11- **[state-management.md](./frontend/state-management.md)** - global state management with Svelte 5 runes (toast notifications, tracks cache, upload manager, queue management, liked tracks, preferences, localStorage persistence)
12- **[toast-notifications.md](./frontend/toast-notifications.md)** - user feedback system for async operations with smooth transitions and auto-dismiss
13- **[queue.md](./frontend/queue.md)** - music queue management with server sync
14- **[keyboard-shortcuts.md](./frontend/keyboard-shortcuts.md)** - global keyboard shortcuts with context-aware filtering (Q for queue toggle, patterns for adding new shortcuts)
15
16### backend
17- **[configuration.md](./backend/configuration.md)** - backend configuration and environment setup
18- **[liked-tracks.md](./backend/liked-tracks.md)** - ATProto-backed track likes with error handling and consistency guarantees
19- **[streaming-uploads.md](./backend/streaming-uploads.md)** - SSE-based progress tracking for file uploads with fire-and-forget pattern
20- **[transcoder.md](./backend/transcoder.md)** - rust-based HTTP service for audio format conversion (ffmpeg integration, authentication, fly.io deployment)
21
22### deployment
23- **[environments.md](./deployment/environments.md)** - staging vs production environments, automated deployment via GitHub Actions, CORS, secrets management
24- **[database-migrations.md](./deployment/database-migrations.md)** - automated migration workflow via fly.io release commands, alembic usage, safety procedures
25
26### tools
27- **[logfire.md](./tools/logfire.md)** - SQL query patterns for Logfire DataFusion database, finding exceptions, analyzing performance bottlenecks
28- **[neon.md](./tools/neon.md)** - Neon Postgres database management and best practices
29- **[pdsx.md](./tools/pdsx.md)** - ATProto PDS explorer and debugging tools
30
31### local development
32- **[setup.md](./local-development/setup.md)** - complete local development setup guide
33
34## ATProto integration
35
36plyr.fm uses a hybrid storage model:
37- audio files stored in cloudflare R2 (scalable, CDN-backed)
38- metadata stored as ATProto records on user's PDS (decentralized, user-owned)
39- local database indexes for fast queries
40
41key namespaces:
42- `fm.plyr.track` - track metadata (title, artist, album, features, image, audio file reference)
43- `fm.plyr.like` - user likes on tracks (subject references track URI)
44
45## quick start
46
47### current state
48
49plyr.fm is fully functional with:
50- ✅ OAuth 2.1 authentication (ATProto)
51- ✅ secure cookie-based sessions (HttpOnly, XSS protection)
52- ✅ R2 storage for audio files (cloudflare CDN)
53- ✅ track upload with streaming (prevents OOM)
54- ✅ ATProto record creation (fm.plyr.track namespace)
55- ✅ music player with queue management
56- ✅ liked tracks (fm.plyr.like namespace)
57- ✅ artist pages and track discovery
58- ✅ share buttons across track, album, and artist detail pages for quick copy-to-clipboard links
59- ✅ modular audio player with dedicated subcomponents for metadata, transport, progress, and volume controls
60- ✅ image uploads for track artwork
61- ✅ audio transcoding service (rust + ffmpeg)
62- ✅ server-sent events for upload progress
63- ✅ toast notifications
64- ✅ user preferences (accent color, auto-play)
65- ✅ keyboard shortcuts (Q for queue toggle)
66
67### local development
68
69see **[local-development/setup.md](./local-development/setup.md)** for complete setup instructions.
70
71quick start:
72```bash
73# backend
74uv run uvicorn backend.main:app --reload --host 0.0.0.0 --port 8001
75
76# frontend
77cd frontend && bun run dev
78
79# transcoder (optional)
80cd transcoder && just run
81```
82
83### deployment
84
85see **[deployment/environments.md](./deployment/environments.md)** for details on:
86- staging vs production environments
87- automated deployment via GitHub Actions
88- environment variables and secrets
89
90see **[deployment/database-migrations.md](./deployment/database-migrations.md)** for:
91- migration workflow and safety procedures
92- alembic usage and testing
93
94## architecture decisions
95
96### why R2 instead of PDS blobs?
97
98PDS blobs are designed for smaller files like images. audio files are:
99- larger (5-50MB per track)
100- require streaming
101- benefit from CDN distribution
102
103R2 provides:
104- scalable storage
105- free egress to cloudflare CDN
106- simple HTTP URLs
107- cost-effective (~$0.015/GB/month)
108
109### why fm.plyr namespace?
110
111plyr.fm uses `fm.plyr.*` as the ATProto namespace:
112- `fm.plyr.track` for track metadata
113- `fm.plyr.like` for user likes
114
115this is a domain-specific lexicon that allows:
116- clear ownership and governance
117- faster iteration without formal approval
118- alignment with the plyr.fm brand
119
120### why hybrid storage?
121
122storing metadata on ATProto provides:
123- user data sovereignty (users own their catalog)
124- decentralization (no single point of failure)
125- portability (users can move to another client)
126
127storing audio on R2 provides:
128- performance (fast streaming via CDN)
129- scalability (handles growth)
130- cost efficiency (cheaper than PDS blobs)
131
132### why separate transcoder service?
133
134the transcoder runs as a separate rust service because:
135- ffmpeg operations are CPU-intensive and can block event loop
136- rust provides better performance for media processing
137- isolation prevents transcoding from affecting API latency
138- can scale independently from main backend
139
140## testing
141
142plyr.fm uses pytest for backend testing:
143
144```bash
145# run all tests
146just test
147
148# run specific test file
149just test tests/api/test_track_likes.py
150
151# run with verbose output
152just test -v
153```
154
155test categories:
156- API endpoints (`tests/api/`)
157- storage backends (`tests/storage/`)
158- ATProto integration (`tests/test_atproto.py`)
159- audio format validation (`tests/test_audio_formats.py`)
160
161see [`tests/CLAUDE.md`](../tests/CLAUDE.md) for testing guidelines.
162
163## troubleshooting
164
165### R2 upload fails
166
167```
168error: failed to upload to R2
169```
170
171**check**:
172- R2 credentials in `.env`
173- bucket exists and is accessible
174- account ID is correct
175
176### ATProto record creation fails
177
178```
179error: failed to create atproto record
180```
181
182**check**:
183- OAuth session is valid (not expired)
184- user has write permissions
185- PDS is accessible
186- record format is valid
187
188### audio won't play
189
190```
191404: audio file not found
192```
193
194**check**:
195- `STORAGE_BACKEND` matches actual storage
196- R2 bucket has public read access
197- file_id matches database record
198
199## monitoring
200
201### key metrics to track
202
2031. **upload success rate**
204 - total uploads attempted
205 - successful R2 uploads
206 - successful record creations
207
2082. **storage costs**
209 - total R2 storage (GB)
210 - monthly operations count
211 - estimated cost
212
2133. **playback metrics**
214 - tracks played
215 - average stream duration
216 - errors/failures
217
218### logging
219
220add structured logging for debugging:
221
222```python
223import structlog
224
225logger = structlog.get_logger()
226
227logger.info(
228 "track_uploaded",
229 track_id=track.id,
230 r2_url=r2_url,
231 atproto_uri=atproto_uri,
232)
233```
234
235## security considerations
236
237### audio file access
238
239**current**: R2 URLs are public (anyone with URL can access)
240
241**acceptable for MVP** because:
242- music is meant to be shared
243- no sensitive content
244- URL guessing is impractical (content-based hashes)
245
246**future enhancement**: signed URLs with expiration
247
248### record ownership
249
250**enforced by ATProto**: only user with valid OAuth session can create records in their repo
251
252**enforced by backend**: tracks are associated with `artist_did` and only owner can delete
253
254### rate limiting
255
256**recommended**: limit uploads to prevent abuse
257- 10 uploads per hour per user
258- 100MB total per hour per user
259
260## cost estimates
261
262current monthly costs (~$15-20/month):
263- fly.io backend: $5-10/month (shared-cpu-1x, 256MB RAM)
264- fly.io transcoder: $5-10/month (shared-cpu-1x, 256MB RAM)
265- neon postgres: free tier (0.5GB storage, 3GB data transfer)
266- cloudflare R2: ~$0.16/month (6 buckets: audio-dev, audio-stg, audio-prod, images-dev, images-stg, images-prod)
267- cloudflare pages: free (frontend hosting)
268
269R2 storage scaling (audio + images):
270- 1,000 tracks: ~$0.16/month
271- 10,000 tracks: ~$1.58/month
272- 100,000 tracks: ~$15.81/month
273
274## references
275
276### ATProto documentation
277
278- [repository spec](https://atproto.com/specs/repository)
279- [lexicon spec](https://atproto.com/specs/lexicon)
280- [data model](https://atproto.com/specs/data-model)
281- [OAuth 2.1](https://atproto.com/specs/oauth)
282
283### cloudflare documentation
284
285- [R2 overview](https://developers.cloudflare.com/r2/)
286- [R2 pricing](https://developers.cloudflare.com/r2/pricing/)
287- [S3 compatibility](https://developers.cloudflare.com/r2/api/s3/)
288
289### plyr.fm project files
290
291- project instructions: `CLAUDE.md`
292- main readme: `README.md`
293- justfile: `justfile` (task runner)
294- backend: `src/backend/`
295- frontend: `frontend/`
296- transcoder: `transcoder/`
297
298## contributing
299
300when working on plyr.fm:
301
3021. **test empirically first** - run code and prove it works
3032. **reference existing docs** - check docs directory before researching
3043. **keep it simple** - MVP over perfection
3054. **use lowercase** - respect plyr.fm's aesthetic
3065. **no sprawl** - avoid creating multiple versions of files
3076. **document decisions** - update docs as you work
308
309## questions?
310
311if anything is unclear:
312- check the relevant phase document
313- review example projects in sandbox
314- consult ATProto official docs
315- look at your atproto fork implementation