PlayerAndroid/app/src/main/java/code/name/monkey/retromusic/fragments/player/PlayerAlbumCoverFragment.kt

329 lines
12 KiB
Kotlin

/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.fragments.player
import android.content.SharedPreferences
import android.os.Bundle
import android.text.TextUtils
import android.view.View
import android.widget.FrameLayout
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import androidx.viewpager.widget.ViewPager
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.SHOW_LYRICS
import code.name.monkey.retromusic.adapter.album.AlbumCoverPagerAdapter
import code.name.monkey.retromusic.adapter.album.AlbumCoverPagerAdapter.AlbumCoverFragment
import code.name.monkey.retromusic.databinding.FragmentPlayerAlbumCoverBinding
import code.name.monkey.retromusic.fragments.NowPlayingScreen.*
import code.name.monkey.retromusic.fragments.base.AbsMusicServiceFragment
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.base.goToLyrics
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.model.lyrics.AbsSynchronizedLyrics
import code.name.monkey.retromusic.model.lyrics.Lyrics
import code.name.monkey.retromusic.transform.CarousalPagerTransformer
import code.name.monkey.retromusic.transform.ParallaxPagerTransformer
import code.name.monkey.retromusic.util.LyricUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jaudiotagger.audio.AudioFileIO
import org.jaudiotagger.audio.exceptions.CannotReadException
import org.jaudiotagger.tag.FieldKey
import java.io.File
import java.io.FileNotFoundException
class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_player_album_cover),
ViewPager.OnPageChangeListener, MusicProgressViewUpdateHelper.Callback,
SharedPreferences.OnSharedPreferenceChangeListener {
private var _binding: FragmentPlayerAlbumCoverBinding? = null
private val binding get() = _binding!!
private var callbacks: Callbacks? = null
private var currentPosition: Int = 0
val viewPager get() = binding.viewPager
private val colorReceiver = object : AlbumCoverFragment.ColorReceiver {
override fun onColorReady(color: MediaNotificationProcessor, request: Int) {
if (currentPosition == request) {
notifyColorChange(color)
}
}
}
private var progressViewUpdateHelper: MusicProgressViewUpdateHelper? = null
private val lyricsLayout: FrameLayout get() = binding.playerLyrics
private val lyricsLine1: TextView get() = binding.playerLyricsLine1
private val lyricsLine2: TextView get() = binding.playerLyricsLine2
var lyrics: Lyrics? = null
fun removeSlideEffect() {
val transformer = ParallaxPagerTransformer(R.id.player_image)
transformer.setSpeed(0.3f)
}
private fun updateLyrics() {
lyrics = null
lifecycleScope.launch(Dispatchers.IO) {
val song = MusicPlayerRemote.currentSong
val lyrics = try {
var lrcFile: File? = null
if (LyricUtil.isLrcOriginalFileExist(song.data)) {
lrcFile = LyricUtil.getLocalLyricOriginalFile(song.data)
} else if (LyricUtil.isLrcFileExist(song.title, song.artistName)) {
lrcFile = LyricUtil.getLocalLyricFile(song.title, song.artistName)
}
val data: String = LyricUtil.getStringFromLrc(lrcFile)
if (!TextUtils.isEmpty(data)) {
Lyrics.parse(song, data)
} else {
// Get Embedded Lyrics and check if they are Synchronized
val embeddedLyrics = try{
AudioFileIO.read(File(song.data)).tagOrCreateDefault.getFirst(FieldKey.LYRICS)
} catch(e: Exception){
null
}
if (AbsSynchronizedLyrics.isSynchronized(embeddedLyrics)) {
Lyrics.parse(song, embeddedLyrics)
} else {
null
}
}
} catch (err: FileNotFoundException) {
null
} catch (e: CannotReadException){
null
}
withContext(Dispatchers.Main) {
this@PlayerAlbumCoverFragment.lyrics = lyrics
}
}
}
override fun onUpdateProgressViews(progress: Int, total: Int) {
if (_binding == null) return
if (!isLyricsLayoutVisible()) {
hideLyricsLayout()
return
}
if (lyrics !is AbsSynchronizedLyrics) return
val synchronizedLyrics = lyrics as AbsSynchronizedLyrics
lyricsLayout.visibility = View.VISIBLE
lyricsLayout.alpha = 1f
val oldLine = lyricsLine2.text.toString()
val line = synchronizedLyrics.getLine(progress)
if (oldLine != line || oldLine.isEmpty()) {
lyricsLine1.text = oldLine
lyricsLine2.text = line
lyricsLine1.visibility = View.VISIBLE
lyricsLine2.visibility = View.VISIBLE
lyricsLine2.measure(
View.MeasureSpec.makeMeasureSpec(
lyricsLine2.measuredWidth,
View.MeasureSpec.EXACTLY
),
View.MeasureSpec.UNSPECIFIED
)
val h: Float = lyricsLine2.measuredHeight.toFloat()
lyricsLine1.alpha = 1f
lyricsLine1.translationY = 0f
lyricsLine1.animate().alpha(0f).translationY(-h).duration =
AbsPlayerFragment.VISIBILITY_ANIM_DURATION
lyricsLine2.alpha = 0f
lyricsLine2.translationY = h
lyricsLine2.animate().alpha(1f).translationY(0f).duration =
AbsPlayerFragment.VISIBILITY_ANIM_DURATION
}
}
private fun isLyricsLayoutVisible(): Boolean {
return lyrics != null && lyrics!!.isSynchronized && lyrics!!.isValid
}
private fun hideLyricsLayout() {
lyricsLayout.animate().alpha(0f).setDuration(AbsPlayerFragment.VISIBILITY_ANIM_DURATION)
.withEndAction {
if (_binding == null) return@withEndAction
lyricsLayout.visibility = View.GONE
lyricsLine1.text = null
lyricsLine2.text = null
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentPlayerAlbumCoverBinding.bind(view)
binding.viewPager.addOnPageChangeListener(this)
val nps = PreferenceUtil.nowPlayingScreen
val metrics = resources.displayMetrics
val ratio = metrics.heightPixels.toFloat() / metrics.widthPixels.toFloat()
if (nps == Full || nps == Classic || nps == Fit || nps == Gradient) {
binding.viewPager.offscreenPageLimit = 2
} else if (PreferenceUtil.isCarouselEffect) {
binding.viewPager.clipToPadding = false
val padding =
if (ratio >= 1.777f) {
40
} else {
100
}
binding.viewPager.setPadding(padding, 0, padding, 0)
binding.viewPager.pageMargin = 0
binding.viewPager.setPageTransformer(false, CarousalPagerTransformer(requireContext()))
} else {
binding.viewPager.offscreenPageLimit = 2
binding.viewPager.setPageTransformer(
true,
PreferenceUtil.albumCoverTransform
)
}
progressViewUpdateHelper = MusicProgressViewUpdateHelper(this, 500, 1000)
// Don't show lyrics container for below conditions
if (!(nps == Circle || nps == Peak || nps == Tiny || !PreferenceUtil.showLyrics)) {
progressViewUpdateHelper?.start()
}
// Go to lyrics activity when clicked lyrics
binding.playerLyricsLine2.setOnClickListener {
goToLyrics(requireActivity())
}
}
override fun onResume() {
super.onResume()
val nps = PreferenceUtil.nowPlayingScreen
// Don't show lyrics container for below conditions
if (nps == Circle || nps == Peak || nps == Tiny || !PreferenceUtil.showLyrics) {
lyricsLayout.isVisible = false
progressViewUpdateHelper?.stop()
} else {
lyricsLayout.isVisible = true
progressViewUpdateHelper?.start()
}
PreferenceManager.getDefaultSharedPreferences(requireContext())
.registerOnSharedPreferenceChangeListener(this)
}
override fun onDestroyView() {
super.onDestroyView()
PreferenceManager.getDefaultSharedPreferences(requireContext())
.unregisterOnSharedPreferenceChangeListener(this)
binding.viewPager.removeOnPageChangeListener(this)
progressViewUpdateHelper?.stop()
_binding = null
}
override fun onServiceConnected() {
updatePlayingQueue()
updateLyrics()
}
override fun onPlayingMetaChanged() {
binding.viewPager.currentItem = MusicPlayerRemote.position
updateLyrics()
}
override fun onQueueChanged() {
updatePlayingQueue()
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
if (key == SHOW_LYRICS) {
if (sharedPreferences.getBoolean(key, false)) {
progressViewUpdateHelper?.start()
lyricsLayout.animate().alpha(1f).duration =
AbsPlayerFragment.VISIBILITY_ANIM_DURATION
binding.playerLyrics.isVisible = true
} else {
progressViewUpdateHelper?.stop()
lyricsLayout.animate().alpha(0f)
.setDuration(AbsPlayerFragment.VISIBILITY_ANIM_DURATION)
.withEndAction {
if (_binding != null) {
binding.playerLyrics.isVisible = false
lyricsLine1.text = null
lyricsLine2.text = null
}
}
}
}
}
private fun updatePlayingQueue() {
binding.viewPager.apply {
adapter = AlbumCoverPagerAdapter(childFragmentManager, MusicPlayerRemote.playingQueue)
adapter?.notifyDataSetChanged()
currentItem = MusicPlayerRemote.position
onPageSelected(MusicPlayerRemote.position)
}
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
}
override fun onPageSelected(position: Int) {
currentPosition = position
if (binding.viewPager.adapter != null) {
(binding.viewPager.adapter as AlbumCoverPagerAdapter).receiveColor(
colorReceiver,
position
)
}
if (position != MusicPlayerRemote.position) {
MusicPlayerRemote.playSongAt(position)
}
}
override fun onPageScrollStateChanged(state: Int) {
}
private fun notifyColorChange(color: MediaNotificationProcessor) {
callbacks?.onColorChanged(color)
}
fun setCallbacks(listener: Callbacks) {
callbacks = listener
}
interface Callbacks {
fun onColorChanged(color: MediaNotificationProcessor)
fun onFavoriteToggled()
}
companion object {
val TAG: String = PlayerAlbumCoverFragment::class.java.simpleName
}
}