Skip to content

Repository files navigation

Muzix

Muzix

ExpoTypeScriptFastAPIPostgreSQLCloudflareZustandSentryNumPySQLiteArgon2

Music streaming app with real-time play tracking, lyrics sharing, and mood-based home dashboard.

Screenshots

SearchLibraryProfileSign In
SearchLibraryProfileSign In
Mini PlayerLyricsShare Link
Mini PlayerLyricsShare Link
HomePlayer
HomePlayer

Demo

Muzix demo

Muzix demo

Architecture

Backend

backend/
├── config.py # Environment variables, constants, R2 client
├── middleware.py # CORS + security headers + X-Request-ID (raw ASGI)
├── helpers.py # Response format, rate limiting, caching, validation, serialization, auth
├── crypto.py # Password hashing: argon2id (new) + bcrypt (legacy)
├── main.py # App bootstrap, exception handlers, router includes (local routes gated in prod)
├── db.py # Async SQLAlchemy engine + session factory
├── models.py # SQLAlchemy ORM models
├── schemas.py # Pydantic request/response models + OpenAPI error schemas
├── migrate.py # Idempotent SQL migration (no Alembic)
├── backfill_genre.py # Song genre enrichment via MusicBrainz + Wikipedia
├── import_songs.py # Catalog import: Genius lyrics + YouTube download + MusicBrainz genre
├── repositories/ # Database operations per entity
├── services/ # Business logic per entity
└── routes/ # API endpoints per entity

Layer flow:routes/services/repositories/db.py + models.py

Frontend

web/muzix/
├── app/ # Expo Router file-based routes
│ ├── (tabs)/ # Main tab screens (home, search, library, profile)
│ ├── share/ # Public share link resolution
│ ├── _layout.tsx # Root layout: auth guard, offline banner, queue panel, keyboard shortcuts, Sentry
│ └── login.tsx, register.tsx
├── components/ # Reusable UI components
│ ├── NowPlaying.tsx # Full player view with lyrics sharing
│ ├── MiniPlayer.tsx # Persistent mini player bar
│ ├── QueuePanel.tsx # Queue management modal (reorder, remove, clear)
│ ├── LyricsPanel.tsx, LyricsImageGenerator.tsx
│ ├── ErrorBoundary.tsx # Sentry-wrapped error boundary
│ └── EmptyStates.tsx, Skeleton.tsx
├── hooks/ # Custom hooks
│ ├── useSharing.ts # Unified content sharing (API + native/web share)
│ ├── useKeyboardShortcuts.ts # Web keyboard controls (Space, arrows, N/P, L, Q, Esc)
│ ├── useHaptics.ts # Haptic feedback (native only, web no-op)
│ ├── useLyricsSharing.ts # Lyrics image generation via view-shot
│ └── useConnectivity.ts # Online/offline detection
├── store/ # Zustand state
│ ├── playerStore.ts # Player, queue, likes state with zustand persist
│ ├── authStore.ts # Auth token + user state
│ └── storage.ts # Cross-platform storage adapter (MMKV native / localStorage web)
├── services/
│ ├── api.ts # API client with retry, timeout, dedup, Sentry error reporting
│ ├── cache.ts # ETag-based API response cache (in-memory + localStorage)
│ ├── metrics.ts # Sentry metrics (API latency, queue depth, track plays)
│ ├── playTimeTracker.ts # Persistent play time accumulator with delta-flush
│ ├── offlineQueue.ts # Offline request queue with retry
│ └── auth.ts # Auth API helpers
└── lib/ # Colors, spacing, utilities, responsive breakpoints

API Flow

Example of a single API visualized — one endpoint's request → response path.

Single API flow example

Generate and view the interactive API flow from backend/:

npx api-understanding scan # → writes analysis.json
npx api-understanding dashboard analysis.json # → interactive dashboard

Algorithms

See algorithms/ for documented runtime logic: ALS recommendation engine, Fisher-Yates shuffle, play-time tracking, analytics scoring, caching strategies, and more — 16 algorithms with actual code references, constants, and input/output specs.

What's newest: the ALS model retraining now runs in the app lifespan as a background task (fit once on startup; subsequent requests serve the cached factor matrices).

