Commit Graph

50 Commits

Author SHA1 Message Date
cottongin
d451d005c0 fix: register session with addSession() to activate Media3 notifications
MediaSessionService only sets up its internal MediaNotificationManager
when addSession() is called. This normally happens when a MediaController
binds, but since we use custom intents, no controller ever connected.
Calling addSession() after session creation activates the notification
pipeline. Also removed debug logging.

Made-with: Cursor
2026-03-18 06:58:55 -04:00
cottongin
5126ad6366 fix: post placeholder foreground notification immediately on play
startForegroundService() requires startForeground() within ~5 seconds.
The async coroutine (DB queries, old job join) delayed the Media3
notification pipeline past this deadline. Now we:
- Create the media session in onCreate() instead of lazily
- Post a minimal "Connecting…" notification synchronously in
  onStartCommand before launching the async play coroutine
- Media3 replaces it with the proper media notification once the
  adapter state updates to Playing

Made-with: Cursor
2026-03-18 06:41:24 -04:00
cottongin
f65b0db0f4 fix: propagate ICY metadata to MediaItem and remove unsafe stopSelf
- updateMetadata() now also updates _currentMediaItem so track info
  and artwork flow through to the notification/lockscreen via getState()
- Remove else -> stopSelf() from onStartCommand to avoid killing the
  service on internal MediaLibraryService intents

Made-with: Cursor
2026-03-18 06:14:49 -04:00
cottongin
00f4da20b6 feat: rewrite RadioPlaybackService as MediaLibraryService with browse tree
Made-with: Cursor
2026-03-18 06:10:17 -04:00
cottongin
04225de888 feat: add RadioPlayerAdapter — thin Player facade for Media3
Made-with: Cursor
2026-03-18 05:58:09 -04:00
cottongin
39653462d1 feat: add Android Auto automotive_app.xml and update service manifest
Made-with: Cursor
2026-03-18 05:56:38 -04:00
cottongin
effb498896 chore: swap androidx.media for media3-session + media3-common
Made-with: Cursor
2026-03-18 05:55:32 -04:00
cottongin
a978905ea6 Add RadioDatabase schema v6
Made-with: Cursor
2026-03-18 05:25:38 -04:00
cottongin
d5dc2d81bd fix: scroll only until text exits screen
Was scrolling an extra container-width of invisible distance after the
text had already left the screen, adding ~11s of dead time. Now scrolls
to -textWidthPx (text just off-screen) instead of -(textWidthPx +
containerWidthPx).

Made-with: Cursor
2026-03-18 05:18:53 -04:00
cottongin
600687ab56 feat: add fade-in and shorten restart pause for ticker
Text now fades in over 300ms when it reappears instead of popping in.
Restart pause shortened from 1.5s to 0.5s. Initial 1.5s delay kept.
Switched from infiniteRepeatable to Animatable + LaunchedEffect loop
for independent control over initial vs subsequent cycles.

Made-with: Cursor
2026-03-18 04:10:23 -04:00
cottongin
9bd38224b1 feat: replace bounce marquee with constant-speed ticker
Replaces BounceMarqueeText with TickerText that scrolls at a fixed
33 dp/s regardless of text length. Text scrolls left off-screen with
a container-width gap before looping. Initial 1.5s delay lets the
user read the beginning.

Made-with: Cursor
2026-03-12 05:52:42 -04:00
cottongin
a4c0fa9a6e fix: resolve playback stop failure and Icecast stream disconnection
AudioEngine.stop() only called Thread.interrupt(), which doesn't
interrupt blocking InputStream.read() on OkHttp streams. This caused
audio to continue after stop and blocked subsequent play attempts
(old job never completed). Now closes timedStream to force the
blocking read to fail.

Removed LowLatencySocketFactory (16KB receive buffer) which triggered
Icecast slow-client disconnection on burst-on-connect. Force HTTP/1.1
to avoid HTTP/2 negotiation issues with Icecast servers.

Also fixed: awaitEngine() SharedFlow collector coroutine leak, and
added MAX_CATCHUP_FRAMES safety cap to prevent infinite frame skipping.

