feat: fetch beat metadata from api

This commit is contained in:
2026-05-31 11:41:06 +02:00
parent 633afd115d
commit f7c5b932e9
7 changed files with 176 additions and 2 deletions

View File

@@ -7,6 +7,7 @@ import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import at.lockstep.jukebox.api.LockstepApiException
import at.lockstep.jukebox.db.TrackRow
import at.lockstep.player.data.MetadataFetchResult
import at.lockstep.player.data.UserPreferencesRepository
import at.lockstep.player.data.db.FileMetadataEntity
import at.lockstep.player.data.db.TrackPairingEntity
@@ -349,6 +350,109 @@ class LockstepViewModel(
syncMetadataEntry(entry.copy(id = id))
}
/**
* Fetches beat metadata from the server for each track in [playlistId] that has no local beats file.
* Records [FileMetadataEntity.lastFetchAttemptAt] when the server returns 404 so polling can retry later.
*/
fun fetchBeatsMetadataForPlaylist(
playlistId: String,
playlistDisplayName: String,
) {
viewModelScope.launch(Dispatchers.IO) {
val token = spotifyAccessToken.value?.takeIf { it.isNotBlank() } ?: return@launch
val tracks = loadJukeboxTracksEnsuringDetail(playlistId)
for (track in tracks) {
val trackId = track.trackId ?: continue
fetchBeatMetadataForTrack(
token = token,
trackId = trackId,
playlistDisplayName = playlistDisplayName,
queueIndex = track.position,
)
}
}
}
private suspend fun fetchBeatMetadataForTrack(
token: String,
trackId: String,
playlistDisplayName: String,
queueIndex: Int,
) {
val existing =
fileMetadataDao.findByTrackIdAndType(trackId, FileMetadataEntity.TYPE_BEATS)
if (existing != null && existing.fileUri.isNotBlank()) {
return
}
if (existing?.lastFetchAttemptAt != null) {
return
}
val attemptAt = System.currentTimeMillis()
val result =
try {
app.metadataSyncClient.fetchMetadata(
accessToken = token,
trackId = trackId,
type = FileMetadataEntity.TYPE_BEATS,
)
} catch (e: IOException) {
Log.w(METADATA_TAG, "beat metadata fetch failed trackId=$trackId", e)
return
}
when (result) {
is MetadataFetchResult.Found -> {
val beatsUri =
BeatAnnotationStorage.writeBeatsFileFromJson(
context = getApplication(),
playlistDisplayName = playlistDisplayName,
trackQueueIndex0Based = queueIndex,
json = result.collection,
) ?: return
val now = System.currentTimeMillis()
if (existing != null) {
fileMetadataDao.updateFile(existing.id, beatsUri.toString(), BuildConfig.VERSION_CODE, now)
} else {
fileMetadataDao.insert(
FileMetadataEntity(
fileUri = beatsUri.toString(),
trackId = trackId,
type = FileMetadataEntity.TYPE_BEATS,
version = BuildConfig.VERSION_CODE,
synced = true,
lastSyncedAt = now,
),
)
}
Log.d(METADATA_TAG, "fetched beat metadata from server trackId=$trackId")
}
MetadataFetchResult.NotFound -> {
recordBeatMetadataUnavailable(trackId, attemptAt, existing)
Log.d(METADATA_TAG, "beat metadata not on server trackId=$trackId")
}
}
}
private suspend fun recordBeatMetadataUnavailable(
trackId: String,
attemptAt: Long,
existing: FileMetadataEntity?,
) {
if (existing != null) {
fileMetadataDao.recordFetchAttempt(existing.id, attemptAt)
} else {
fileMetadataDao.insert(
FileMetadataEntity(
fileUri = "",
trackId = trackId,
type = FileMetadataEntity.TYPE_BEATS,
version = BuildConfig.VERSION_CODE,
synced = false,
lastFetchAttemptAt = attemptAt,
),
)
}
}
suspend fun syncPendingMetadata(): String? {
val token = spotifyAccessToken.value
if (token.isNullOrBlank()) {