package dev.stave.lyrics
import kotlinx.serialization.Serializable
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
/**
* Raw response shape from `https://lrclib.net/api/get`. LRCLIB is generous about which
* fields it returns — plain-text-only tracks get `syncedLyrics = null`, instrumentals
* get `instrumental = true` with both lyrics fields null, tracks it has no record of
* return a 404 which we surface as [LyricsResult.NotFound].
*/
@Serializable
data class LrclibResponse(
val id: Long? = null,
val trackName: String? = null,
val artistName: String? = null,
val albumName: String? = null,
val duration: Double? = null,
val instrumental: Boolean = false,
val plainLyrics: String? = null,
val syncedLyrics: String? = null,
)
/**
* One LRC line = timestamp + text. Timestamp is the start of the line; the line's end
* is the next line's start (or the track's duration for the last one).
*/
data class LrcLine(val start: Duration, val text: String) {
companion object {
fun at(millis: Long, text: String) = LrcLine(millis.milliseconds, text)
}
}
/**
* Everything the lyrics UI needs for one track. Synced lyrics give us line-level
* highlighting during playback; plain lyrics are shown as a static block. Both may
* be present; if only plain is present we show that.
*/
sealed interface LyricsResult {
/** Still fetching from LRCLIB (or from the on-disk cache). */
data object Loading : LyricsResult
/** Track exists in LRCLIB and has lyrics. [synced] is non-empty when the track has
* timestamped lyrics; [plain] is the plain-text version (may also be non-null). */
data class Loaded(
val synced: List<LrcLine>,
val plain: String?,
) : LyricsResult
/** LRCLIB knows this track but the rightsholder marked it instrumental. */
data object Instrumental : LyricsResult
/** LRCLIB has no record of this track. */
data object NotFound : LyricsResult
/** External calls are disabled in privacy settings. UI surfaces this with a
* pointer to the setting rather than pretending we searched and found nothing. */
data object PrivacyDisabled : LyricsResult
/** Network / parse / anything else went sideways. */
data class Error(val message: String) : LyricsResult
}