6.9 KiB
radiOLA — Design Spec
Date: 2026-06-01 Author: brainstorming skill Status: Approved
Overview
Android-приложение для прослушивания онлайн-радио Radio Record (кодовое название проекта: radiOLA) с фокусом на:
- Прослушивание станций с фильтрацией и поиском
- Отображение текущего трека (now playing)
- Deep links в музыкальные сервисы для быстрого поиска трека
Tech Stack
- Language: Kotlin
- UI: Jetpack Compose + Material 3
- Navigation: Jetpack Navigation Compose
- DI: Hilt
- Network: Retrofit + Kotlinx Serialization
- Database: Room
- Preferences: DataStore
- Player: ExoPlayer (Media3)
- Background Service: MediaSessionService
- Images: Coil
- Testing: JUnit 5 + MockK + Turbine + Compose UI Test
Architecture
Clean Architecture + Domain Layer (feature-based packages inside single Gradle module).
📱 ui/
├── player/ # PlayerScreen, PlayerViewModel
├── stations/ # StationsScreen, StationsViewModel
├── favorites/ # FavoritesScreen
├── history/ # HistoryScreen
└── settings/ # SettingsScreen
🧠 domain/
├── model/ # Station, Track, PlayerState, DeeplinkService
├── repository/ # Interfaces (StationRepository, NowPlayingRepository, etc.)
└── usecase/ # PlayStation, GetNowPlaying, SearchTrackInService, etc.
💾 data/
├── remote/ # Retrofit API, DTOs, mappers
├── local/ # Room Entities, DAOs
└── repository/ # Repository implementations
🎵 service/ # MediaSessionService (ExoPlayer + MediaSession)
🔗 deeplink/ # URL builders for music services
📱 widget/ # AppWidgetProvider
Screens & Navigation
Bottom Navigation (4 tabs) + Player BottomSheet.
| Tab | Screen | Content |
|---|---|---|
| 🎧 Радио | StationsScreen |
Grid of stations, tag chips (filter), search bar |
| ⭐ Избранное | FavoritesScreen |
Favorite stations grid, quick play |
| 🕐 История | HistoryScreen |
List of last 200 tracks with timestamps, search, swipe actions |
| ⚙️ Настройки | SettingsScreen |
Sleep timer, equalizer, recording toggle |
Player:
- Collapsed: mini-bar at bottom (cover 48dp, station name, play/pause)
- Expanded (tap): fullscreen with large cover, track info, deep link buttons row
Data Sources
Remote API (Radio Record)
| Endpoint | Description |
|---|---|
GET https://www.radiorecord.ru/api/stations/ |
List of stations with metadata (id, name, prefix, genre, tags, covers) |
GET https://air.radiorecord.ru/api/stations/now/ |
Current tracks for all stations (artist, song, iTunes covers) |
Stream URL format: https://air.radiorecord.ru:805/{prefix}_128
Cache policy: stations list cached 1 hour; now playing polled every 10 seconds while player active.
Local (Room)
@Entity
class StationEntity(
val id: Int,
val name: String,
val prefix: String,
val streamUrl: String,
val coverUrl: String,
val sortOrder: Int
)
@Entity
class TrackHistoryEntity(
val id: Int = 0,
val artist: String,
val song: String,
val stationName: String,
val coverUrl: String?,
val timestamp: Long
)
Track history limit: 200 records, FIFO cleanup.
Preferences (DataStore)
last_station_id: last played stationsleep_timer_minutes: default timer valueenabled_deeplink_services: set of visible service IDs
Player & Background Playback
- ExoPlayer inside
MediaSessionService(Media3) - MediaSession for system integration: headphone buttons, Bluetooth, lock screen
- Notification: MediaStyle with prev/pause/next buttons, cover art, now playing text
- Audio Focus: auto-pause on call, duck on notifications
- Now Playing Polling:
GET /api/stations/now/every 10s, match bystation.idorprefix
Deep Links to Music Services
Supported services: Яндекс Музыка, ВК Музыка, BOOM, Spotify, Apple Music, YouTube Music, Tidal, Deezer.
URL template: https://<service>/search?q={artist}+{track} (URLEncoded).
Opening: Intent(ACTION_VIEW, uri) — system handles app vs browser fallback.
UI:
- Player: horizontal row of service icons below cover art
- History: swipe left → "Find in..." → BottomSheet with service list
- Settings: toggle visibility per service
Additional Features
| Feature | Implementation |
|---|---|
| Favorites | Room + ⭐ toggle in player/station card + drag-sort |
| History | Auto-log on track change (deduplicate consecutive duplicates) + search + swipe-to-delete |
| Sleep Timer | Handler postDelayed / WorkManager; stop service at timeout |
| Equalizer | Android Equalizer AudioEffect; 5 bands + presets (Rock, Pop, Jazz, Flat, Bass); DataStore persistence |
| Stream Recording | MediaMuxer on ExoPlayer stream; save to Music/RadioRecord/ via MediaStore; ⚠️ personal use only |
| Widget | 4x1 AppWidgetProvider; cover + station name + play/pause; updates on track change |
Error Handling
| Scenario | Behavior |
|---|---|
| No internet | Snackbar "Offline mode"; show cached stations from Room; disable play |
| API down | Exponential backoff retry (1s→2s→4s→8s, max 5); fallback to cache |
| Stream fails | ExoPlayer retry x3; then suggest switching station |
| Recording unavailable | Request WRITE_EXTERNAL_STORAGE; hide button if denied |
| Deep link fails | Toast "Opening in browser" + force chooser |
| Bluetooth disconnect | Auto-pause |
| Incoming call | Auto-pause; resume after hangup |
Testing Strategy
| Type | Scope | Tools |
|---|---|---|
| Unit | UseCases (filtering, deeplink URL building) | JUnit 5 + MockK |
| Unit | Repositories (cache merge, local/remote) | Room in-memory + MockWebServer |
| UI | Compose screens (search, filters, navigation) | Compose UI Test + Hilt Test |
| Integration | Player (play/pause, station switch) | Espresso + IdlingResource |
| Manual | Deep links on real device with all services | — |
Out of Scope (Future)
- Android Auto / Automotive OS
- Crossfade between stations
- Multiple stream qualities (64/128/320)
- Social sharing (send track to friend)
- Lyrics display
Risks & Assumptions
- Radio Record API stability — endpoints are unofficial/public; may change without notice. Mitigation: graceful degradation to cached data.
- Recording legality — stream recording is for personal use only. Mitigation: clear disclaimer in app; no sharing functionality.
- Deep link URL changes — music services may change search URL patterns. Mitigation: configurable URL templates in code; easy to update.