Files
radiola-android/docs/superpowers/specs/2026-06-01-radio-record-design.md
2026-06-01 11:14:38 +03:00

203 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.