Features

  • Content sharing: Generate share links for songs, albums, artists, playlists, lyrics. 30-day token expiry. Web Share API / native share sheet / clipboard fallback.
  • Lyrics sharing: Select up to 5 lyrics lines, share as image (16:9 PNG) or plain text. Synced scrolling with LRC support.
  • Home screen dashboard: 2x2 smart grid with current time, live weather (geolocation + wttr.in), mood derived from recently played song genres.
  • Mood detection: Analyzes genre of your recent plays and displays a mood label + icon (Energetic, Calm, Confident, etc.).
  • Genre enrichment: Song genre metadata fetched from MusicBrainz + Wikipedia, stored per-track in the database.
  • MMKV storage: ~30x faster than AsyncStorage on native. Zustand persist + offline queue + play time tracker all use MMKV. Web falls back to localStorage.
  • Audio playback: Uses expo-audio for cross-platform playback; downloads audio to cache via expo-file-system. Falls back gracefully when native TrackPlayer module is unavailable.
  • Play time tracking: Persistent per-song accumulator flushes deltas to POST /telemetry/duration every 30s. Survives app backgrounding and restarts.
  • Queue management: Slide-up panel with reorder (up/down arrows), remove, clear all.
  • Keyboard shortcuts (web): Space=play/pause, arrows=next/prev, N/P=next/prev, L=like, Q=queue, Esc=close.
  • Haptic feedback: Light/medium/success/error on native (no-op on web).
  • Offline banner: Persistent top banner when disconnected.
  • Pull-to-refresh: All detail screens (album, artist, playlist, profile).
  • Responsive layout: Desktop sidebar, tablet split-view, mobile bottom tabs. Orientation-aware.

Monitoring

  • Sentry: Error reporting + performance tracing (20% sample in production). Sentry.wrap() on root layout, ErrorBoundary catches component crashes, API layer reports errors with request IDs.
  • Sentry Metrics:api_response_time, api_error, queue_depth, track_play tracked via services/metrics.ts.
  • Logfire: Request-level tracing with logfire.instrument_fastapi(). Trace context stripped by SecurityMiddleware to prevent cross-service contamination.
  • X-Request-ID: Every response includes a UUID. Accepts client-sent IDs for end-to-end correlation. Set as Sentry tag for debugging.
  • Structured errors:ApiError class with ErrorKind, ErrorSeverity, and retryable fields. Auth errors auto-redirect to login.

