# 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) ```kotlin @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 station - `sleep_timer_minutes`: default timer value - `enabled_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 by `station.id` or `prefix` --- ## Deep Links to Music Services Supported services: Яндекс Музыка, ВК Музыка, BOOM, Spotify, Apple Music, YouTube Music, Tidal, Deezer. URL template: `https:///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 1. **Radio Record API stability** — endpoints are unofficial/public; may change without notice. Mitigation: graceful degradation to cached data. 2. **Recording legality** — stream recording is for personal use only. Mitigation: clear disclaimer in app; no sharing functionality. 3. **Deep link URL changes** — music services may change search URL patterns. Mitigation: configurable URL templates in code; easy to update.