Merge pull request #1378 from prathameshmm02/dev

Bug fixes
This commit is contained in:
Daksh P. Jain 2022-06-14 14:24:09 +05:30 committed by GitHub
commit bc39d3a462
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 315 additions and 172 deletions

View file

@ -14,8 +14,8 @@ android {
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
applicationId "code.name.monkey.retromusic" applicationId "code.name.monkey.retromusic"
versionCode 10591 versionCode 10592
versionName '6.0.0-beta' versionName '6.0.1-beta'
buildConfigField("String", "GOOGLE_PLAY_LICENSING_KEY", "\"${getProperty(getProperties('../public.properties'), 'GOOGLE_PLAY_LICENSE_KEY')}\"") buildConfigField("String", "GOOGLE_PLAY_LICENSING_KEY", "\"${getProperty(getProperties('../public.properties'), 'GOOGLE_PLAY_LICENSE_KEY')}\"")
} }
@ -51,7 +51,7 @@ android {
} }
} }
lint { lint {
disable 'MissingTranslation', 'ImpliedQuantity' warning 'MissingTranslation', 'ImpliedQuantity', 'Instantiatable'
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
@ -105,7 +105,7 @@ dependencies {
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version" implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
implementation "androidx.navigation:navigation-ui-ktx:$navigation_version" implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"
def room_version = '2.4.2' def room_version = '2.5.0-alpha02'
implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version" implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version" kapt "androidx.room:room-compiler:$room_version"

View file

@ -13,7 +13,7 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission <uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" /> android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" /> <uses-permission android:name="android.permission.BROADCAST_STICKY" />
@ -34,12 +34,12 @@
android:configChanges="locale|layoutDirection" android:configChanges="locale|layoutDirection"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:restoreAnyVersion="true" android:restoreAnyVersion="true"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.RetroMusic.FollowSystem" android:theme="@style/Theme.RetroMusic.FollowSystem"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
android:requestLegacyExternalStorage="true"
tools:targetApi="m"> tools:targetApi="m">
<activity <activity
android:name=".activities.MainActivity" android:name=".activities.MainActivity"
@ -194,7 +194,7 @@
</provider> </provider>
<receiver <receiver
android:name="androidx.media.session.MediaButtonReceiver" android:name=".service.MediaButtonIntentReceiver"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" /> <action android:name="android.intent.action.MEDIA_BUTTON" />
@ -305,9 +305,6 @@
<intent-filter> <intent-filter>
<action android:name="android.media.browse.MediaBrowserService" /> <action android:name="android.media.browse.MediaBrowserService" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</service> </service>
<meta-data <meta-data

View file

@ -62,6 +62,17 @@
</head> </head>
<body> <body>
<div>
<h5>June 13, 2022</h5>
<h2>v6.0.1<span class="tag"><i>Beta</i></span></h2>
<h3>Fixed</h3>
<ul>
<li>Fixed ChromeCast crash</li>
<li>Fixed Slider crashes</li>
<li>Fixed storage related crashes on Android 10</li>
<li>Fixed CrossFade not working Fade Audio is not working</li>
</ul>
</div>
<div> <div>
<h5>June 7, 2022</h5> <h5>June 7, 2022</h5>
<h2>v6.0.0<span class="tag"><i>Beta</i></span></h2> <h2>v6.0.0<span class="tag"><i>Beta</i></span></h2>

View file

@ -14,12 +14,10 @@
*/ */
package code.name.monkey.retromusic.activities package code.name.monkey.retromusic.activities
import android.animation.ObjectAnimator
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.os.Bundle import android.os.Bundle
import android.view.animation.LinearInterpolator
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
@ -27,7 +25,6 @@ import code.name.monkey.retromusic.databinding.ActivityDriveModeBinding
import code.name.monkey.retromusic.db.toSongEntity import code.name.monkey.retromusic.db.toSongEntity
import code.name.monkey.retromusic.extensions.accentColor import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.extensions.drawAboveSystemBars import code.name.monkey.retromusic.extensions.drawAboveSystemBars
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.glide.BlurTransformation import code.name.monkey.retromusic.glide.BlurTransformation
import code.name.monkey.retromusic.glide.GlideApp import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.RetroGlideExtension
@ -246,12 +243,10 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
} }
override fun onUpdateProgressViews(progress: Int, total: Int) { override fun onUpdateProgressViews(progress: Int, total: Int) {
binding.progressSlider.valueTo = total.toFloat() binding.progressSlider.run {
valueTo = total.toFloat()
val animator = ObjectAnimator.ofFloat(binding.progressSlider, "value", progress.toFloat()) value = progress.toFloat().coerceIn(valueFrom, valueTo)
animator.duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME }
animator.interpolator = LinearInterpolator()
animator.start()
binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())

View file

@ -191,7 +191,7 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventLi
override fun getPermissionsToRequest(): Array<String> { override fun getPermissionsToRequest(): Array<String> {
return mutableListOf(Manifest.permission.READ_EXTERNAL_STORAGE).apply { return mutableListOf(Manifest.permission.READ_EXTERNAL_STORAGE).apply {
if (!VersionUtils.hasQ()) { if (!VersionUtils.hasR()) {
add(Manifest.permission.WRITE_EXTERNAL_STORAGE) add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
} }
}.toTypedArray() }.toTypedArray()

View file

@ -82,9 +82,8 @@ abstract class AbsPlayerControlsFragment(@LayoutRes layout: Int) : AbsMusicServi
if (seekBar == null) { if (seekBar == null) {
progressSlider?.valueTo = total.toFloat() progressSlider?.valueTo = total.toFloat()
if (progress > total) return progressSlider?.value =
progressSlider?.value = progress.toFloat() progress.toFloat().coerceIn(progressSlider?.valueFrom, progressSlider?.valueTo)
} else { } else {
seekBar?.max = total seekBar?.max = total

View file

@ -14,7 +14,6 @@
*/ */
package code.name.monkey.retromusic.fragments.other package code.name.monkey.retromusic.fragments.other
import android.animation.ObjectAnimator
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
@ -23,7 +22,6 @@ import android.text.style.ForegroundColorSpan
import android.view.GestureDetector import android.view.GestureDetector
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.animation.DecelerateInterpolator
import androidx.core.text.toSpannable import androidx.core.text.toSpannable
import androidx.core.view.isVisible import androidx.core.view.isVisible
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
@ -139,10 +137,7 @@ open class MiniPlayerFragment : AbsMusicServiceFragment(R.layout.fragment_mini_p
override fun onUpdateProgressViews(progress: Int, total: Int) { override fun onUpdateProgressViews(progress: Int, total: Int) {
binding.progressBar.max = total binding.progressBar.max = total
val animator = ObjectAnimator.ofInt(binding.progressBar, "progress", progress) binding.progressBar.progress = progress
animator.duration = 1000
animator.interpolator = DecelerateInterpolator()
animator.start()
} }
override fun onResume() { override fun onResume() {

View file

@ -52,7 +52,6 @@ import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import code.name.monkey.retromusic.util.logD import code.name.monkey.retromusic.util.logD
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_player_album_cover), class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_player_album_cover),
ViewPager.OnPageChangeListener, MusicProgressViewUpdateHelper.Callback, ViewPager.OnPageChangeListener, MusicProgressViewUpdateHelper.Callback,
@ -86,22 +85,18 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
} }
private fun updateLyrics() { private fun updateLyrics() {
binding.lyricsView.setLabel(context?.getString(R.string.no_lyrics_found))
val song = MusicPlayerRemote.currentSong val song = MusicPlayerRemote.currentSong
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
val lrcFile = LyricUtil.getSyncedLyricsFile(song) val lrcFile = LyricUtil.getSyncedLyricsFile(song)
if (lrcFile != null) { if (lrcFile != null) {
withContext(Dispatchers.Main) { binding.lyricsView.loadLrc(lrcFile)
binding.lyricsView.loadLrc(lrcFile)
}
} else { } else {
val embeddedLyrics = LyricUtil.getEmbeddedSyncedLyrics(song.data) val embeddedLyrics = LyricUtil.getEmbeddedSyncedLyrics(song.data)
withContext(Dispatchers.Main) { if (embeddedLyrics != null) {
if (embeddedLyrics != null) { binding.lyricsView.loadLrc(embeddedLyrics)
binding.lyricsView.loadLrc(embeddedLyrics) } else {
} else { binding.lyricsView.reset()
binding.lyricsView.reset() binding.lyricsView.setLabel(context?.getString(R.string.no_lyrics_found))
}
} }
} }
} }

View file

@ -35,7 +35,6 @@ import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentCirclePlayerBinding import code.name.monkey.retromusic.databinding.FragmentCirclePlayerBinding
import code.name.monkey.retromusic.extensions.* import code.name.monkey.retromusic.extensions.*
import code.name.monkey.retromusic.fragments.MusicSeekSkipTouchListener import code.name.monkey.retromusic.fragments.MusicSeekSkipTouchListener
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.base.goToAlbum import code.name.monkey.retromusic.fragments.base.goToAlbum
import code.name.monkey.retromusic.fragments.base.goToArtist import code.name.monkey.retromusic.fragments.base.goToArtist
@ -315,16 +314,10 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player),
val progressSlider = binding.progressSlider val progressSlider = binding.progressSlider
progressSlider.valueTo = total.toFloat() progressSlider.valueTo = total.toFloat()
if (isSeeking) { progressSlider.valueTo = total.toFloat()
progressSlider.value = progress.toFloat()
} else { progressSlider.value =
progressAnimator = progress.toFloat().coerceIn(progressSlider.valueFrom, progressSlider.valueTo)
ObjectAnimator.ofFloat(progressSlider, "value", progress.toFloat()).apply {
duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME
interpolator = LinearInterpolator()
start()
}
}
binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())

View file

@ -22,7 +22,6 @@ import android.graphics.PorterDuff
import android.graphics.drawable.AnimatedVectorDrawable import android.graphics.drawable.AnimatedVectorDrawable
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.animation.LinearInterpolator
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
@ -41,7 +40,6 @@ import code.name.monkey.retromusic.adapter.song.PlayingQueueAdapter
import code.name.monkey.retromusic.databinding.FragmentGradientPlayerBinding import code.name.monkey.retromusic.databinding.FragmentGradientPlayerBinding
import code.name.monkey.retromusic.extensions.* import code.name.monkey.retromusic.extensions.*
import code.name.monkey.retromusic.fragments.MusicSeekSkipTouchListener import code.name.monkey.retromusic.fragments.MusicSeekSkipTouchListener
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.base.goToAlbum import code.name.monkey.retromusic.fragments.base.goToAlbum
import code.name.monkey.retromusic.fragments.base.goToArtist import code.name.monkey.retromusic.fragments.base.goToArtist
@ -573,16 +571,9 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
val progressSlider = binding.playbackControlsFragment.progressSlider val progressSlider = binding.playbackControlsFragment.progressSlider
progressSlider.valueTo = total.toFloat() progressSlider.valueTo = total.toFloat()
if (isSeeking) { progressSlider.value =
progressSlider.value = progress.toFloat() progress.toFloat().coerceIn(progressSlider.valueFrom, progressSlider.valueTo)
} else {
progressAnimator =
ObjectAnimator.ofFloat(progressSlider, "value", progress.toFloat()).apply {
duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME
interpolator = LinearInterpolator()
start()
}
}
binding.playbackControlsFragment.songTotalTime.text = binding.playbackControlsFragment.songTotalTime.text =
MusicUtil.getReadableDurationString(total.toLong()) MusicUtil.getReadableDurationString(total.toLong())
binding.playbackControlsFragment.songCurrentProgress.text = binding.playbackControlsFragment.songCurrentProgress.text =

View file

@ -24,6 +24,7 @@ import code.name.monkey.retromusic.interfaces.ICabCallback
import code.name.monkey.retromusic.interfaces.ICabHolder import code.name.monkey.retromusic.interfaces.ICabHolder
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.RetroColorUtil import code.name.monkey.retromusic.util.RetroColorUtil
import code.name.monkey.retromusic.util.ThemedFastScroller
import com.afollestad.materialcab.attached.AttachedCab import com.afollestad.materialcab.attached.AttachedCab
import com.afollestad.materialcab.attached.destroy import com.afollestad.materialcab.attached.destroy
import com.afollestad.materialcab.attached.isActive import com.afollestad.materialcab.attached.isActive
@ -109,6 +110,7 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
binding.recyclerView.apply { binding.recyclerView.apply {
layoutManager = LinearLayoutManager(requireContext()) layoutManager = LinearLayoutManager(requireContext())
binding.recyclerView.adapter = wrappedAdapter binding.recyclerView.adapter = wrappedAdapter
ThemedFastScroller.create(this)
} }
playlistSongAdapter.registerAdapterDataObserver(object : playlistSongAdapter.registerAdapterDataObserver(object :
RecyclerView.AdapterDataObserver() { RecyclerView.AdapterDataObserver() {

View file

@ -24,9 +24,10 @@ class MusicProgressViewUpdateHelper : Handler {
private var callback: Callback? = null private var callback: Callback? = null
private var intervalPlaying: Int = 0 private var intervalPlaying: Int = 0
private var intervalPaused: Int = 0 private var intervalPaused: Int = 0
private var firstUpdate = true
fun start() { fun start() {
queueNextRefresh(1) queueNextRefresh(refreshProgressViews().toLong())
} }
fun stop() { fun stop() {
@ -59,10 +60,11 @@ class MusicProgressViewUpdateHelper : Handler {
private fun refreshProgressViews(): Int { private fun refreshProgressViews(): Int {
val progressMillis = MusicPlayerRemote.songProgressMillis val progressMillis = MusicPlayerRemote.songProgressMillis
val totalMillis = MusicPlayerRemote.songDurationMillis val totalMillis = MusicPlayerRemote.songDurationMillis
if (totalMillis > 0) if (totalMillis > 0) {
firstUpdate = false
callback?.onUpdateProgressViews(progressMillis, totalMillis) callback?.onUpdateProgressViews(progressMillis, totalMillis)
}
if (!MusicPlayerRemote.isPlaying) { if (!MusicPlayerRemote.isPlaying && !firstUpdate) {
return intervalPaused return intervalPaused
} }
@ -84,7 +86,7 @@ class MusicProgressViewUpdateHelper : Handler {
companion object { companion object {
private const val CMD_REFRESH_PROGRESS_VIEWS = 1 private const val CMD_REFRESH_PROGRESS_VIEWS = 1
private const val MIN_INTERVAL = 20 private const val MIN_INTERVAL = 20
private const val UPDATE_INTERVAL_PLAYING = 1000 private const val UPDATE_INTERVAL_PLAYING = 500
private const val UPDATE_INTERVAL_PAUSED = 500 private const val UPDATE_INTERVAL_PAUSED = 500
} }
} }

View file

@ -10,16 +10,16 @@ import code.name.monkey.retromusic.util.PreferenceUtil
class AudioFader { class AudioFader {
companion object { companion object {
inline fun createFadeAnimator( fun createFadeAnimator(
fadeInMp: MediaPlayer, fadeInMp: MediaPlayer,
fadeOutMp: MediaPlayer, fadeOutMp: MediaPlayer,
crossinline endAction: (animator: Animator) -> Unit, /* Code to run when Animator Ends*/ endAction: (animator: Animator) -> Unit, /* Code to run when Animator Ends*/
): Animator? { ): Animator? {
val duration = PreferenceUtil.crossFadeDuration * 1000 val duration = PreferenceUtil.crossFadeDuration * 1000
if (duration == 0) { if (duration == 0) {
return null return null
} }
return ValueAnimator.ofFloat(1f, 0f).apply { return ValueAnimator.ofFloat(0f, 1f).apply {
this.duration = duration.toLong() this.duration = duration.toLong()
addUpdateListener { animation: ValueAnimator -> addUpdateListener { animation: ValueAnimator ->
fadeInMp.setVolume( fadeInMp.setVolume(
@ -34,15 +34,14 @@ class AudioFader {
} }
} }
@JvmStatic
fun startFadeAnimator( fun startFadeAnimator(
playback: Playback, playback: Playback,
fadeIn: Boolean, /* fadeIn -> true fadeOut -> false*/ fadeIn: Boolean, /* fadeIn -> true fadeOut -> false*/
callback: Runnable, /* Code to run when Animator Ends*/ callback: Runnable? = null, /* Code to run when Animator Ends*/
) { ) {
val duration = PreferenceUtil.audioFadeDuration.toLong() val duration = PreferenceUtil.audioFadeDuration.toLong()
if (duration == 0L) { if (duration == 0L) {
callback.run() callback?.run()
return return
} }
val startValue = if (fadeIn) 0f else 1.0f val startValue = if (fadeIn) 0f else 1.0f
@ -50,12 +49,10 @@ class AudioFader {
val animator = ValueAnimator.ofFloat(startValue, endValue) val animator = ValueAnimator.ofFloat(startValue, endValue)
animator.duration = duration animator.duration = duration
animator.addUpdateListener { animation: ValueAnimator -> animator.addUpdateListener { animation: ValueAnimator ->
playback.setVolume( playback.setVolume(animation.animatedValue as Float)
animation.animatedValue as Float
)
} }
animator.doOnEnd { animator.doOnEnd {
callback.run() callback?.run()
} }
animator.start() animator.start()
} }

View file

@ -10,8 +10,7 @@ import com.google.android.gms.cast.MediaStatus
import com.google.android.gms.cast.framework.CastSession import com.google.android.gms.cast.framework.CastSession
import com.google.android.gms.cast.framework.media.RemoteMediaClient import com.google.android.gms.cast.framework.media.RemoteMediaClient
class CastPlayer(castSession: CastSession) : Playback, class CastPlayer(castSession: CastSession) : Playback, RemoteMediaClient.Callback() {
RemoteMediaClient.Callback() {
override val isInitialized: Boolean = true override val isInitialized: Boolean = true

View file

@ -37,7 +37,7 @@ class CrossFadePlayer(context: Context) : LocalPlayback(context) {
private var crossFadeAnimator: Animator? = null private var crossFadeAnimator: Animator? = null
override var callbacks: PlaybackCallbacks? = null override var callbacks: PlaybackCallbacks? = null
private var crossFadeDuration = PreferenceUtil.crossFadeDuration private var crossFadeDuration = PreferenceUtil.crossFadeDuration
private var isCrossFading = false var isCrossFading = false
init { init {
player1.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK) player1.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK)
@ -303,7 +303,7 @@ class CrossFadePlayer(context: Context) : LocalPlayback(context) {
private fun switchPlayer() { private fun switchPlayer() {
getNextPlayer()?.start() getNextPlayer()?.start()
crossFade(getCurrentPlayer()!!, getNextPlayer()!!) crossFade(getNextPlayer()!!, getCurrentPlayer()!!)
currentPlayer = currentPlayer =
if (currentPlayer == CurrentPlayer.PLAYER_ONE || currentPlayer == CurrentPlayer.NOT_SET) { if (currentPlayer == CurrentPlayer.PLAYER_ONE || currentPlayer == CurrentPlayer.NOT_SET) {
CurrentPlayer.PLAYER_TWO CurrentPlayer.PLAYER_TWO

View file

@ -0,0 +1,201 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* 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.service
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Handler
import android.os.Message
import android.os.PowerManager
import android.os.PowerManager.WakeLock
import android.util.Log
import android.view.KeyEvent
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.media.session.MediaButtonReceiver
import code.name.monkey.retromusic.BuildConfig
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_PAUSE
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_PLAY
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_REWIND
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_SKIP
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_STOP
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE
/**
* Used to control headset playback.
* Single press: pause/resume
* Double press: actionNext track
* Triple press: previous track
*/
class MediaButtonIntentReceiver : MediaButtonReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (DEBUG) Log.v(TAG, "Received intent: $intent")
if (handleIntent(context, intent) && isOrderedBroadcast) {
abortBroadcast()
}
}
companion object {
val TAG: String = MediaButtonIntentReceiver::class.java.simpleName
private val DEBUG = BuildConfig.DEBUG
private const val MSG_HEADSET_DOUBLE_CLICK_TIMEOUT = 2
private const val DOUBLE_CLICK = 400
private var wakeLock: WakeLock? = null
private var mClickCounter = 0
private var mLastClickTime: Long = 0
@SuppressLint("HandlerLeak") // false alarm, handler is already static
private val mHandler = object : Handler() {
override fun handleMessage(msg: Message) {
when (msg.what) {
MSG_HEADSET_DOUBLE_CLICK_TIMEOUT -> {
val clickCount = msg.arg1
if (DEBUG) Log.v(TAG, "Handling headset click, count = $clickCount")
val command = when (clickCount) {
1 -> ACTION_TOGGLE_PAUSE
2 -> ACTION_SKIP
3 -> ACTION_REWIND
else -> null
}
if (command != null) {
val context = msg.obj as Context
startService(context, command)
}
}
}
releaseWakeLockIfHandlerIdle()
}
}
fun handleIntent(context: Context, intent: Intent): Boolean {
println("Intent Action: ${intent.action}")
val intentAction = intent.action
if (Intent.ACTION_MEDIA_BUTTON == intentAction) {
val event = intent.getParcelableExtra<KeyEvent>(Intent.EXTRA_KEY_EVENT)
?: return false
val keycode = event.keyCode
val action = event.action
val eventTime = if (event.eventTime != 0L)
event.eventTime
else
System.currentTimeMillis()
var command: String? = null
when (keycode) {
KeyEvent.KEYCODE_MEDIA_STOP -> command = ACTION_STOP
KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> command =
ACTION_TOGGLE_PAUSE
KeyEvent.KEYCODE_MEDIA_NEXT -> command = ACTION_SKIP
KeyEvent.KEYCODE_MEDIA_PREVIOUS -> command = ACTION_REWIND
KeyEvent.KEYCODE_MEDIA_PAUSE -> command = ACTION_PAUSE
KeyEvent.KEYCODE_MEDIA_PLAY -> command = ACTION_PLAY
}
if (command != null) {
if (action == KeyEvent.ACTION_DOWN) {
if (event.repeatCount == 0) {
// Only consider the first event in a sequence, not the repeat events,
// so that we don't trigger in cases where the first event went to
// a different app (e.g. when the user ends a phone call by
// long pressing the headset button)
// The service may or may not be running, but we need to send it
// a command.
if (keycode == KeyEvent.KEYCODE_HEADSETHOOK || keycode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
if (eventTime - mLastClickTime >= DOUBLE_CLICK) {
mClickCounter = 0
}
mClickCounter++
if (DEBUG) Log.v(TAG, "Got headset click, count = $mClickCounter")
mHandler.removeMessages(MSG_HEADSET_DOUBLE_CLICK_TIMEOUT)
val msg = mHandler.obtainMessage(
MSG_HEADSET_DOUBLE_CLICK_TIMEOUT, mClickCounter, 0, context
)
val delay = (if (mClickCounter < 3) DOUBLE_CLICK else 0).toLong()
if (mClickCounter >= 3) {
mClickCounter = 0
}
mLastClickTime = eventTime
acquireWakeLockAndSendMessage(context, msg, delay)
} else {
startService(context, command)
}
return true
}
}
}
}
return false
}
private fun startService(context: Context, command: String?) {
val intent = Intent(context, MusicService::class.java)
intent.action = command
try {
// IMPORTANT NOTE: (kind of a hack)
// on Android O and above the following crashes when the app is not running
// there is no good way to check whether the app is running so we catch the exception
// we do not always want to use startForegroundService() because then one gets an ANR
// if no notification is displayed via startForeground()
// according to Play analytics this happens a lot, I suppose for example if command = PAUSE
context.startService(intent)
} catch (ignored: IllegalStateException) {
ContextCompat.startForegroundService(context, intent)
}
}
private fun acquireWakeLockAndSendMessage(context: Context, msg: Message, delay: Long) {
if (wakeLock == null) {
val appContext = context.applicationContext
val pm = appContext.getSystemService<PowerManager>()
wakeLock = pm?.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK,
"RetroMusicApp:Wakelock headset button"
)
wakeLock!!.setReferenceCounted(false)
}
if (DEBUG) Log.v(TAG, "Acquiring wake lock and sending " + msg.what)
// Make sure we don't indefinitely hold the wake lock under any circumstances
wakeLock!!.acquire(10000)
mHandler.sendMessageDelayed(msg, delay)
}
private fun releaseWakeLockIfHandlerIdle() {
if (mHandler.hasMessages(MSG_HEADSET_DOUBLE_CLICK_TIMEOUT)) {
if (DEBUG) Log.v(TAG, "Handler still has messages pending, not releasing wake lock")
return
}
if (wakeLock != null) {
if (DEBUG) Log.v(TAG, "Releasing wake lock")
wakeLock!!.release()
wakeLock = null
}
}
}
}

View file

@ -15,6 +15,7 @@ package code.name.monkey.retromusic.service
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.NotificationManager import android.app.NotificationManager
import android.app.PendingIntent
import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetManager
import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothDevice.EXTRA_DEVICE import android.bluetooth.BluetoothDevice.EXTRA_DEVICE
@ -39,7 +40,6 @@ import android.widget.Toast
import androidx.core.content.edit import androidx.core.content.edit
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.media.MediaBrowserServiceCompat import androidx.media.MediaBrowserServiceCompat
import androidx.media.session.MediaButtonReceiver.handleIntent
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import code.name.monkey.appthemehelper.util.VersionUtils import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.* import code.name.monkey.retromusic.*
@ -94,6 +94,7 @@ import kotlinx.coroutines.Dispatchers.Main
import org.koin.java.KoinJavaComponent.get import org.koin.java.KoinJavaComponent.get
import java.util.* import java.util.*
/** /**
* @author Karim Abou Zeid (kabouzeid), Andrew Neal. Modified by Prathamesh More * @author Karim Abou Zeid (kabouzeid), Andrew Neal. Modified by Prathamesh More
*/ */
@ -203,8 +204,7 @@ class MusicService : MediaBrowserServiceCompat(),
} }
} }
} }
private var queueSaveHandler: QueueSaveHandler? = null
private var queueSaveHandlerThread: HandlerThread? = null
private var queuesRestored = false private var queuesRestored = false
var repeatMode = 0 var repeatMode = 0
@ -277,12 +277,6 @@ class MusicService : MediaBrowserServiceCompat(),
playbackManager.setCallbacks(this) playbackManager.setCallbacks(this)
setupMediaSession() setupMediaSession()
// queue saving needs to run on a separate thread so that it doesn't block the playback handler
// events
queueSaveHandlerThread =
HandlerThread("QueueSaveHandler", Process.THREAD_PRIORITY_BACKGROUND)
queueSaveHandlerThread?.start()
queueSaveHandler = QueueSaveHandler(this, queueSaveHandlerThread!!.looper)
uiThreadHandler = Handler(Looper.getMainLooper()) uiThreadHandler = Handler(Looper.getMainLooper())
registerReceiver(widgetIntentReceiver, IntentFilter(APP_WIDGET_UPDATE)) registerReceiver(widgetIntentReceiver, IntentFilter(APP_WIDGET_UPDATE))
registerReceiver(updateFavoriteReceiver, IntentFilter(FAVORITE_STATE_CHANGED)) registerReceiver(updateFavoriteReceiver, IntentFilter(FAVORITE_STATE_CHANGED))
@ -291,7 +285,7 @@ class MusicService : MediaBrowserServiceCompat(),
notificationManager = getSystemService() notificationManager = getSystemService()
initNotification() initNotification()
mediaStoreObserver = MediaStoreObserver(this, playerHandler!!) mediaStoreObserver = MediaStoreObserver(this, playerHandler!!)
throttledSeekHandler = ThrottledSeekHandler(this, playerHandler!!) throttledSeekHandler = ThrottledSeekHandler(this, Handler(mainLooper))
contentResolver.registerContentObserver(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, contentResolver.registerContentObserver(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
true, true,
mediaStoreObserver) mediaStoreObserver)
@ -656,7 +650,6 @@ class MusicService : MediaBrowserServiceCompat(),
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent != null && intent.action != null) { if (intent != null && intent.action != null) {
handleIntent(mediaSession, intent)
serviceScope.launch { serviceScope.launch {
restoreQueuesAndPositionIfNecessary() restoreQueuesAndPositionIfNecessary()
when (intent.action) { when (intent.action) {
@ -761,11 +754,11 @@ class MusicService : MediaBrowserServiceCompat(),
this.position = position this.position = position
openCurrent { success -> openCurrent { success ->
completion(success) completion(success)
notifyChange(META_CHANGED)
notHandledMetaChangedForCurrentTrack = false
if (success) { if (success) {
prepareNextImpl() prepareNextImpl()
} }
notifyChange(META_CHANGED)
notHandledMetaChangedForCurrentTrack = false
} }
} }
@ -777,11 +770,10 @@ class MusicService : MediaBrowserServiceCompat(),
@Synchronized @Synchronized
fun play() { fun play() {
playbackManager.play(onNotInitialized = { playSongAt(getPosition()) }) { playbackManager.play { playSongAt(getPosition()) }
if (notHandledMetaChangedForCurrentTrack) { if (notHandledMetaChangedForCurrentTrack) {
handleChangeInternal(META_CHANGED) handleChangeInternal(META_CHANGED)
notHandledMetaChangedForCurrentTrack = false notHandledMetaChangedForCurrentTrack = false
}
} }
notifyChange(PLAY_STATE_CHANGED) notifyChange(PLAY_STATE_CHANGED)
} }
@ -798,12 +790,14 @@ class MusicService : MediaBrowserServiceCompat(),
// Every chromecast method needs to run on main thread or you are greeted with IllegalStateException // Every chromecast method needs to run on main thread or you are greeted with IllegalStateException
// So it will use Main dispatcher // So it will use Main dispatcher
// And by using Default dispatcher for local playback we are reduce the burden of main thread // And by using Default dispatcher for local playback we are reduce the burden of main thread
serviceScope.launch(if(playbackManager.isLocalPlayback) Default else Main) { serviceScope.launch(if (playbackManager.isLocalPlayback) Default else Main) {
openTrackAndPrepareNextAt(position) { success -> openTrackAndPrepareNextAt(position) { success ->
if (success) { if (success) {
play() play()
} else { } else {
showToast(resources.getString(R.string.unplayable_file)) runOnUiThread {
showToast(R.string.unplayable_file)
}
} }
} }
} }
@ -953,17 +947,6 @@ class MusicService : MediaBrowserServiceCompat(),
} }
} }
fun saveQueuesImpl() {
MusicPlaybackQueueStore.getInstance(this).saveQueues(playingQueue, originalPlayingQueue)
}
fun saveState() {
saveQueues()
savePosition()
savePositionInTrack()
storage.saveSong(currentSong)
}
@Synchronized @Synchronized
fun seek(millis: Int): Int { fun seek(millis: Int): Int {
return try { return try {
@ -1104,9 +1087,9 @@ class MusicService : MediaBrowserServiceCompat(),
// We must call updateMediaSessionPlaybackState after the load of album art is completed // We must call updateMediaSessionPlaybackState after the load of album art is completed
// if we are loading it or it won't be updated in the notification // if we are loading it or it won't be updated in the notification
updateMediaSessionMetaData(::updateMediaSessionPlaybackState) updateMediaSessionMetaData(::updateMediaSessionPlaybackState)
savePosition()
savePositionInTrack()
serviceScope.launch(IO) { serviceScope.launch(IO) {
savePosition()
savePositionInTrack()
val currentSong = currentSong val currentSong = currentSong
HistoryStore.getInstance(this@MusicService).addSongId(currentSong.id) HistoryStore.getInstance(this@MusicService).addSongId(currentSong.id)
if (songPlayCountHelper.shouldBumpPlayCount()) { if (songPlayCountHelper.shouldBumpPlayCount()) {
@ -1114,13 +1097,14 @@ class MusicService : MediaBrowserServiceCompat(),
.bumpPlayCount(songPlayCountHelper.song.id) .bumpPlayCount(songPlayCountHelper.song.id)
} }
songPlayCountHelper.notifySongChanged(currentSong) songPlayCountHelper.notifySongChanged(currentSong)
storage.saveSong(currentSong)
} }
} }
QUEUE_CHANGED -> { QUEUE_CHANGED -> {
mediaSession?.setQueueTitle(getString(R.string.now_playing_queue)) mediaSession?.setQueueTitle(getString(R.string.now_playing_queue))
mediaSession?.setQueue(playingQueue.toMediaSessionQueue()) mediaSession?.setQueue(playingQueue.toMediaSessionQueue())
updateMediaSessionMetaData(::updateMediaSessionPlaybackState) // because playing queue size might have changed updateMediaSessionMetaData(::updateMediaSessionPlaybackState) // because playing queue size might have changed
saveState() saveQueues()
if (playingQueue.size > 0) { if (playingQueue.size > 0) {
prepareNext() prepareNext()
} else { } else {
@ -1198,6 +1182,8 @@ class MusicService : MediaBrowserServiceCompat(),
seek(progress) seek(progress)
if (wasPlaying) { if (wasPlaying) {
play() play()
} else {
pause()
} }
} }
} }
@ -1218,10 +1204,14 @@ class MusicService : MediaBrowserServiceCompat(),
openQueue(playlistSongs, 0, true) openQueue(playlistSongs, 0, true)
} }
} else { } else {
showToast(R.string.playlist_is_empty, Toast.LENGTH_LONG) runOnUiThread {
showToast(R.string.playlist_is_empty, Toast.LENGTH_LONG)
}
} }
} else { } else {
showToast(R.string.playlist_is_empty, Toast.LENGTH_LONG) runOnUiThread {
showToast(R.string.playlist_is_empty, Toast.LENGTH_LONG)
}
} }
} }
@ -1247,8 +1237,6 @@ class MusicService : MediaBrowserServiceCompat(),
private fun releaseResources() { private fun releaseResources() {
playerHandler?.removeCallbacksAndMessages(null) playerHandler?.removeCallbacksAndMessages(null)
musicPlayerHandlerThread?.quitSafely() musicPlayerHandlerThread?.quitSafely()
queueSaveHandler?.removeCallbacksAndMessages(null)
queueSaveHandlerThread?.quitSafely()
playbackManager.release() playbackManager.release()
mediaSession?.release() mediaSession?.release()
} }
@ -1275,8 +1263,10 @@ class MusicService : MediaBrowserServiceCompat(),
} }
private fun saveQueues() { private fun saveQueues() {
queueSaveHandler?.removeMessages(SAVE_QUEUES) serviceScope.launch(IO) {
queueSaveHandler?.sendEmptyMessage(SAVE_QUEUES) MusicPlaybackQueueStore.getInstance(this@MusicService)
.saveQueues(playingQueue, originalPlayingQueue)
}
} }
private fun sendChangeInternal(what: String) { private fun sendChangeInternal(what: String) {
@ -1314,13 +1304,25 @@ class MusicService : MediaBrowserServiceCompat(),
} }
private fun setupMediaSession() { private fun setupMediaSession() {
val mediaButtonReceiverComponentName = ComponentName(applicationContext,
MediaButtonIntentReceiver::class.java)
val mediaButtonIntent = Intent(Intent.ACTION_MEDIA_BUTTON)
mediaButtonIntent.component = mediaButtonReceiverComponentName
val mediaButtonReceiverPendingIntent = PendingIntent.getBroadcast(
applicationContext, 0, mediaButtonIntent,
if (VersionUtils.hasMarshmallow()) PendingIntent.FLAG_IMMUTABLE else 0
)
mediaSession = MediaSessionCompat( mediaSession = MediaSessionCompat(
this, this,
"RetroMusicPlayer" BuildConfig.APPLICATION_ID,
mediaButtonReceiverComponentName,
mediaButtonReceiverPendingIntent
) )
val mediaSessionCallback = MediaSessionCallback(this) val mediaSessionCallback = MediaSessionCallback(this)
mediaSession?.setCallback(mediaSessionCallback) mediaSession?.setCallback(mediaSessionCallback)
mediaSession?.isActive = true mediaSession?.isActive = true
mediaSession?.setMediaButtonReceiver(mediaButtonReceiverPendingIntent)
} }
inner class MusicBinder : Binder() { inner class MusicBinder : Binder() {
@ -1368,7 +1370,6 @@ class MusicService : MediaBrowserServiceCompat(),
const val REPEAT_MODE_NONE = 0 const val REPEAT_MODE_NONE = 0
const val REPEAT_MODE_ALL = 1 const val REPEAT_MODE_ALL = 1
const val REPEAT_MODE_THIS = 2 const val REPEAT_MODE_THIS = 2
const val SAVE_QUEUES = 0
private const val MEDIA_SESSION_ACTIONS = (PlaybackStateCompat.ACTION_PLAY private const val MEDIA_SESSION_ACTIONS = (PlaybackStateCompat.ACTION_PLAY
or PlaybackStateCompat.ACTION_PAUSE or PlaybackStateCompat.ACTION_PAUSE
or PlaybackStateCompat.ACTION_PLAY_PAUSE or PlaybackStateCompat.ACTION_PLAY_PAUSE

View file

@ -16,7 +16,7 @@ class PlaybackManager(val context: Context) {
var playback: Playback? = null var playback: Playback? = null
private var playbackLocation = PlaybackLocation.LOCAL private var playbackLocation = PlaybackLocation.LOCAL
val isLocalPlayback get() = playbackLocation== PlaybackLocation.LOCAL val isLocalPlayback get() = playbackLocation == PlaybackLocation.LOCAL
val audioSessionId: Int val audioSessionId: Int
get() = if (playback != null) { get() = if (playback != null) {
@ -47,16 +47,19 @@ class PlaybackManager(val context: Context) {
playback?.callbacks = callbacks playback?.callbacks = callbacks
} }
fun play(onNotInitialized: () -> Unit = {}, onPlay: () -> Unit = {}) { fun play(onNotInitialized: () -> Unit) {
if (playback != null && !playback!!.isPlaying) { if (playback != null && !playback!!.isPlaying) {
if (!playback!!.isInitialized) { if (!playback!!.isInitialized) {
onNotInitialized() onNotInitialized()
} else { } else {
openAudioEffectSession() openAudioEffectSession()
if (playbackLocation == PlaybackLocation.LOCAL) { if (playbackLocation == PlaybackLocation.LOCAL) {
AudioFader.startFadeAnimator(playback!!, true) { if (playback is CrossFadePlayer) {
// Code when Animator Ends if (!(playback as CrossFadePlayer).isCrossFading) {
onPlay() AudioFader.startFadeAnimator(playback!!, true)
}
} else {
AudioFader.startFadeAnimator(playback!!, true)
} }
} }
if (shouldSetSpeed) { if (shouldSetSpeed) {
@ -168,14 +171,11 @@ class PlaybackManager(val context: Context) {
playback: Playback, playback: Playback,
onChange: (wasPlaying: Boolean, progress: Int) -> Unit, onChange: (wasPlaying: Boolean, progress: Int) -> Unit,
) { ) {
val oldPlayback = playback val oldPlayback = this.playback
val wasPlaying: Boolean = oldPlayback.isPlaying val wasPlaying: Boolean = oldPlayback?.isPlaying == true
val progress: Int = oldPlayback.position() val progress: Int = oldPlayback?.position() ?: 0
this.playback = playback this.playback = playback
oldPlayback?.stop()
oldPlayback.stop()
onChange(wasPlaying, progress) onChange(wasPlaying, progress)
} }

View file

@ -1,35 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* 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.service
import android.os.Handler
import android.os.Looper
import android.os.Message
import code.name.monkey.retromusic.service.MusicService.Companion.SAVE_QUEUES
import java.lang.ref.WeakReference
internal class QueueSaveHandler(
musicService: MusicService,
looper: Looper
) : Handler(looper) {
private val service: WeakReference<MusicService> = WeakReference(musicService)
override fun handleMessage(msg: Message) {
val service: MusicService? = service.get()
if (msg.what == SAVE_QUEUES) {
service?.saveQueuesImpl()
}
}
}

View file

@ -2,7 +2,7 @@
buildscript { buildscript {
ext { ext {
kotlin_version = '1.6.21' kotlin_version = '1.7.0'
navigation_version = '2.5.0-rc01' navigation_version = '2.5.0-rc01'
mdc_version = '1.7.0-alpha02' mdc_version = '1.7.0-alpha02'
preference_version = '1.2.0' preference_version = '1.2.0'