Files
radiola-android/docs/superpowers/memory.md

103 lines
5.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 — Project Memory
## Overview
Android online radio app combining **Radio Record** (Russian dance radio network) with **Radio Browser API** (`radio-browser.info`) for genre filtering across thousands of internet radio stations.
**Stack:** Kotlin 2.0, Compose BOM 2024.06, Material3, Hilt 2.51.1, Media3 ExoPlayer 1.3.1, Room 2.6.1, Retrofit + Kotlinx Serialization, Coil 2.6.0, DataStore, Lucide icons.
---
## Architecture
Clean Architecture with these layers:
- `domain/` — models (`Station`, `Track`, `DeeplinkService`), repository interfaces, use cases
- `data/remote/` — Retrofit APIs (`RecordApi`, `RadioBrowserApi`), DTOs, mappers
- `data/local/` — Room (`AppDatabase` v2), DAOs (`StationDao`, `TagDao`, `TrackHistoryDao`)
- `data/repository/` — impls (`StationRepositoryImpl`, `RadioBrowserRepositoryImpl`, etc.)
- `di/` — Hilt `AppModule` (two Retrofit instances: Record + Radio Browser)
- `ui/` — Compose screens (`StationsScreen`, `HistoryScreen`, `SettingsScreen`, `PlayerBottomSheet`)
- `service/``PlayerController` (ExoPlayer singleton), `PlayerService` (`MediaSessionService`)
- `widget/``PlayerWidgetProvider` (4×1 home screen widget)
---
## Current State
### ✅ Working
- **Radio Record stations** load from `https://www.radiorecord.ru/api/stations/` → cached in Room
- **Stations grid** (`LazyVerticalGrid`, 2 columns) with cover, name, favorite heart
- **Playback** — real stream URLs via ExoPlayer, background service, notification, lock screen
- **Favorites** — Room toggle, context-aware next/prev (all vs favorites only)
- **History** — auto-saves tracks to `TrackHistoryRepository` on every `_currentTrack` change
- **Audio devices** — pauses on BT/wired/USB removal, resumes on reconnect
- **Deeplink buttons** — search current track in Yandex, Spotify, etc.
- **Genre chips** — `FilterChips` row with music genres only
- **Radio Browser integration** — loads stations by tag when VPN is active
- **Icy metadata parsing** for Radio Browser streams (`Artist - Song` format)
### ⚠️ Known Issues / Limitations
1. **Radio Browser API blocked** by some ISPs in Russia (IP `91.98.4.78` times out). App detects this and disables Radio Browser features gracefully. **Workaround: use VPN**.
2. **Radio Record API** does not return per-station tags. Tags are global (`StationsResult.tags`). We map them to Room `TagEntity` table via `TagDao`.
3. **Record station-to-genre mapping** is approximate — uses keyword matching on `tooltip`/`genre` field because API lacks structured genre data per station.
4. **Widget** uses `RemoteViews` with limited Compose support; `widget_player.xml` avoids AppCompat attrs.
5. **Deprecated `Divider`** in `HistoryScreen.kt:60` — should be `HorizontalDivider`.
6. **KSP `NonExistentClass`** — clean build required if Hilt constructor params change.
---
## Key Data Flows
### Station Loading
1. `StationsViewModel.init``refreshStationsUseCase()``StationRepositoryImpl.refreshStations()`
2. `RecordApi.getStations()` returns `StationsResponse(result = StationsResult(stations, tags))`
3. `StationDao.insertAll()` + `TagDao.clearAll()` + `TagDao.insertAll()`
4. `StationsViewModel.tags` combines `stationRepository.getTags()` + `_radioBrowserTags`
### Playback
1. `PlayerViewModel.play(station)``PlayerController.play(url, prefix, name)`
2. **Record source**`getNowPlayingUseCase(id)` polls `/api/stations/now/` every 10s
3. **Radio Browser source** → listens to `PlayerController.icyTitle` (ExoPlayer `onMediaMetadataChanged`)
4. `_currentTrack` updates → auto-saved to history + lock screen metadata via `updateMetadata()`
### Genre Filtering
- `tag == null` → show all Record stations
- `tag != null` → show Radio Browser stations for tag + Record stations whose `genre`/`name` matches keywords (`tagKeywords` map in `StationsViewModel`)
---
## Critical Code Rules
### ExoPlayer Lifecycle
`PlayerController` is `@Singleton`. **`PlayerService.onDestroy()` must NOT call `playerController.release()`** — releasing ExoPlayer kills its internal thread. Subsequent `play()` calls throw `IllegalStateException: dead thread` after OS restarts the service.
### Room Migrations
`AppDatabase` is version 2. `MIGRATION_1_2` creates `tags` table. Always add migration + bump version when adding entities.
### Radio Browser Null-Safety
`RadioBrowserStationDto` fields are nullable (`urlResolved`, `tags`, `country`, `codec`). Mapper falls back: `streamUrl = urlResolved ?: url ?: ""`.
---
## File Inventory
| File | Responsibility |
|------|----------------|
| `di/AppModule.kt` | Two Retrofit instances, Room DB with migration, repository bindings |
| `data/repository/StationRepositoryImpl.kt` | Fetch Record API, cache stations + tags in Room |
| `data/repository/RadioBrowserRepositoryImpl.kt` | Fetch Radio Browser stations/tags, swallow network errors |
| `ui/stations/StationsViewModel.kt` | Genre filtering, tag loading, search, Radio Browser availability flag |
| `ui/stations/StationsScreen.kt` | Grid, chips, search bar, empty state with "Show All" button |
| `service/PlayerController.kt` | ExoPlayer, audio device callback, Icy title capture, MediaSession wrapper |
| `service/PlayerService.kt` | Background service, MediaSession, notification, lock screen |
| `ui/player/PlayerViewModel.kt` | Playback orchestration, now playing polling, Icy parsing, playlist |
| `data/remote/dto/RadioBrowserDto.kt` | DTOs with nullable fields for Radio Browser API |
| `data/local/AppDatabase.kt` | Room v2 with `StationEntity`, `TrackHistoryEntity`, `TagEntity` |
---
## Next Steps / Open Items
- [ ] Replace deprecated `Divider` with `HorizontalDivider` in `HistoryScreen`
- [ ] Add empty-state illustration or better UX when Radio Browser is unavailable
- [ ] Consider caching Radio Browser stations per tag in Room for offline reuse
- [ ] Evaluate if `tagKeywords` mapping for Record stations needs tuning per user feedback
- [ ] Add error retry / exponential backoff for Radio Browser API calls when VPN is unstable