Your private, offline music player with persistent analytics — data stored directly in your music folder as rhythmix_data.json.
The web app used localStorage — wiped on reinstall.
Capacitor Filesystem only writes to app-private dirs — also wiped on reinstall.
This app uses Android's Storage Access Framework (SAF) with persistable URI permissions:
User picks /sdcard/Music/
↓
Android grants read+write permission to that URI
↓
We call takePersistableUriPermission()
↓
Permission is stored in Android's permission table — SURVIVES reinstall
↓
We write rhythmix_data.json INTO /sdcard/Music/
↓
Reinstall app → user picks same folder → rhythmix_data.json found → data restored ✓
This is the only Play Store-compliant way to write to arbitrary user folders on Android 10+.
app/src/main/java/com/rhythmix/app/
├── model/
│ ├── Song.kt — Audio track data
│ └── AnalyticsData.kt — PlayEvent, AnalyticsData, SongStats
├── storage/
│ └── StorageManager.kt — SAF folder picker, file R/W, song scanning
├── player/
│ ├── PlaybackService.kt — Media3 foreground service
│ └── PlayerController.kt — ExoPlayer wrapper, analytics recording
└── ui/
├── MainActivity.kt — Hosts fragments, SAF launcher
├── MainViewModel.kt — Central state: songs, analytics, navigation
├── OnboardingFragment.kt — Folder picker screen
├── ScanningFragment.kt — Progress screen
├── HomeFragment.kt — Dashboard
├── LibraryFragment.kt — Full song list + search
├── AnalyticsFragment.kt — Stats dashboard
├── PlayerFragment.kt — Full-screen player
├── SettingsFragment.kt — Folder & data management
└── adapter/
├── SongAdapter.kt
└── StatsAdapter.kt
- Android Studio Hedgehog (2023.1.1) or newer
- JDK 17 (bundled with Android Studio)
- Android SDK API 34
-
Open in Android Studio
File → Open → select the RhythmixAndroid folder -
Sync Gradle Android Studio will prompt to sync. Click "Sync Now".
-
Build
Build → Make Project (Ctrl+F9) -
Run
- Connect an Android phone (USB debugging on) or use the emulator
- Click the green ▶ Run button
- Android 8.0 (API 26) or higher
- ~50 MB storage (app itself is ~10 MB; Media3 adds ~30 MB)
- App opens → Onboarding screen
- Tap "Choose Music Folder"
- Android's built-in folder picker opens
- Navigate to your MP3 folder and tap "Use this folder"
- App scans for audio files (shows progress ring)
- Home screen loads with your library
rhythmix_data.json is created automatically in your music folder.
- Uninstall app
- Install app again
- Tap "Choose Music Folder"
- Pick the same folder as before
- App finds
rhythmix_data.jsonand loads all your history instantly ✓
No sync. No cloud. No internet. Everything lives in your music folder.
rhythmix_data.json schema is identical to the web app — fully portable:
{
"schemaVersion": 1,
"lastSyncAt": 1718000000000,
"playEvents": [
{
"songId": "a1b2c3d4",
"startedAt": 1718000000000,
"listenedMs": 185000,
"completed": true
}
]
}songId: stable hash offilename + filesize— same algorithm as the web app- Up to 5,000 events stored (older ones trimmed automatically)
| Library | Purpose |
|---|---|
androidx.media3:media3-exoplayer |
Audio playback engine |
androidx.media3:media3-session |
Foreground playback service + media notification |
androidx.documentfile:documentfile |
SAF tree URI helper |
com.google.code.gson:gson |
JSON serialisation for analytics |
androidx.lifecycle:lifecycle-viewmodel-ktx |
ViewModel + LiveData |
The current version parses metadata from filenames (Artist - Title.mp3).
To read actual ID3 tags, add JAudioTagger or use MediaMetadataRetriever with additional keys (METADATA_KEY_ARTIST, METADATA_KEY_TITLE, etc.) — already imported in StorageManager.kt.
Use MediaMetadataRetriever.getEmbeddedPicture() to extract cover art bytes from the audio file, then load with Glide:
val art = mmr.embeddedPicture
Glide.with(context).load(art).into(imageView)Store playlists as additional fields in AnalyticsData — they'll be persisted to the same JSON file automatically.
- No internet permission in the manifest
- No analytics SDKs
- No crash reporting
- All data stays on device, in your music folder
- The app cannot access any files outside the folder you explicitly chose