Documents all DHU testing attempts, root causes for failures (DHU v2.0
protocol incompatibility with AA v16+), and lessons learned. Adds
*.apkm to .gitignore to avoid accidentally committing large APK bundles.
Made-with: Cursor
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
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
- 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
Covers architecture for replacing MediaSessionCompat with Media3
MediaLibraryService, RadioPlayerAdapter facade, browse tree for
Android Auto, and notification improvements.
Made-with: Cursor
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
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
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
Captures the design for replacing BounceMarqueeText with a
constant-speed TickerText composable (33 dp/s, continuous leftward
scroll with gap looping).
Made-with: Cursor
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
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
- 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
- Add StationPreferenceDao to StreamResolver constructor
- Check station_preferences before station.qualityOverride / global prefs
- Mock stationPrefDao in StreamResolverTest, add precedence test
Made-with: Cursor
- 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
- 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
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
Build failures now display a colored error banner with the last 20
lines of Gradle output. Removed stderr suppression from clean_build
so Gradle errors are visible.
Made-with: Cursor