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

Feature/split synced lyrics #22

Merged
merged 2 commits into from
Jan 20, 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
5 changes: 2 additions & 3 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import io.github.teccheck.fastlyrics.model.SongWithLyrics
import io.github.teccheck.fastlyrics.utils.Utils
import java.util.concurrent.Executors
import dev.forkhandles.result4k.Result
import io.github.teccheck.fastlyrics.model.LyricsType

object LyricStorage {
private const val TAG = "LyricsStorage"
Expand Down Expand Up @@ -73,4 +74,7 @@ object LyricStorage {

fun findSong(title: String, artist: String): SongWithLyrics? =
database.songsDao().findSong(title, artist)

fun findSong(title: String, artist: String, type: LyricsType): SongWithLyrics? =
database.songsDao().findSong(title, artist, type)
}
31 changes: 25 additions & 6 deletions app/src/main/java/io/github/teccheck/fastlyrics/api/LyricsApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import androidx.lifecycle.MutableLiveData
import dev.forkhandles.result4k.Failure
import dev.forkhandles.result4k.Result
import dev.forkhandles.result4k.Success
import io.github.teccheck.fastlyrics.api.provider.Deezer
import io.github.teccheck.fastlyrics.api.provider.Genius
import io.github.teccheck.fastlyrics.api.provider.LyricsProvider
import io.github.teccheck.fastlyrics.exceptions.LyricsApiException
import io.github.teccheck.fastlyrics.exceptions.LyricsNotFoundException
import io.github.teccheck.fastlyrics.model.LyricsType
import io.github.teccheck.fastlyrics.model.SearchResult
import io.github.teccheck.fastlyrics.model.SongMeta
import io.github.teccheck.fastlyrics.model.SongWithLyrics
Expand All @@ -17,12 +20,17 @@ object LyricsApi {

private const val TAG = "LyricsApi"

private val executor = Executors.newSingleThreadExecutor()
private val executor = Executors.newFixedThreadPool(2)

private var providers: Array<LyricsProvider> = arrayOf(Genius)
private var providers_synced: Array<LyricsProvider> = arrayOf(Deezer)

private var providers: Array<LyricsProvider> = LyricsProvider.getAllProviders()
private val provider: LyricsProvider
get() = providers.first()

private val provider_synced: LyricsProvider
get() = providers_synced.first()

fun setProviderOrder(order: Array<String>) {
val all = LyricsProvider.getAllProviders()
providers = order.mapNotNull { name -> all.find { provider -> provider.getName() == name } }
Expand All @@ -31,16 +39,20 @@ object LyricsApi {

fun getLyricsAsync(
songMeta: SongMeta,
liveDataTarget: MutableLiveData<Result<SongWithLyrics, LyricsApiException>>
liveDataTarget: MutableLiveData<Result<SongWithLyrics, LyricsApiException>>,
synced: Boolean = false
) {
executor.submit {
val song = songMeta.artist?.let { LyricStorage.findSong(songMeta.title, it) }
Log.d(TAG, "getLyricsAsync($synced)")

val type = if (synced) LyricsType.LRC else LyricsType.RAW_TEXT
val song = songMeta.artist?.let { LyricStorage.findSong(songMeta.title, it, type) }
if (song != null) {
liveDataTarget.postValue(Success(song))
return@submit
}

val result = fetchLyrics(songMeta)
val result = fetchLyrics(songMeta, synced)
liveDataTarget.postValue(result)

if (result is Success) {
Expand Down Expand Up @@ -82,7 +94,8 @@ object LyricsApi {
}

private fun fetchLyrics(
songMeta: SongMeta
songMeta: SongMeta,
synced: Boolean = false
): Result<SongWithLyrics, LyricsApiException> {
var searchQuery = songMeta.title
if (songMeta.artist != null) {
Expand All @@ -92,6 +105,12 @@ object LyricsApi {
var bestResult: SearchResult? = null
var bestResultScore = 0.0

val providers = if (synced) {
providers_synced
} else {
providers
}

for (provider in providers) {
val search = provider.search(searchQuery)
if (search !is Success)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.github.teccheck.fastlyrics.api.storage
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import io.github.teccheck.fastlyrics.model.LyricsType
import io.github.teccheck.fastlyrics.model.SongWithLyrics

@Dao
Expand All @@ -14,6 +15,10 @@ interface SongsDao {
@Query("SELECT * FROM songs WHERE title = :title AND artist = :artist")
fun findSong(title: String, artist: String): SongWithLyrics?


@Query("SELECT * FROM songs WHERE title = :title AND artist = :artist AND type = :type")
fun findSong(title: String, artist: String, type: LyricsType): SongWithLyrics?

@Query("SELECT * FROM songs WHERE id = :id")
fun getSong(id: Long): SongWithLyrics?

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import com.squareup.picasso.Picasso
import dev.forkhandles.result4k.Failure
import dev.forkhandles.result4k.Success
import dev.forkhandles.result4k.Result
import io.github.teccheck.fastlyrics.R
import io.github.teccheck.fastlyrics.Settings
import io.github.teccheck.fastlyrics.api.provider.LyricsProvider
Expand Down Expand Up @@ -58,10 +59,23 @@ class FastLyricsFragment : Fragment() {
}

lyricsViewModel.songWithLyrics.observe(viewLifecycleOwner) { result ->
binding.header.syncedLyricsSwitch.isChecked = false
binding.refreshLayout.isRefreshing = false
displayResult(result)
}

lyricsViewModel.songWithLyricsSynced.observe(viewLifecycleOwner) { result ->
binding.refreshLayout.isRefreshing = false

when (result) {
is Success -> displaySongWithLyrics(result.value)
is Failure -> displayError(result.reason)
is Success -> {
lyricsViewModel.syncedLyricsAvailable = true
binding.header.syncedLyricsAvailable.visibility = View.VISIBLE
}
is Failure -> {
binding.header.syncedLyricsAvailable.visibility = View.GONE
binding.header.syncedLyricsSwitch.isChecked = false
}
}
}

Expand All @@ -72,6 +86,16 @@ class FastLyricsFragment : Fragment() {
)
binding.refreshLayout.setOnRefreshListener { loadLyricsForCurrentSong() }

binding.header.syncedLyricsSwitch.setOnCheckedChangeListener { _, checked ->
val song = if (checked && lyricsViewModel.syncedLyricsAvailable) {
lyricsViewModel.songWithLyricsSynced.value
} else {
lyricsViewModel.songWithLyrics.value
}

song?.let { displayResult(it) }
}

val notificationAccess =
context?.let { DummyNotificationListenerService.canAccessNotifications(it) } ?: false

Expand Down Expand Up @@ -104,6 +128,13 @@ class FastLyricsFragment : Fragment() {
}
}

private fun displayResult(result: Result<SongWithLyrics, LyricsApiException>) {
when (result) {
is Success -> displaySongWithLyrics(result.value)
is Failure -> displayError(result.reason)
}
}

private fun displaySongMeta(songMeta: SongMeta) {
binding.header.container.visibility = View.VISIBLE
binding.errorView.container.visibility = View.GONE
Expand All @@ -128,7 +159,12 @@ class FastLyricsFragment : Fragment() {
val providerIconRes = Utils.getProviderIconRes(it)!!
val icon = AppCompatResources.getDrawable(requireContext(), providerIconRes)
binding.lyricsView.source.setIconResource(providerIconRes)
binding.lyricsView.textLyricsProvider.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null)
binding.lyricsView.textLyricsProvider.setCompoundDrawablesRelativeWithIntrinsicBounds(
icon,
null,
null,
null
)

val nameRes = Utils.getProviderNameRes(it)!!
val name = getString(nameRes)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,25 @@ class FastLyricsViewModel : ViewModel() {

private val _songMeta = MutableLiveData<Result<SongMeta, LyricsApiException>>()
private val _songWithLyrics = MutableLiveData<Result<SongWithLyrics, LyricsApiException>>()
private val _songWithLyricsSynced = MutableLiveData<Result<SongWithLyrics, LyricsApiException>>()
private val _songPosition = MutableLiveData<Long>()

private var songPositionTimer: Timer? = null

val songMeta: LiveData<Result<SongMeta, LyricsApiException>> = _songMeta
val songWithLyrics: LiveData<Result<SongWithLyrics, LyricsApiException>> = _songWithLyrics
val songWithLyricsSynced: LiveData<Result<SongWithLyrics, LyricsApiException>> = _songWithLyricsSynced
val songPosition: LiveData<Long> = _songPosition

var syncedLyricsAvailable = false
var autoRefresh = false

private val songMetaCallback = MediaSession.SongMetaCallback {
if (!autoRefresh) return@SongMetaCallback

_songMeta.postValue(Success(it))
LyricsApi.getLyricsAsync(it, _songWithLyrics)
loadLyrics(it)

}

fun loadLyricsForCurrentSong(context: Context): Boolean {
Expand All @@ -47,12 +51,18 @@ class FastLyricsViewModel : ViewModel() {
_songMeta.value = songMetaResult

if (songMetaResult is Success) {
LyricsApi.getLyricsAsync(songMetaResult.value, _songWithLyrics)
loadLyrics(songMetaResult.value)
}

return songMetaResult is Success
}

private fun loadLyrics(songMeta: SongMeta) {
syncedLyricsAvailable = false
LyricsApi.getLyricsAsync(songMeta, _songWithLyrics)
LyricsApi.getLyricsAsync(songMeta, _songWithLyricsSynced, true)
}

fun setupSongMetaListener() {
MediaSession.registerSongMetaCallback(songMetaCallback)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import androidx.recyclerview.selection.SelectionTracker
import androidx.recyclerview.widget.RecyclerView
import com.squareup.picasso.Picasso
import io.github.teccheck.fastlyrics.R
import io.github.teccheck.fastlyrics.api.provider.LyricsProvider
import io.github.teccheck.fastlyrics.model.SongWithLyrics
import io.github.teccheck.fastlyrics.utils.Utils

class RecyclerAdapter :
RecyclerView.Adapter<RecyclerAdapter.ViewHolder>() {
Expand All @@ -22,6 +24,7 @@ class RecyclerAdapter :
private val imageArt: ImageView
private val textTitle: TextView
private val textArtist: TextView
private val iconProvider: ImageView
private val selectionIcon: ImageView

private var song: SongWithLyrics? = null
Expand All @@ -30,13 +33,19 @@ class RecyclerAdapter :
imageArt = view.findViewById(R.id.image_song_art)
textTitle = view.findViewById(R.id.text_song_title)
textArtist = view.findViewById(R.id.text_song_artist)
iconProvider = view.findViewById(R.id.provider_icon)
selectionIcon = view.findViewById(R.id.selection_icon)
}

fun bind(song: SongWithLyrics, selected: Boolean) {
this.song = song
textTitle.text = song.title
textArtist.text = song.artist

LyricsProvider.getProviderByName(song.provider)?.let { provider ->
Utils.getProviderIconRes(provider)?.let { iconProvider.setImageResource(it) }
}

Picasso.get().load(song.artUrl).into(imageArt)
selectionIcon.visibility = if (selected) View.VISIBLE else View.GONE
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,15 @@ object Utils {
return when(provider) {
Genius -> R.drawable.genius
Deezer -> R.drawable.deezer
else -> null
else -> R.drawable.fastlyrics
}
}

fun getProviderNameRes(provider: LyricsProvider): Int? {
return when(provider) {
Genius -> R.string.source_genius
Deezer -> R.string.source_deezer
else -> null
else -> R.string.app_name
}
}
}
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/fastlyrics.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#000000">
<path
android:fillColor="#ffffff"
android:pathData="M5,2 L2,22L5,22L6.5,12h8.051L15,9L6.951,9L7.551,5L15.551,5L16,2L8,2ZM19,2 L16.449,19h-6L10,22h8,1L22,2Z" />
</vector>
Loading