diff --git a/README.md b/README.md index d235af7..4bab520 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,149 @@ # 24/7 Radio -Personal-use Android app for 24/7 internet radio streaming. +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 -- **Custom Raw Audio Pipeline** — OkHttp → IcyParser → Mp3FrameSync → MediaCodec → AudioTrack for absolute minimum latency (~26ms per MP3 frame) -- **Stay Connected Mode** — Aggressive reconnection with exponential backoff, never gives up -- **Dual Timers** — Session elapsed time and connection elapsed time -- **Latency Indicator** — Estimated stream-to-speaker latency -- **Icecast/Shoutcast Metadata** — Track title, artist extraction from ICY protocol -- **Album Art** — MusicBrainz/Cover Art Archive lookup with fallback chain -- **Playlist Management** — PLS/M3U import/export with #EXTIMG support -- **Station Organization** — Playlists, starring/favoriting, manual reorder +### 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 -./gradlew assembleDebug +./build.sh ``` -## Import Stations +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 import icon in the top bar -3. Select a .m3u or .pls file -4. Stations are added to your list +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 -See [Design Document](docs/plans/2026-03-09-android-247-radio-design.md) and [Implementation Plan](docs/plans/2026-03-09-android-247-radio-implementation.md). +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) diff --git a/docs/screenshots/01-station-list.jpg b/docs/screenshots/01-station-list.jpg new file mode 100644 index 0000000..03c3289 Binary files /dev/null and b/docs/screenshots/01-station-list.jpg differ diff --git a/docs/screenshots/02-now-playing-art.jpg b/docs/screenshots/02-now-playing-art.jpg new file mode 100644 index 0000000..2b49fa9 Binary files /dev/null and b/docs/screenshots/02-now-playing-art.jpg differ diff --git a/docs/screenshots/03-station-list-miniplayer.jpg b/docs/screenshots/03-station-list-miniplayer.jpg new file mode 100644 index 0000000..ba3866f Binary files /dev/null and b/docs/screenshots/03-station-list-miniplayer.jpg differ diff --git a/docs/screenshots/04-now-playing-station-art.jpg b/docs/screenshots/04-now-playing-station-art.jpg new file mode 100644 index 0000000..40634b4 Binary files /dev/null and b/docs/screenshots/04-now-playing-station-art.jpg differ