Release v2026.05.18.0 — integration-tests-in-CI + recommendations/cover-art/discover batch #51
@@ -2,6 +2,7 @@ package com.fabledsword.minstrel.player
|
|||||||
|
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
import androidx.media3.common.MediaMetadata
|
import androidx.media3.common.MediaMetadata
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
@@ -90,7 +91,12 @@ class PlayerController @Inject constructor(
|
|||||||
val controller =
|
val controller =
|
||||||
try {
|
try {
|
||||||
connectController()
|
connectController()
|
||||||
} catch (e: Exception) {
|
} catch (
|
||||||
|
// Service-connection failure is a "show error, move on" boundary —
|
||||||
|
// any throwable from the Media3 IPC handshake gets surfaced via
|
||||||
|
// playbackError. Specific-exception handling buys nothing here.
|
||||||
|
@Suppress("TooGenericExceptionCaught") e: Throwable,
|
||||||
|
) {
|
||||||
uiStateInternal.value =
|
uiStateInternal.value =
|
||||||
uiStateInternal.value.copy(playbackError = e.message ?: "Player unavailable")
|
uiStateInternal.value.copy(playbackError = e.message ?: "Player unavailable")
|
||||||
return
|
return
|
||||||
@@ -132,7 +138,13 @@ class PlayerController @Inject constructor(
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
cont.resume(future.get())
|
cont.resume(future.get())
|
||||||
} catch (e: Exception) {
|
} catch (
|
||||||
|
// ListenableFuture.get() throws ExecutionException +
|
||||||
|
// InterruptedException + CancellationException — handle
|
||||||
|
// them uniformly by forwarding to the continuation;
|
||||||
|
// the caller catches at the outer boundary.
|
||||||
|
@Suppress("TooGenericExceptionCaught") e: Throwable,
|
||||||
|
) {
|
||||||
cont.resumeWithException(e)
|
cont.resumeWithException(e)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -141,19 +153,24 @@ class PlayerController @Inject constructor(
|
|||||||
cont.invokeOnCancellation { future.cancel(/* mayInterruptIfRunning = */ false) }
|
cont.invokeOnCancellation { future.cancel(/* mayInterruptIfRunning = */ false) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun TrackRef.toMediaItem(source: String?): MediaItem =
|
private fun TrackRef.toMediaItem(source: String?): MediaItem {
|
||||||
MediaItem.Builder()
|
val metadata = MediaMetadata.Builder()
|
||||||
|
.setTitle(title)
|
||||||
|
.setArtist(artistName)
|
||||||
|
.setAlbumTitle(albumTitle)
|
||||||
|
.apply {
|
||||||
|
if (source != null) setExtras(sourceExtras(source))
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
return MediaItem.Builder()
|
||||||
.setMediaId(id)
|
.setMediaId(id)
|
||||||
.setUri(streamUrl)
|
.setUri(streamUrl)
|
||||||
.setMediaMetadata(
|
.setMediaMetadata(metadata)
|
||||||
MediaMetadata.Builder()
|
|
||||||
.setTitle(title)
|
|
||||||
.setArtist(artistName)
|
|
||||||
.setAlbumTitle(albumTitle)
|
|
||||||
.apply { source?.let { setExtras(android.os.Bundle().apply { putString(MINSTREL_SOURCE_KEY, it) }) } }
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
.build()
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sourceExtras(source: String): Bundle =
|
||||||
|
Bundle().apply { putString(MINSTREL_SOURCE_KEY, source) }
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val MINSTREL_SOURCE_KEY: String = "minstrel_source"
|
const val MINSTREL_SOURCE_KEY: String = "minstrel_source"
|
||||||
|
|||||||
Reference in New Issue
Block a user