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>
43 lines
1.6 KiB
Kotlin
43 lines
1.6 KiB
Kotlin
package com.InfernalAquatics.deepwave
|
|
|
|
import android.Manifest
|
|
import android.content.pm.PackageManager
|
|
import android.os.Build
|
|
import android.os.Bundle
|
|
import androidx.activity.ComponentActivity
|
|
import androidx.activity.compose.setContent
|
|
import androidx.activity.enableEdgeToEdge
|
|
import androidx.activity.result.contract.ActivityResultContracts
|
|
import androidx.core.content.ContextCompat
|
|
import com.InfernalAquatics.deepwave.ui.navigation.DeepwaveNavHost
|
|
import com.InfernalAquatics.deepwave.ui.theme.DeepwaveTheme
|
|
import dagger.hilt.android.AndroidEntryPoint
|
|
|
|
@AndroidEntryPoint
|
|
class MainActivity : ComponentActivity() {
|
|
|
|
private val notificationPermissionLauncher =
|
|
registerForActivityResult(ActivityResultContracts.RequestPermission()) { /* playback works either way; only the notification is affected */ }
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
super.onCreate(savedInstanceState)
|
|
enableEdgeToEdge()
|
|
requestNotificationPermissionIfNeeded()
|
|
setContent {
|
|
DeepwaveTheme {
|
|
DeepwaveNavHost()
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Without this on API 33+, the playback service still plays but its notification never shows. */
|
|
private fun requestNotificationPermissionIfNeeded() {
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
|
|
val granted = ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) ==
|
|
PackageManager.PERMISSION_GRANTED
|
|
if (!granted) {
|
|
notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
|
|
}
|
|
}
|
|
}
|