Made-with: Cursor
2026-03-11 18:14:13 -04:00
cottongin
8872b9de96 fix: resolve tab long-press context menu blocked by drag gesture
The detectDragGesturesAfterLongPress modifier was consuming the
long-press event before combinedClickable.onLongClick could fire.
Replace combinedClickable with movement-tracking inside the drag
handler — small movement on release shows the context menu, large
movement commits the reorder. Move tap handling into Tab.onClick.

Made-with: Cursor
2026-03-11 17:12:14 -04:00
cottongin
5f7cb050e5 fix: address code review issues — pinned on fresh install, reorder selection, stale rename, detached scopes, duplicate click
Made-with: Cursor
2026-03-11 16:32:33 -04:00
cottongin
5c87f821e0 feat: add drag-to-reorder for tabs within pinned/unpinned groups
Made-with: Cursor
2026-03-11 16:30:00 -04:00
cottongin
2d3b0cea7a feat: add tab rename and pin/unpin via long-press context menu
Made-with: Cursor
2026-03-11 16:27:13 -04:00
cottongin
973bb9e7fe feat: add playlist naming dialog on import
Made-with: Cursor
2026-03-11 16:25:58 -04:00
cottongin
eb111fce35 feat: add pinned column to Playlist with migration
Made-with: Cursor
2026-03-11 16:25:56 -04:00
cottongin
cf57d22dc5 feat: add per-station quality UI — context menu and dialog
- Create QualityOverrideDialog with radio selection for preferred quality
- Add Quality menu item to station long-press (only for stations with streams)
- Add setQualityOverride, getStreamsForStation, getQualityOverrideForStation to ViewModel
- Add getStationIdsWithStreams to StationStreamDao for hasStreams lookup

Made-with: Cursor
2026-03-11 16:22:59 -04:00
cottongin
92f3b35418 Task 8: Wire StationPreference into StreamResolver
- Add StationPreferenceDao to StreamResolver constructor
- Check station_preferences before station.qualityOverride / global prefs
- Mock stationPrefDao in StreamResolverTest, add precedence test

Made-with: Cursor
2026-03-11 16:20:21 -04:00
cottongin
2087b48e4e Task 7: Create StationPreference entity and DAO
- Add StationPreference entity with FK to Station, unique index on stationId
- Add StationPreferenceDao with getByStationId, upsert, deleteByStationId
- Add station_preferences table via MIGRATION_4_5

Made-with: Cursor
2026-03-11 16:20:14 -04:00
cottongin
d0443dfd85 Add StreamResolver unit tests for URL ordering logic
Made-with: Cursor
2026-03-11 16:16:48 -04:00
cottongin
98730959dd fix: add navigation bar padding to miniplayer
Made-with: Cursor
2026-03-11 16:16:09 -04:00
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
cottongin
0d5992f4a3 test(PlaybackState): add state machine transition tests
- Idle → Connecting, Connecting → Playing/Paused/Reconnecting/Idle
- Playing → Paused/Reconnecting/Idle
- Paused → Connecting
- Reconnecting → Connecting/Idle
- Stop from any state returns to Idle

Made-with: Cursor
2026-03-11 16:11:14 -04:00
cottongin
f3d28f1a52 refactor(PlaybackState): add URL context to Connecting state
- Add urls: List<String> = emptyList() for URL fallback list
- Add currentUrlIndex: Int = 0 for tracking fallback progress
- Defaults preserve backward compatibility (Connecting(station) still works)

Made-with: Cursor
2026-03-11 16:11:12 -04:00
cottongin
7da9fe9b5b fix: enable cleartext HTTP for radio streams
Made-with: Cursor
2026-03-11 16:09:54 -04:00
cottongin
e1286911cc fix: disable NullSafeMutableLiveData lint check crashing release builds
The lintVitalAnalyzeRelease task fails with an IncompatibleClassChangeError
in NonNullableMutableLiveDataDetector, which is a known bug in the lint
library. Disabling this specific check unblocks release APK builds.

Made-with: Cursor
2026-03-11 16:09:22 -04:00
cottongin
d381e80828 fix: polish pass — dark mode, notifications, icons, edge-to-edge
- Wrap SettingsScreen in Scaffold to fix invisible text in dark mode
- Fix notification showing raw ICY metadata instead of parsed track info
- Add proper white-on-transparent notification icon
- Create branded adaptive launcher icon (amber tower on BlueGray900)
- Add edge-to-edge support with proper window inset handling
- Add About section to Settings with version and app info
- Enable BuildConfig generation for version display

