cottongin 3240db829f Refactor RadioPlaybackService: transition(), URL fallback, fixed finally
- Add transition() - all state changes route through it
- Add ConnectionFailedException for connection failures
- Refactor startEngine to accept urls: List<String>, iterate with Connecting state
- Extract awaitEngine() for event collection
- Set Connecting (not Playing) initially; Playing only on AudioEngineEvent.Started
- Fix handlePlay finally block: handle all states (Paused vs else)
- Update reconnectLoop to resolve URLs inside loop
- Add Service import for STOP_FOREGROUND_REMOVE

Made-with: Cursor
2026-03-11 16:14:27 -04:00
2026-03-10 00:18:05 -04:00
2026-03-10 03:11:24 -04:00

24/7 Radio

Personal-use Android app for 24/7 internet radio streaming.

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

Requirements

  • Android 9.0+ (API 28)
  • Internet connection

Build

./gradlew assembleDebug

Import 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

Architecture

See Design Document and Implementation Plan.

Description
Android app for 24/7 internet radio streaming with minimum latency, aggressive reconnection, and full Icecast/Shoutcast metadata support.
Readme 1 MiB
Languages
Kotlin 97.3%
Shell 2.7%