A cross-platform mobile music player app built with Flutter, streaming and downloading YouTube audio/video ad-free with offline playback support.
Project Overview
Muon is a minimalist, ad-free mobile music player for iOS and Android that streams and downloads YouTube content. Users can search YouTube, play audio in the background with lock-screen controls, download audio (m4a) or full video (H.264/mp4), and manage offline playlists. The FastAPI backend handles download tasks asynchronously via a task queue with status polling, serves static media files, and runs a TTL-based disk cleanup service.
Technical Challenges & Solutions
Background Audio on iOS & Android
Maintaining uninterrupted audio playback when the app is backgrounded or the screen is locked on both platforms.
Solution: Integrated audio_service to register as a background audio service, implementing the required platform callbacks for play/pause/skip. Combined with just_audio for actual playback, this gives native lock-screen controls and Bluetooth headset support.
Async Download Task System
YouTube downloads via yt-dlp can take 10–60+ seconds. A synchronous API would time out and leave the client hanging.
Solution: Implemented an async task queue on the FastAPI backend: the POST /api/download endpoint immediately returns a task_id, while the Flutter client polls GET /api/status/{task_id} until the status is 'completed', then fetches the media URL.
Offline Storage & Playlist Management
Managing downloaded tracks and user playlists reliably in a local SQLite database on the device.
Solution: Used Drift (type-safe SQLite ORM for Flutter) with code generation to define strongly-typed tables for tracks and playlists, ensuring safe migrations and compile-time query correctness.
Architecture
Flutter frontend uses Riverpod (code-gen) for state management, just_audio + audio_service for background playback, Drift (SQLite) for local offline storage, and Dio for HTTP. FastAPI backend wraps yt-dlp + ffmpeg in an async task system, exposes a /api/status/{task_id} polling endpoint, and serves downloaded files via StaticFiles. Deployed via Docker Compose.
Learnings
Building Muon gave me deep experience with Flutter's audio ecosystem (just_audio, audio_service) and the complexity of background media sessions across iOS and Android. Designing an async task queue on FastAPI with status polling was a valuable pattern for handling long-running jobs without blocking the API response cycle.