LyricsData.kt
69 lines · 2.8 KB
Changed in: 0.5.0
| 1 | package dev.stave.lyrics |
| 2 | |
| 3 | import kotlinx.serialization.Serializable |
| 4 | import kotlin.time.Duration |
| 5 | import kotlin.time.Duration.Companion.milliseconds |
| 6 | |
| 7 | /** |
| 8 | * Raw response shape from `https://lrclib.net/api/get`. LRCLIB is generous about which |
| 9 | * fields it returns — plain-text-only tracks get `syncedLyrics = null`, instrumentals |
| 10 | * get `instrumental = true` with both lyrics fields null, tracks it has no record of |
| 11 | * return a 404 which we surface as [LyricsResult.NotFound]. |
| 12 | */ |
| 13 | @Serializable |
| 14 | data class LrclibResponse( |
| 15 | val id: Long? = null, |
| 16 | val trackName: String? = null, |
| 17 | val artistName: String? = null, |
| 18 | val albumName: String? = null, |
| 19 | val duration: Double? = null, |
| 20 | val instrumental: Boolean = false, |
| 21 | val plainLyrics: String? = null, |
| 22 | val syncedLyrics: String? = null, |
| 23 | ) |
| 24 | |
| 25 | /** |
| 26 | * One LRC line = timestamp + text. Timestamp is the start of the line; the line's end |
| 27 | * is the next line's start (or the track's duration for the last one). We store as a |
| 28 | * Duration rather than raw millis so display code can format with our `toClock()` |
| 29 | * helper and math is unambiguous. |
| 30 | */ |
| 31 | data class LrcLine(val start: Duration, val text: String) { |
| 32 | companion object { |
| 33 | fun at(millis: Long, text: String) = LrcLine(millis.milliseconds, text) |
| 34 | } |
| 35 | } |
| 36 | |
| 37 | /** |
| 38 | * Everything the lyrics UI needs for one track. Synced lyrics give us line-level |
| 39 | * highlighting during playback; plain lyrics are shown as a static block. Both may |
| 40 | * be present (LRCLIB sometimes returns both); if only plain is present we show that. |
| 41 | */ |
| 42 | sealed interface LyricsResult { |
| 43 | /** Still fetching from LRCLIB (or from the on-disk cache). */ |
| 44 | data object Loading : LyricsResult |
| 45 | |
| 46 | /** Track exists in LRCLIB and has lyrics. [synced] is non-empty when the track has |
| 47 | * timestamped lyrics; [plain] is the plain-text version (may also be non-null). */ |
| 48 | data class Loaded( |
| 49 | val synced: List<LrcLine>, |
| 50 | val plain: String?, |
| 51 | ) : LyricsResult |
| 52 | |
| 53 | /** LRCLIB knows this track but the rightsholder marked it instrumental — no lyrics |
| 54 | * will ever be returned. UI should say so clearly rather than pretend it's still |
| 55 | * searching. */ |
| 56 | data object Instrumental : LyricsResult |
| 57 | |
| 58 | /** LRCLIB has no record of this track. Happens for obscure titles, misspelled |
| 59 | * artists, or anything before LRCLIB's crawl date. */ |
| 60 | data object NotFound : LyricsResult |
| 61 | |
| 62 | /** User has turned off external calls in privacy settings. We never contacted |
| 63 | * LRCLIB at all — the UI must say that, not pretend we tried and found nothing. |
| 64 | * Toggling the setting back on triggers a fresh fetch via [LyricsViewModel.refresh]. */ |
| 65 | data object PrivacyDisabled : LyricsResult |
| 66 | |
| 67 | /** Network / parse / anything else went sideways. Rare but worth surfacing. */ |
| 68 | data class Error(val message: String) : LyricsResult |
| 69 | } |


