feat: M3 weighted shuffle v1 — real /api/radio with scoring #21
@@ -8,7 +8,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.json.Json
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
@@ -41,21 +40,25 @@ class ResumeController @Inject constructor(
|
||||
}
|
||||
|
||||
suspend fun restore() {
|
||||
// runCatching folds row-missing + decode-failure + empty-tracks into
|
||||
// a single null-or-go-ahead expression so detekt's ReturnCount rule
|
||||
// is satisfied. The .onFailure side effect drops a corrupted row
|
||||
// rather than leaving a poison pill across launches.
|
||||
val row = dao.get() ?: return
|
||||
val payload =
|
||||
try {
|
||||
json.decodeFromString<ResumePayload>(row.json)
|
||||
} catch (e: SerializationException) {
|
||||
// Schema drift or corruption — drop the row and start clean
|
||||
// rather than crashing or leaving a poison pill.
|
||||
Timber.w(e, "ResumeController: dropping unreadable resume state")
|
||||
dao.clear()
|
||||
return
|
||||
}
|
||||
if (payload.tracks.isEmpty()) return
|
||||
runCatching { json.decodeFromString<ResumePayload>(row.json) }
|
||||
.onFailure {
|
||||
Timber.w(it, "ResumeController: dropping unreadable resume state")
|
||||
dao.clear()
|
||||
}
|
||||
.getOrNull()
|
||||
?.takeIf { it.tracks.isNotEmpty() }
|
||||
?: return
|
||||
playerController.setQueue(
|
||||
tracks = payload.tracks,
|
||||
initialIndex = payload.queueIndex.coerceAtLeast(0).coerceAtMost(payload.tracks.lastIndex),
|
||||
initialIndex = payload.queueIndex
|
||||
.coerceAtLeast(0)
|
||||
.coerceAtMost(payload.tracks.lastIndex),
|
||||
source = payload.source,
|
||||
)
|
||||
// PositionMs not seeked yet — Player connection may still be
|
||||
|
||||
Reference in New Issue
Block a user