commit 4700335f11a99f226be8e3541932d694d83d24ec Author: nk Date: Mon Jun 1 11:14:38 2026 +0300 Add radiOLA design spec diff --git a/docs/superpowers/specs/2026-06-01-radio-record-design.md b/docs/superpowers/specs/2026-06-01-radio-record-design.md new file mode 100644 index 0000000..ead273d --- /dev/null +++ b/docs/superpowers/specs/2026-06-01-radio-record-design.md @@ -0,0 +1,202 @@ +# 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.