diff --git a/app/src/main/java/at/lockstep/player/LockstepViewModel.kt b/app/src/main/java/at/lockstep/player/LockstepViewModel.kt index aebbfd7..d90ac46 100644 --- a/app/src/main/java/at/lockstep/player/LockstepViewModel.kt +++ b/app/src/main/java/at/lockstep/player/LockstepViewModel.kt @@ -562,13 +562,34 @@ class LockstepViewModel( app.playlistRepository.syncPlaylistDetail(playlistId) null } catch (e: LockstepApiException) { - e.message ?: "Load failed" + syncPlaylistDetailErrorForLibraryOpen(playlistId, e) } catch (e: IOException) { - e.message ?: "Load failed" + syncPlaylistDetailErrorForLibraryOpen(playlistId, e) } } } + /** + * Playlist detail fetch is best-effort when opening from the library. Unauthorized responses mean + * the token is stale; cached jukebox rows (if any) are still valid for pairing and playback. + */ + private fun syncPlaylistDetailErrorForLibraryOpen(playlistId: String, e: Exception): String? { + if (isPlaylistDetailHttp401(e)) { + Log.w(TAG, "syncPlaylistDetail unauthorized for $playlistId, opening from jukebox cache", e) + return null + } + if (app.playlistRepository.getTracks(playlistId).isNotEmpty()) { + Log.w(TAG, "syncPlaylistDetail failed for $playlistId, using cached tracks", e) + return null + } + return e.message ?: "Load failed" + } + + private fun isPlaylistDetailHttp401(e: Exception): Boolean { + val msg = e.message ?: return false + return msg.contains("HTTP 401", ignoreCase = true) + } + fun completeOnboarding() { viewModelScope.launch { prefs.setOnboardingComplete(true)