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