Security

  • Password hashing: argon2id (new registrations) with bcrypt fallback (existing users)
  • JWT signing: HS384 (SHA-384 HMAC) with 24-hour expiry + 30-day refresh tokens
  • CORS: Configurable allowed origins via CORS_ORIGINS env var
  • Rate limiting: Per-IP + per-path sliding window; 10 shares/min per user
  • Security headers: X-Content-Type-Options, X-Frame-Options, HSTS, CSP, Referrer-Policy, Permissions-Policy
  • Input validation: Pydantic models with email/password complexity rules
  • IDOR protection: Playlist ownership checks on all mutation endpoints
  • OpenAPI docs:/docs (Swagger UI), /redoc (ReDoc), and /openapi.json are enabled with a fully typed spec — Pydantic request/response schemas, bearer-auth security scheme, and per-endpoint error responses for every route.
  • Path traversal blocked:/thumbnails/{filename} rejects /, .., \, null bytes
  • Telemetry capped:POST /telemetry/events limited to 50 events per request
  • Bearer auth documented: JWT-protected endpoints are declared with a bearerAuth security scheme in the generated OpenAPI spec
  • Dev routes gated:/local/* routes and static file mounts only available when ENV != production

Quick Start

Backend

cd backend
cp .env.example .env # fill in values
uv sync
uv run python migrate.py # create tables
uv run uvicorn main:app --reload --host 0.0.0.0 --port 8000

Frontend

cd web/muzix
cp .env.example .env.local # set EXPO_PUBLIC_API_URL
pnpm install
pnpm dev

Environment Variables

Backend (backend/.env)

VarPurpose
DATABASE_URLPostgreSQL connection string (asyncpg)
JWT_SECRETSecret key for JWT token signing (min 32 characters)
R2_ACCOUNT_IDCloudflare account ID
R2_ACCESS_KEY_IDR2 API token access key
R2_SECRET_ACCESS_KEYR2 API token secret
R2_BUCKETPrivate bucket name (e.g. muzix-audio)
R2_PUBLIC_URLOptional custom S3 endpoint
CORS_ORIGINSComma-separated frontend origins
ENVSet to production to gate dev-only routes
LOGFIRE_TOKENPydemon Logfire token for tracing
UPSTASH_REDIS_REST_URL / UPSTASH_REDIS_REST_TOKENUpstash Redis credentials for distributed rate limiting and catalog cache (optional; falls back to in-memory)

Frontend (web/muzix/.env.local)

VarPurpose
EXPO_PUBLIC_API_URLBase URL of the FastAPI backend

Database

The migration (uv run python migrate.py) creates:

TableDescription
songsTrack metadata with genre, full-text search (tsvector)
albumsAlbum metadata with FTS
artistsArtist metadata with FTS
playlistsUser playlists (with M2M playlist_songs)
usersAuth accounts (email + argon2id hash)
refresh_tokensJWT refresh tokens with rotation + revocation tracking (is_revoked BOOLEAN)
listening_eventsPer-play telemetry
user_sessionsSession engagement metrics
song_durationsPersistent per-user listening time accumulator (unique on user_id + song_id)
user_likesUser song likes (unique constraint)
sharesShare links with 30-day expiry, per-content metadata

API

All endpoints return standardized JSON:

{
"status": "success"| "failed" | "exception","data": {},
"message": "...",
"meta": { "pagination": { "total": 65, "limit": 100, ... } }
}
MethodPathAuthDescription
GET/healthNoHealth check
GET/songsNoList songs (paginated, brief, includes genre)
GET/songs/{id}NoGet song by ID (full, includes genre)
GET/albumsNoList albums
GET/albums/{id}NoGet album by ID
GET/artistsNoList artists
GET/artists/{id}NoGet artist by ID
GET/playlistsYesList user playlists
POST/playlistsYesCreate playlist
PUT/playlists/{id}YesUpdate playlist
DELETE/playlists/{id}YesDelete playlist
POST/playlists/{id}/songs/{songId}YesAdd song to playlist
DELETE/playlists/{id}/songs/{songId}YesRemove song from playlist
GET/likesYesGet user's liked songs
POST/likes/{songId}YesLike a song
DELETE/likes/{songId}YesUnlike a song
GET/search?q=NoFull-text search (songs, albums, artists)
GET/stream/{id}NoGet 1-hour presigned R2 URL
GET/thumbnails/{id}.jpgNoGet song/album thumbnail
POST/auth/registerNoCreate account
POST/auth/loginNoGet JWT + refresh token
POST/auth/refreshNoRefresh JWT token
GET/auth/meYesCurrent user profile
POST/telemetry/eventsYesBatch insert listening events (max 50)
POST/telemetry/durationYesRecord accumulated play time for a song
POST/telemetry/session/startYesStart session
POST/telemetry/session/endYesEnd session
POST/api/share/generateYesGenerate share link (10/min)
GET/api/share/{token}NoResolve share link (public)
GET/analytics/user/top-songsYesUser's top songs
GET/analytics/user/statsYesUser listening stats
GET/analytics/user/recent-activityYesRecent listening activity

Performance

  • Async everything: All database and R2 operations are async (boto3 calls wrapped in asyncio.to_thread)
  • ETag caching: List endpoints return ETag + Cache-Control headers; 304 on If-None-Match
  • Distributed Redis cache: When Upstash credentials are set, rate limiting and catalog caching are distributed via Redis with automatic stale-key cleanup (falls back to in-memory)
  • MMKV storage: ~30x faster than AsyncStorage on native for key-value operations
  • Rate limiting: Sliding window per IP + path with automatic stale key cleanup
  • Brief serialization: List responses omit lyrics and r2_object_key (~3KB/song savings)
  • Local file caching: 60s TTL cache for local asset reads
  • FTS indexes: GIN-indexed tsvector columns on songs, albums, artists
  • Request deduplication: In-flight Map prevents duplicate concurrent requests to the same endpoint

Deployment

Backend is deployed to FastAPI Cloud:

cd backend
uv run fastapi cloud deploy

Designed and developed by Akshat Kotpalliwar
Copyright © 2026 Akshat Kotpalliwar. All rights reserved. Licensed under the AGPL-3.0.

About

Cross-platform music streaming app with real-time play tracking, lyrics sharing, and mood-based home dashboard. Built with FastAPI, PostgreSQL, Cloudflare R2, Expo (React Native), Sentry monitoring, and Logfire tracing. Features queue management, keyboard shortcuts, haptic feedback, and offline support.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages