fix: star visibility, station switching, skip-ahead, and latency optimizations

- Star icons now use distinct tint colors (primary vs faded) for clear state
- Station switching no longer races — old playback job is cancelled before new
- Skip-ahead drops ~1s of buffered audio per tap via atomic counter
- Custom SocketFactory with SO_RCVBUF=16KB and TCP_NODELAY for minimal TCP buffering
- Catch-up drain: discards pre-buffered frames until network reads block (live edge)
- AudioTrack PERFORMANCE_MODE_LOW_LATENCY for smallest hardware buffer

Made-with: Cursor
This commit is contained in:
cottongin
2026-03-10 04:41:40 -04:00
parent 6144de6b08
commit 49bbb54bb9
10 changed files with 376 additions and 72 deletions

View File

@@ -0,0 +1,34 @@
# Bugfix: Star icons, station switching, and seek-to-live
**Date:** 2026-03-09
## Task description
Fixed three issues found during manual testing of the app.
## Changes made
### 1. Star icon visual state (StationListScreen.kt)
- Added explicit tint colors to star icons: `primary` color when starred, faded `onSurfaceVariant` (40% alpha) when unstarred.
- Applies to both station rows and playlist section headers.
### 2. Station switching race condition (RadioPlaybackService.kt)
- Added `playJob` tracking — each new play request cancels the previous playback coroutine before starting a new one.
- The old job's `finally` block now checks `playJob == coroutineContext[Job]` to avoid calling `cleanup()`/`stopSelf()` when being replaced by a new station.
- Tapping the same station now restarts it (re-fires `ACTION_PLAY`).
- Fixed collector coroutine leak: `return@collect` on terminal events (`Error`, `Stopped`) so the SharedFlow collection terminates.
### 3. Seek-to-live feature
- Added `ACTION_SEEK_LIVE` to `RadioPlaybackService` — ends current connection span, stops/restarts the engine for the current station without creating a new `ListeningSession`.
- Added `seekToLive()` to `RadioController`.
- Added `seekToLive()` to `NowPlayingViewModel`.
- Added "SKIP TO LIVE" `FilledTonalButton` to `NowPlayingScreen`, positioned between latency indicator and Stay Connected toggle. Disabled during reconnection.
## Files changed
- `app/src/main/java/.../ui/screens/stationlist/StationListScreen.kt`
- `app/src/main/java/.../service/RadioPlaybackService.kt`
- `app/src/main/java/.../service/RadioController.kt`
- `app/src/main/java/.../ui/screens/nowplaying/NowPlayingScreen.kt`
- `app/src/main/java/.../ui/screens/nowplaying/NowPlayingViewModel.kt`
## Follow-up items
- None identified.

View File

@@ -0,0 +1,75 @@
# Full Implementation — Android 24/7 Radio
**Date:** 2026-03-09
## Task Description
Brainstormed, designed, planned, and implemented a complete Android 24/7 internet radio streaming app from scratch using subagent-driven development.
## What Was Built
### Audio Engine (custom raw pipeline)
- **StreamConnection** — OkHttp HTTP client with ICY metadata header support
- **IcyParser** — Separates audio bytes from ICY metadata, parses StreamTitle into artist/title
- **Mp3FrameSync** — Finds MP3 frame boundaries with two-frame validation and re-sync
- **AudioEngine** — Wires pipeline: StreamConnection → IcyParser → RingBuffer → Mp3FrameSync → MediaCodec → AudioTrack
### Android Service Layer
- **RadioPlaybackService** — Foreground service with wake lock, wifi lock, MediaSession
- **Stay Connected** — Exponential backoff reconnection (1s→30s cap), ConnectivityManager callback for instant retry
- **NotificationHelper** — Media-style notification with stop action
- **RadioController** — Shared state between service and UI via StateFlow
### Data Layer
- **Room Database** — Station, Playlist, MetadataSnapshot, ListeningSession, ConnectionSpan entities with full DAOs
- **DataStore Preferences** — stayConnected, bufferMs, lastStationId
- **PLS/M3U Import/Export** — Full parsers with #EXTIMG support, round-trip tested
### UI (Jetpack Compose + Material 3)
- **Station List** — Playlists as expandable groups, starring, tap-to-play, long-press menu, import
- **Now Playing** — Album art, dual timers (session + connection), latency indicator, stay connected toggle, buffer slider
- **Settings** — Playback prefs, playlist export, recently played, track history with search
- **MiniPlayer** — Bottom bar on station list when playing
### Metadata
- **AlbumArtResolver** — MusicBrainz/Cover Art Archive → ICY StreamUrl → #EXTIMG → placeholder
- **ArtCache** — In-memory LRU cache (500 entries)
- **Coil 3** — Image loading in Compose
## Commit History (20 commits)
1. Design document and implementation plan
2. Project scaffolding (Gradle, manifest, dependencies)
3. Room entities, DAOs, database, DataStore
4. M3U/PLS import/export with tests
5. ICY metadata parser with tests
6. MP3 frame synchronizer with tests
7. HTTP stream connection with tests
8. Audio engine integration
9. Foreground service with Stay Connected
10. Material 3 theme and navigation
11. Station List screen
12. Now Playing screen with dual timers
13. Settings screen with history
14. Album art resolution with MusicBrainz
15. Final integration and README
## Test Coverage
- IcyParser: 10 tests
- Mp3FrameSync: 9 tests
- StreamConnection: 6 tests
- M3uParser: 6 tests
- PlsParser: 5 tests
- PlaylistExporter: 4 tests
- RingBuffer: 4 tests
- AlbumArtResolver: 9 tests (MockWebServer)
## Follow-Up Items
- Test on actual Android 9 device with real Icecast/Shoutcast streams
- Add drag-to-reorder for stations and playlists
- Implement latency estimation from AudioTrack write/play head positions
- Add Bluetooth headset AUDIO_BECOMING_NOISY handling
- Add audio focus management
- Future: recording, clips, analytics (schema already supports it)