# 24/7 Radio Android app for 24/7 internet radio streaming with minimum latency, aggressive reconnection, and full Icecast/Shoutcast metadata support. > [!IMPORTANT] > This project was built with the assistance of [Cursor](https://cursor.com) (Claude Opus/Sonnet 4.6). --- ## Screenshots | Station List | Now Playing | Mini-Player | Station Art Fallback | |:---:|:---:|:---:|:---:| | ![Station list showing SomaFM tab](docs/screenshots/01-station-list.jpg) | ![Now Playing with album art](docs/screenshots/02-now-playing-art.jpg) | ![Station list with mini-player bar](docs/screenshots/03-station-list-miniplayer.jpg) | ![Now Playing with station art fallback](docs/screenshots/04-now-playing-station-art.jpg) | --- ## Features ### Playback - **Custom raw audio pipeline** — OkHttp → IcyParser → Mp3FrameSync → MediaCodec → AudioTrack for minimum latency (~26ms per MP3 frame) - **Stay Connected mode** — aggressive reconnection with exponential backoff; never gives up until you say stop - **Configurable buffer** — 0–500ms slider; 0ms keeps you as live as possible, higher values smooth out spotty connections - **Icecast/Shoutcast metadata** — ICY protocol track title and artist extraction - **Album art** — MusicBrainz/Cover Art Archive lookup with a fallback chain (ICY stream URL → station default art → station logo → placeholder) ### Station Management - **SomaFM built-in** — full SomaFM catalog pre-loaded, sorted by listener count - **Per-station stream quality** — choose 128/256 kbps and SSL/non-SSL per station, or set a global preference order - **PLS/M3U import/export** — with `#EXTIMG` support for station artwork URLs - **Tabs (playlists)** — pin/unpin, rename, and drag-to-reorder tabs and stations within them - **Starring/favoriting** — starred stations sort to the top of their tab ### Now Playing Screen - **Blurred album art background** — art fills the screen behind the player controls - **Session timer** — total elapsed time since you hit play, not reset on reconnect - **Connection timer** — elapsed time for the current TCP connection, resets on each reconnect - **Latency indicator** — estimated stream-to-speaker latency in ms - **Track history** — every track change is logged to the database with station and timestamp - **Now Playing file logging** — optionally write track history to CSV, JSON Lines, or plain text ### System Integration - **Media3 notification** — system media notification with album art, transport controls (play/stop + seek-to-live), lockscreen and Bluetooth headset support - **Android Auto** — app registers as a media source; browse your playlists and stations from the car display ### Build Tooling - **`build.sh`** — interactive menu: release APK (signed), debug APK, keystore management, and clean --- ## Requirements - Android 9.0+ (API 28) - Internet connection --- ## Build The recommended path is the interactive build script: ```bash ./build.sh ``` Menu options: 1. **Build Release APK** — signed, ready to sideload 2. **Build Debug APK** — quick, for testing 3. **Manage Keystore** — create or inspect the signing key 4. **Clean** — remove build artifacts On first run, choose option 3 to create a keystore before building a release APK. Output lands in `dist/`. Alternatively, build directly with Gradle: ```bash ./gradlew assembleDebug # debug ./gradlew assembleRelease # release (requires keystore configured in build.gradle.kts) ``` --- ## Importing Stations 1. Open the app 2. Tap the settings gear → Import 3. Select a `.m3u` or `.pls` file 4. Stations are added to your library Exported files include `#EXTIMG` tags for stations that have a default artwork URL set. --- ## SomaFM The SomaFM catalog is pre-loaded on first launch — no import needed. Stations are sorted by current listener count (refreshable with the sync button). You can set a global stream quality preference (256 kbps SSL recommended) or override it per station via long-press → Quality. --- ## Architecture Four layers: ``` ┌─────────────────────────────────────────┐ │ UI Layer │ │ Compose screens, ViewModel, StateFlow │ ├─────────────────────────────────────────┤ │ Service Layer │ │ RadioPlaybackService (MediaLibrary │ │ Service), RadioPlayerAdapter (Media3 │ │ Player facade), wake + wifi locks │ ├─────────────────────────────────────────┤ │ Audio Engine │ │ StreamConnection → IcyParser → │ │ Mp3FrameSync → MediaCodec → AudioTrack │ ├─────────────────────────────────────────┤ │ Data Layer │ │ Room DB, PLS/M3U import/export, │ │ DataStore preferences │ └─────────────────────────────────────────┘ ``` The audio engine is a standalone component with no Android framework dependencies. The service owns reconnection policy, Android Auto browse tree, and the Media3 session. The UI observes state via `StateFlow` and never touches the engine directly. For full detail see the [design document](docs/plans/2026-03-09-android-247-radio-design.md) and [Media3/Android Auto design](docs/plans/2026-03-18-media3-android-auto-design.md). --- ## Tech Stack | Layer | Library | |---|---| | Language | Kotlin | | UI | Jetpack Compose, Material Design 3 | | Media session / Auto | AndroidX Media3 (media3-session, media3-common) | | HTTP streaming | OkHttp | | Audio decoding | MediaCodec (hardware-accelerated) | | Audio output | AudioTrack | | Database | Room | | Preferences | DataStore | | Image loading | Coil | | Album art lookup | MusicBrainz / Cover Art Archive (no API key required) | --- ## License [MIT](LICENSE)