Made-with: Cursor
2026-03-10 21:18:48 -04:00
cottongin
c0ba23b208 feat: now playing UX overhaul with stream quality and audio improvements
Redesign Now Playing screen with blurred album art background, dominant
color extraction, bounce marquee for long text, cross-fade artwork
transitions, icon-labeled timers, and stream quality badge (bitrate,
codec, SSL). Add StreamInfo propagation from connection through to UI.
Fix MediaCodec PTS spam by providing incrementing presentation timestamps.

Made-with: Cursor
2026-03-10 20:16:43 -04:00
cottongin
6481d74d95 feat: tabbed station libraries, SomaFM integration, and settings panel
Add tabbed playlist UI with SomaFM as a built-in library including live
listener counts, station hiding, and stream quality selection. Implement
settings panel with quality preferences, listening history, and playlist
import/export improvements. Includes DB migrations 1-4, SomaFM seed
data, stream resolver, and now-playing history logging.

Made-with: Cursor
2026-03-10 20:16:09 -04:00
cottongin
5dd7a411ed fix: refactor player state machine to eliminate race conditions
Introduce a Connecting state so the UI reflects user intent immediately,
centralize navigation in MainActivity via state transitions, and replace
the pauseRequested volatile flag with controller state as single source
of truth.

Made-with: Cursor
2026-03-10 05:15:31 -04:00
cottongin
49bbb54bb9 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
2026-03-10 04:41:40 -04:00
cottongin
c143483f33 feat: wire album art to UI and handle Android 13+ notification permission
Made-with: Cursor
2026-03-10 03:11:09 -04:00
cottongin
89b58477c9 feat: add album art resolution with MusicBrainz and fallback chain
Made-with: Cursor
2026-03-10 03:07:32 -04:00
cottongin
20daa86b52 feat: add Settings screen with export, history, and track search
Made-with: Cursor
2026-03-10 02:49:38 -04:00
cottongin
7678b2b12a feat: add Now Playing screen with dual timers and latency indicator
Made-with: Cursor
2026-03-10 02:48:26 -04:00
cottongin
30b4bc9814 feat: add Station List screen with playlists, starring, and import
Made-with: Cursor
2026-03-10 02:42:25 -04:00
cottongin
cacbb0d98d feat: add Material 3 theme and screen navigation
Made-with: Cursor
2026-03-10 02:37:26 -04:00
cottongin
ca7b757812 feat: add foreground playback service with Stay Connected reconnection
Made-with: Cursor
2026-03-10 02:30:48 -04:00
cottongin
cfb04d3200 feat: integrate audio engine pipeline with MediaCodec and AudioTrack
Made-with: Cursor
2026-03-10 02:18:36 -04:00
cottongin
7814d682f6 feat: add HTTP stream connection with ICY header support
Made-with: Cursor
2026-03-10 02:14:46 -04:00
cottongin
fd73caf181 fix: correct MP3 frame header values in tests
Made-with: Cursor
2026-03-10 02:01:46 -04:00
cottongin
3d4d163508 feat: add MP3 frame synchronizer with re-sync and validation
Made-with: Cursor
2026-03-10 01:51:24 -04:00
cottongin
1a3a58b8f0 fix: add metaint validation and mid-metadata truncation test
Made-with: Cursor
2026-03-10 01:22:31 -04:00
cottongin
45a946f829 feat: add ICY metadata parser with artist/title extraction
Made-with: Cursor
2026-03-10 01:18:51 -04:00
cottongin
bb14a6af53 fix: sanitize station names and validate URLs in parsers
Made-with: Cursor
2026-03-10 01:12:46 -04:00
cottongin
fcf02c2595 feat: add M3U/PLS import and export with EXTIMG support
Made-with: Cursor
2026-03-10 01:10:07 -04:00
cottongin
b3d22650c7 feat: add Room entities, DAOs, database, and DataStore preferences
Made-with: Cursor
2026-03-10 01:04:55 -04:00
cottongin
2a9b21b67f feat: scaffold Android project with dependencies
Made-with: Cursor
2026-03-10 00:49:16 -04:00