Offtunes is a cross-platform music acquisition and playback application built with Flutter, featuring a retro-hardware-inspired UI. Import playlists from Spotify or YouTube Music, resolve each track to a YouTube audio stream, download the audio locally, automatically tag it with metadata/cover art, and play it back β all offline-first.
| Library (Songs) | Library (Playlists) | Search Screen |
|---|---|---|
![]() |
![]() |
![]() |
| Import Playlist | Settings & Configuration |
|---|---|
![]() |
![]() |
The interface is inspired by vintage, high-end audio equipment:
- Warm Color Palette: Rich browns (
#1A0E0A), deep crimson/velvet (#8B2252), and warm cream (#F5E6D3). - LCD Display Widgets: Using pixel-perfect monospace typography (
VT323andSpace Mono) to simulate retro dot-matrix hardware screens. - Tactile Elements: High-fidelity embossed panels, physical-style pressable buttons, and custom hardware sliders.
- Micro-interactions: Interactive LED indicators, haptic feedback on press, and realistic click sounds.
- Playlist Portability: Import Spotify or YouTube Music playlist/album URLs without requiring API keys.
- Smart Query Matching: Features an advanced scoring algorithm (up to 115 points based on title similarity, duration match, artist matching, and video type) to find the absolute best quality audio stream on YouTube automatically.
- Platform-Aware Download Pipeline:
- Android: Executes
yt-dlpnatively in a background thread via Chaquopy (embedded Python 3.14). - Windows: Directly spawns and controls a background
yt-dlp.exeprocess with stdout progress parsing.
- Android: Executes
- Instant Audio Tagging: Uses high-performance FFmpeg stream copying to embed metadata tags (Title, Artist, Album, Track number) and high-res cover art into
.m4afiles instantly without re-encoding. - Advanced Playback Engine: Gapless playback, dynamic queue management, and background audio session control via
just_audio. - Completely Offline-First: Scan, manage, and play your local music catalog anytime.
The application follows a clean service-oriented architecture:
graph TD
subgraph Flutter UI
A[MainScreen] --> B[HomeScreen]
A --> C[SearchScreen]
A --> D[ImportScreen]
A --> E[SettingsScreen]
B --> F[PlaylistDetailScreen]
B --> G[NowPlayingScreen]
end
subgraph State Management [Riverpod / Provider]
H[DownloadProvider] --> I[LibraryProvider]
end
subgraph Downloader Services
J[DownloaderService] --> K{Platform?}
K -->|Android| L[MethodChannel β Chaquopy Python 3.14 β yt-dlp]
K -->|Windows| M[Process β yt-dlp.exe]
N[FfmpegService] --> O[ffmpeg_kit_flutter / ffmpeg.exe]
end
-
Flutter SDK
$\ge$ 3.11.5 - Java SDK 17 (for Android builds)
- Android SDK & NDK 28.x
-
Python 3.10+ (installed on the build machine for Chaquopy
.pyccompilation)
Because large binary executables exceed GitHub's file size limits, ffmpeg.exe and yt-dlp.exe are excluded from the repository and must be added manually before compiling:
- Create the directory:
mkdir assets/binaries - Add
yt-dlp.exe:- Download from the latest yt-dlp release.
- Place it inside
assets/binaries/yt-dlp.exe.
- Add
ffmpeg.exe:- Download a Windows build (Essentials version is recommended) from gyan.dev.
- Extract and place the
ffmpeg.exebinary insideassets/binaries/ffmpeg.exe.
- Run the application:
flutter run -d windows
Chaquopy automatically handles downloading Python and installing the yt-dlp library during the Gradle build step.
- Simply launch the application:
(Note: The first compilation compiles Python modules and may take 5-10 minutes).
flutter run -d <your-android-device>
| Dependency | Purpose |
|---|---|
just_audio |
High-fidelity audio playback engine with gapless streaming |
flutter_riverpod |
Clean, compile-safe application state management |
ffmpeg_kit_flutter_new_audio |
Native FFmpeg wrapper for Android audio processing |
google_fonts |
Typography engine for vintage dot-matrix LCD displays |
audio_metadata_reader |
Read ID3 tags and metadata from saved tracks |
permission_handler |
Manage dynamic runtime media storage access permissions |
This project is licensed under the MIT License - see the LICENSE file for details.




