Add radiOLA design spec

This commit is contained in:
nk
2026-06-01 11:14:38 +03:00
commit 4700335f11

View File

@@ -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://<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
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.