Builds the single MediaSession-backed player that in-app playback,
offline downloads (Phase 5), and Android Auto (Phase 6) will all
share, per the Phase 2+ roadmap's Phase 4.
- DeepwavePlaybackService: ExoPlayer + MediaLibrarySession, built as
a MediaLibraryService from day one (not the plainer
MediaSessionService) so Android Auto only has to add browse-tree
content later, never rebuild the service. Browse tree is a stub
for now (MediaLibrarySession.Callback's defaults deny browsing).
- LocalOrRemoteDataSource: the entire "prefer a downloaded file over
streaming" mechanism as a ResolvingDataSource.Resolver, backed by
an in-memory LocalTrackFiles registry that stays empty until
Phase 5 populates it from Room - reused unmodified by Android Auto
later.
- ExoPlayer's OkHttpDataSource shares the same signed OkHttpClient as
Retrofit/Coil, so SubsonicRequestInterceptor signs stream requests
identically to every other Subsonic call - streamUrl() builds the
request URL the same way coverArtUrl() does for Coil.
- PlaybackController: app-facing facade over a MediaController,
exposing Flow<PlaybackUiState> - same facade convention as
ServerRepository. NowPlayingScreen (new, full-screen: artwork,
seek bar, play/pause/skip) and MiniPlayerBar (now real, replacing
Phase 2/3's static placeholder) both consume it.
- Track clicks in AlbumDetailScreen (queues the whole album from the
clicked index) and SearchScreen (single-song queue) now actually
play, via PlaybackController injected into their ViewModels.
- Scrobbling: submission=false when a track starts, submission=true
for the outgoing track on each transition - a simplified heuristic
rather than a played-percentage threshold, feeding Navidrome's
play-count data that Phase 3's getAlbumList2(frequent/recent) rows
read from.
- Runtime POST_NOTIFICATIONS request added to MainActivity for API
33+ (declared in the manifest but easy to forget the runtime half
of - without it the service still plays, but its notification
never shows).
Split MainScreen into a thin PlayerViewModel-resolving wrapper plus a
previewable MainContent, rather than having MainScreen call
hiltViewModel() directly - preserves the interactive Android Studio
preview from last session, which a direct Hilt dependency would have
broken.
media3 pinned to 1.11.1 (built against Kotlin 2.2.0, matching this
project's 2.2.10 - learned from Phase 3's Coil version conflict to
check this before picking a version this time).
Verified extensively on-device against the real Navidrome server:
real tracks play with correct metadata/artwork, pause/resume and
queue auto-advance work, the real Android MediaSession exposes
correct state (checked independently via `dumpsys media_session`,
not just the app's own UI), background playback survives
foregrounding/backgrounding, and no crashes across the whole session.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Spotify-like Navidrome client scaffold with Hilt DI, a Subsonic API
client (token auth, base-URL rewriting), encrypted single-server
credential storage (DataStore + Tink/Keystore), and a login/session
flow gating Compose Navigation between Login and a placeholder Home
screen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>