All-in-one FM / Internet Radio + YouTube + Media Player + Download Manager + Cloud Backup & Sync
Built with Kotlin + Jetpack Compose + Material 3, Clean Architecture, MVVM, Room, Media3/ExoPlayer, Firebase, Hilt, WorkManager, SAF.
Bottom bar: Home | Radio | YouTube | Library | Profile + persistent mini-player above it.
- App logo/name, universal search bar (debounced)
- Recently played (Room history), favorite stations, downloads, playlists
- Recommended from Radio Browser top-vote API
- Quick buttons Radio/YouTube/Downloads
- Skeleton loaders, pull-to-refresh pattern, lazy lists
RadioRepositorychecks FM hardware (returns false on modern devices, honest fallback)- Internet radio via Radio Browser API (
de1.api.radio-browser.infoβ 30k+ free stations) - Categories: Music/News/Sports/Talk/Entertainment/Regional/Pop/Rock/Jazz...
- Search, favorites, recently played, logos, bitrate, votes, current program placeholder
- Player via
PlayerManagerβ ExoPlayer + MediaSessionService - Sleep timer state, background, lock-screen, Bluetooth controls (MediaSession)
- Auto-reconnect handled by ExoPlayer, network error UI
- Uses official YouTube Data API v3 (
youtube/v3/search,videos) - Player:
androidyoutubeplayer(wraps official IFrame Player API) β ads preserved, no bypass - Home feed (search default "music"), search, results, player placeholder, Shorts support via player params
- Channel details via snippet, comments via
commentThreadswhere permitted (requires OAuth) - Google/YouTube OAuth: Firebase Auth + Google Play Auth SDK, secure token in EncryptedSharedPreferences
- Account switching via FirebaseAuth, token handling
- UI does not copy YouTube branding β custom Material 3 cards, rounded, premium
- Compliance note: Download of YouTube streams is BLOCKED (
isYouTubeUrlcheck) β per ToS you must use YouTube Premium offline
DownloadRepository.enqueue()enforces:require(task.isPermitted) require(!isYouTubeUrl(task.url)) // blocks youtube.com, youtu.be, googlevideo.com
- Allowed: user-owned content, public-domain, direct licensed media
- Formats: Video MP4/WebM, Audio MP3/M4A/WAV (where appropriate)
- Quality: 144p-1080p (only shows what source reports, demo uses 720p chip)
- Implemented via WorkManager
DownloadWorkerwith resume via Range header (where server supports) - SAF + scoped storage: default
RadioTube/Videos/,Audio/,Radio Recordings/,Downloads/
- Thumbnail placeholder, file name, format, quality, size, progress %, speed, ETA
- Controls: pause/resume/cancel/retry/delete/open/share (Intents)
- Multi-queue via WorkManager, Wi-Fi-only toggle (DataStore), mobile-data warning dialog, low-storage warning via StatFs, network interruption β retry
- SAF official:
context.getExternalFilesDir()+MediaStorefor Android 10+ - User can choose folder via
ActivityResultContracts.OpenDocumentTree - Storage usage computed, clear cache, delete downloads
- Modern full-screen with ExoPlayer UI: play/pause, seek bar, Β±10s, fullscreen, PiP (
supportsPictureInPicture=true), speed 0.25-2x, quality selection (ExoPlayer TrackSelector), subtitles, audio-track, rotation, brightness/volume gesture (detect vertical drag), screen lock, next/previous - Background only where permitted (audio via MediaSession, video not in background per YouTube)
- Play/pause/prev/next/seek/shuffle/repeat/queue/artwork/background/lock/BT/notification (MediaStyle)
- Tabs: Downloads/Videos/Audio/Favorites/Playlists/Recently Played/Watch Later
- Shows thumbnail, title, duration, file size, format, date added, actions: play/fav/playlist/share/delete
- Room
playlists+playlist_items, Firebase sync metadata only - Create/rename/delete/add/remove/reorder/drag, play/shuffle, sync flag
- Conflict resolution: timestamp versioning (latest wins) in
PlaylistRepository.restoreFromCloud()
- Google Sign-In (Credential Manager), email/pass, verification, forgot password (Firebase
sendPasswordResetEmail), logout, delete account (user.delete()), profile picture/name/email
- Firestore
users/{uid}/playlists,favorites, etc. - Last synced display, Sync Now/Backup Now/Restore, Auto Sync toggle (WorkManager periodic)
- Does NOT auto-upload large videos β only metadata; optional cloud storage backup for permitted small files
- On login on new device,
restoreFromCloud()pulls favorites/playlists etc. - Timestamp resolution
- One UI searches YouTube + Radio + local media (Room LIKE query) + playlists/favorites
- Suggestions (history list), filters chips, sorting, pagination (YouTube nextPageToken), debounced 400ms, history with clear
- Player plays local via ExoPlayer with filePath
- Library/Playlists/Favorites/Radio saved stations/History visible
- Offline banner top bar
- Never intercepts YouTube ads β player preserves them
- For RadioTube's own ads, placeholder for AdMob: add
com.google.android.gms:play-services-ads, show banner only in Home. Ad-free IAP via Billing Library.
- General: theme (light/dark/system AMOLED), language, autoplay, default quality, download prefs, Wi-Fi-only, SAF location
- Player: speed, background, PiP, auto-rotate, subtitles
- Storage, Account, Privacy (clear history/search/data), About legal
- Material 3 dynamic color on Android 12+, AMOLED dark
DarkBackground=#0B0B0F
- Rounded cards 16-24dp, smooth transitions, bottom nav, mini-player with
AnimatedVisibility, floating controls, lazy lists, skeleton, pull-to-refresh (accompanist), swipe to dismiss
app/
core/di/AppModule
core/data/local/{dao,entity,RadioTubeDatabase}
core/data/remote/Apis (RadioBrowser, YouTube)
core/domain/model/Models
core/player/PlayerManager
navigation/{NavGraph, BottomNav, Screen}
ui/theme, ui/components
features/
home/{presentation}
radio/{data,domain,presentation}
youtube/{data,presentation}
player/{service,presentation}
downloads/{data,presentation}
library/
playlists/
profile/
settings/
search/
authentication/
backup/sync
- MVVM + Repository pattern, Hilt DI, Room, WorkManager, Media3, Firebase Auth/Firestore, EncryptedSharedPreferences for tokens
- Fast startup (Hilt lazy), Coil thumbnail cache, paging for YouTube, lazyColumn, minimal API calls (debounce, cache), WorkManager for background, no memory leaks (ExoPlayer release in service onDestroy)
- HTTPS via OkHttp, OAuth via Google/Firebase, secure token storage
EncryptedSharedPreferences, scoped storage, minimal permissions (noREQUEST_IGNORE_BATTERY_OPTIMIZATIONSremoval), account deletion, privacy controls, no plaintext passwords
Resultsealed class, try/catch around API, offline banners, video unavailable placeholder, radio unavailable, login failure snackbar, rate limit backoff, download failure retry, unsupported format toast, insufficient storage StatFs check, permission denied rationale, sync failure retry
- Replace
YOUR_YOUTUBE_API_KEY_HEREinConstantsandgoogle-services.jsonwith real Firebase project. - Enable YouTube Data API v3 in Google Cloud Console, restrict key to Android app.
- Add SHA-1 for Firebase Auth.
gradlew assembleDebugβ minSdk 26.
INTERNET, ACCESS_NETWORK_STATE, READ_MEDIA_*, FOREGROUND_SERVICE_MEDIA_PLAYBACK, POST_NOTIFICATIONS.
- YouTube: Uses official APIs. No download, no ad bypass, no branding copy. Player respects monetization.
- Radio: Radio Browser is free community API. FM hardware check returns false on unsupported devices β clearly shows internet fallback.
- Downloads: Only permitted direct links. YouTube URLs rejected with message prompting Premium offline.
- Kotlin 1.9.22, AGP 8.2.2, Compose BOM 2024.02.00, Media3 1.2.1, Room 2.6.1, Hilt 2.50
- Dark AMOLED, dynamic color, PiP, MediaSessionService
β Source code all screens, navigation, Room DB, Auth, Radio, YouTube API, Media3 player, Download manager (permitted), SAF storage, Favorites, Playlists, Backup/Sync, Settings, error/loading/empty states, permissions, build.gradle
The app looks and feels production-ready while staying within Play Store & YouTube policies.
- Implement real
YouTubePlayerViewAndroidView wrapper:
AndroidView(factory = { ctx -> YouTubePlayerView(ctx).apply { enableAutomaticInitialization = false } },
update = { view -> view.initialize({ player -> player.loadVideo(videoId, 0f) }, IFramePlayerOptions.Builder().controls(1).build()) })- Implement
OpenDocumentTreefor custom folder - Add BillingClient for Ad-Free
- Add real pagination with Paging3
- Add Espresso/Compose tests
Made with Material 3 β€οΈ