Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Synced/Unsynced Lyrics Toggle and Refactor Musixmatch API, closes #91 #95

Merged
merged 12 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ class UserSettingsController(private val dataStore: DataStore<Preferences>) {
var multiPersonWordByWord by mutableStateOf(dataStore.get(multiPersonWordByWordKey, false))
private set

var syncedMusixmatch by mutableStateOf(dataStore.get(syncedMusixmatchKey, true))
private set

var pureBlack by mutableStateOf(dataStore.get(pureBlackKey, false))
private set

Expand Down Expand Up @@ -74,6 +77,11 @@ class UserSettingsController(private val dataStore: DataStore<Preferences>) {
multiPersonWordByWord = to
}

fun updateSyncedMusixmatch(to: Boolean) {
dataStore.set(syncedMusixmatchKey, to)
syncedMusixmatch = to
}

fun updateDisableMarquee(to: Boolean) {
dataStore.set(disableMarqueeKey, to)
disableMarquee = to
Expand All @@ -96,6 +104,7 @@ private val blacklistedFoldersKey = stringPreferencesKey("blacklist")
private val hideLyricsKey = booleanPreferencesKey("hide_lyrics")
private val includeTranslationKey = booleanPreferencesKey("include_translation")
private val multiPersonWordByWordKey = booleanPreferencesKey("multi_person_word_by_word")
private val syncedMusixmatchKey = booleanPreferencesKey("synced_lyrics")
private val disableMarqueeKey = booleanPreferencesKey("marquee_disable")
private val pureBlackKey = booleanPreferencesKey("pure_black")
private val sdCardPathKey = stringPreferencesKey("sd_card_path")
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ class LyricsProviderService {
// Apple Track ID
private var appleID = 0L

// Musixmatch Track ID
private var musixmatchID = 0L
// Musixmatch Song Info
private var musixmatchSongInfo: SongInfo? = null
// TODO: Use values from SongInfo object returned by search instead of storing them here

/**
Expand Down Expand Up @@ -69,7 +69,7 @@ class LyricsProviderService {
} ?: throw NoTrackFoundException()

Providers.MUSIXMATCH -> MusixmatchAPI().getSongInfo(query).also {
musixmatchID = it?.musixmatchID ?: 0
musixmatchSongInfo = it
} ?: throw NoTrackFoundException()
}
} catch (e: InternalErrorException) {
Expand All @@ -94,7 +94,8 @@ class LyricsProviderService {
provider: Providers,
// TODO providers could be a sealed interface to include such parameters
includeTranslationNetEase: Boolean = false,
multiPersonWordByWord: Boolean = false
multiPersonWordByWord: Boolean = false,
syncedMusixmatch: Boolean = true
): String? {
return try {
when (provider) {
Expand All @@ -108,7 +109,10 @@ class LyricsProviderService {
appleID,
multiPersonWordByWord
)
Providers.MUSIXMATCH -> MusixmatchAPI().getSyncedLyrics(musixmatchID)
Providers.MUSIXMATCH -> MusixmatchAPI().getLyrics(
musixmatchSongInfo,
syncedMusixmatch
)
}
} catch (e: Exception) {
null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import io.ktor.client.statement.bodyAsText
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import pl.lambada.songsync.domain.model.SongInfo
import pl.lambada.songsync.domain.model.lyrics_providers.others.MusixmatchLyricsResponse
import pl.lambada.songsync.domain.model.lyrics_providers.others.MusixmatchSearchResponse
import pl.lambada.songsync.util.EmptyQueryException
import pl.lambada.songsync.util.networking.Ktor.client
Expand All @@ -21,52 +20,53 @@ class MusixmatchAPI {
* @return Search result as a SongInfo object.
*/
suspend fun getSongInfo(query: SongInfo): SongInfo? {
val search = withContext(Dispatchers.IO) {
val artistName = withContext(Dispatchers.IO) {
URLEncoder.encode(
"${query.songName} ${query.artistName}",
"${query.artistName}",
Charsets.UTF_8.toString()
)
}

if (search == " ")
val songName = withContext(Dispatchers.IO) {
URLEncoder.encode(
"${query.songName}",
Charsets.UTF_8.toString()
)
}

if (artistName == "" || songName == "")
throw EmptyQueryException()

val response = client.get(
"$baseURL/search?q=$search"
"$baseURL/full?artist=$artistName&track=$songName"
)
val responseBody = response.bodyAsText(Charsets.UTF_8)

if (response.status.value !in 200..299 || responseBody == "[]")
if (response.status.value !in 200..299)
return null

val json = json.decodeFromString<List<MusixmatchSearchResponse>>(responseBody)

val result = json[0]

val result = json.decodeFromString<MusixmatchSearchResponse>(responseBody)

return SongInfo(
songName = result.songName,
artistName = result.artistName,
songLink = result.url,
albumCoverLink = result.artwork,
musixmatchID = result.id,
hasSyncedLyrics = result.hasSyncedLyrics,
hasUnsyncedLyrics = result.hasSyncedLyrics,
syncedLyrics = result.syncedLyrics?.lyrics,
unsyncedLyrics = result.unsyncedLyrics?.lyrics
)
}

/**
* Searches for synced lyrics using the song name and artist name.
* @param id The ID of the song from search results.
* @return The synced lyrics as a string.
* Returns the lyrics.
* @param songInfo The SongInfo of the song from search results.
* @return The lyrics as a string or null if the lyrics were not found.
*/
suspend fun getSyncedLyrics(id: Long): String? {
val response = client.get(
"$baseURL/lyrics?id=$id"
)
val responseBody = response.bodyAsText(Charsets.UTF_8)

if (response.status.value !in 200..299)
return null

val json = json.decodeFromString<MusixmatchLyricsResponse>(responseBody)
return json.lyrics
fun getLyrics(songInfo: SongInfo?, synced: Boolean = true): String? {
return if(synced) songInfo?.syncedLyrics
else songInfo?.unsyncedLyrics
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,8 @@ data class SongInfo(
var neteaseID: Long? = null, // Netease-only
var appleID: Long? = null, // Apple-only
var musixmatchID: Long? = null, // Musixmatch-only
var hasSyncedLyrics: Boolean? = null, // Musixmatch-only
var hasUnsyncedLyrics: Boolean? = null, // Musixmatch-only
var syncedLyrics: String? = null, // Musixmatch-only
var unsyncedLyrics: String? = null, // Musixmatch-only
) : Parcelable
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,24 @@ data class MusixmatchSearchResponse(
val url: String,
val albumId: Long,
val hasSyncedLyrics: Boolean,
val hasUnsyncedLyrics: Boolean
val hasUnsyncedLyrics: Boolean,
val syncedLyrics: SyncedLyricsResponse? = null,
val unsyncedLyrics: UnsyncedLyricsResponse? = null
)

@Serializable
data class MusixmatchLyricsResponse(
data class SyncedLyricsResponse(
val id: Long,
val duration: Int,
val language: String,
val updatedTime: String,
val lyrics: String
)

@Serializable
data class UnsyncedLyricsResponse(
val id: Long,
val language: String,
val updatedTime: String,
val lyrics: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import pl.lambada.songsync.ui.screens.about.components.MultiPersonSwitch
import pl.lambada.songsync.ui.screens.about.components.PureBlackThemeSwitch
import pl.lambada.songsync.ui.screens.about.components.SdCardPathSetting
import pl.lambada.songsync.ui.screens.about.components.SupportSection
import pl.lambada.songsync.ui.screens.about.components.SyncedLyricsSwitch
import pl.lambada.songsync.ui.screens.about.components.TranslationSwitch
import pl.lambada.songsync.ui.screens.about.components.UpdateAvailableDialog
import pl.lambada.songsync.util.ext.getVersion
Expand Down Expand Up @@ -89,6 +90,13 @@ fun AboutScreen(
)
}

item {
SyncedLyricsSwitch(
selected = userSettingsController.syncedMusixmatch,
onToggle = { userSettingsController.updateSyncedMusixmatch(it) }
)
}

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
item {
SdCardPathSetting(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package pl.lambada.songsync.ui.screens.about.components

import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import pl.lambada.songsync.R
import pl.lambada.songsync.ui.components.AboutItem
import pl.lambada.songsync.ui.components.SwitchItem

@Composable
fun SyncedLyricsSwitch(selected: Boolean, onToggle: (Boolean) -> Unit) {
AboutItem(label = stringResource(id = R.string.synced_lyrics)) {
SwitchItem(
label = stringResource(id = R.string.synced_lyrics_summary),
selected = selected,
onClick = { onToggle(!selected) }
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,8 @@ class HomeViewModel(
version,
provider = userSettingsController.selectedProvider,
includeTranslationNetEase = userSettingsController.includeTranslation,
multiPersonWordByWord = userSettingsController.multiPersonWordByWord
multiPersonWordByWord = userSettingsController.multiPersonWordByWord,
syncedMusixmatch = userSettingsController.syncedMusixmatch
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ class LyricsFetchViewModel(
version,
userSettingsController.selectedProvider,
userSettingsController.includeTranslation,
userSettingsController.multiPersonWordByWord
userSettingsController.multiPersonWordByWord,
userSettingsController.syncedMusixmatch
)

fun loadSongInfo(context: Context, tryingAgain: Boolean = false) {
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,6 @@
<string name="embed_non_local_song_error">You tried to embed the lyrics to a non-local file. Aborting operation.</string>
<string name="multi_person_word_by_word">Multi-person word by word lyrics</string>
<string name="multi_person_word_by_word_summary">Use multi-person lyrics format when getting lyrics from Apple Music</string>
<string name="synced_lyrics">Get Synced Lyrics</string>
<string name="synced_lyrics_summary">Toggle to switch between synced and unsynced lyrics from Musixmatch.</string>
</resources>
Loading