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
applicationId "code.name.monkey.retromusic"
versionCode 10591
versionName '6.0.0-beta'
versionCode 10592
versionName '6.0.1-beta'
buildConfigField("String", "GOOGLE_PLAY_LICENSING_KEY", "\"${getProperty(getProperties('../public.properties'), 'GOOGLE_PLAY_LICENSE_KEY')}\"")
}
@ -51,7 +51,7 @@ android {
}
}
lint {
disable 'MissingTranslation', 'ImpliedQuantity'
warning 'MissingTranslation', 'ImpliedQuantity', 'Instantiatable'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
@ -105,7 +105,7 @@ dependencies {
implementation "androidx.navigation:navigation-fragment-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-ktx:$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.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
@ -34,12 +34,12 @@
android:configChanges="locale|layoutDirection"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:restoreAnyVersion="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.RetroMusic.FollowSystem"
android:usesCleartextTraffic="true"
android:requestLegacyExternalStorage="true"
tools:targetApi="m">
<activity
android:name=".activities.MainActivity"
@ -194,7 +194,7 @@
</provider>
<receiver
android:name="androidx.media.session.MediaButtonReceiver"
android:name=".service.MediaButtonIntentReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
@ -305,9 +305,6 @@
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</service>
<meta-data

View file

@ -62,6 +62,17 @@
</head>
<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>
<h5>June 7, 2022</h5>
<h2>v6.0.0<span class="tag"><i>Beta</i></span></h2>

View file

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

View file

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

View file

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

View file

@ -14,7 +14,6 @@
*/
package code.name.monkey.retromusic.fragments.other
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.os.Bundle
@ -23,7 +22,6 @@ import android.text.style.ForegroundColorSpan
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import android.view.animation.DecelerateInterpolator
import androidx.core.text.toSpannable
import androidx.core.view.isVisible
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) {
binding.progressBar.max = total
val animator = ObjectAnimator.ofInt(binding.progressBar, "progress", progress)
animator.duration = 1000
animator.interpolator = DecelerateInterpolator()
animator.start()
binding.progressBar.progress = progress
}
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 kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_player_album_cover),
ViewPager.OnPageChangeListener, MusicProgressViewUpdateHelper.Callback,
@ -86,22 +85,18 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
}
private fun updateLyrics() {
binding.lyricsView.setLabel(context?.getString(R.string.no_lyrics_found))
val song = MusicPlayerRemote.currentSong
lifecycleScope.launch(Dispatchers.IO) {
val lrcFile = LyricUtil.getSyncedLyricsFile(song)
if (lrcFile != null) {
withContext(Dispatchers.Main) {
binding.lyricsView.loadLrc(lrcFile)
}
} else {
val embeddedLyrics = LyricUtil.getEmbeddedSyncedLyrics(song.data)
withContext(Dispatchers.Main) {
if (embeddedLyrics != null) {
binding.lyricsView.loadLrc(embeddedLyrics)
} else {
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.extensions.*
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.goToAlbum
import code.name.monkey.retromusic.fragments.base.goToArtist
@ -315,16 +314,10 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player),
val progressSlider = binding.progressSlider
progressSlider.valueTo = total.toFloat()
if (isSeeking) {
progressSlider.value = progress.toFloat()
} else {
progressAnimator =
ObjectAnimator.ofFloat(progressSlider, "value", progress.toFloat()).apply {
duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME
interpolator = LinearInterpolator()
start()
}
}
progressSlider.valueTo = total.toFloat()
progressSlider.value =
progress.toFloat().coerceIn(progressSlider.valueFrom, progressSlider.valueTo)
binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.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.os.Bundle
import android.view.View
import android.view.animation.LinearInterpolator
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.PopupMenu
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.extensions.*
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.goToAlbum
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
progressSlider.valueTo = total.toFloat()
if (isSeeking) {
progressSlider.value = progress.toFloat()
} else {
progressAnimator =
ObjectAnimator.ofFloat(progressSlider, "value", progress.toFloat()).apply {
duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME
interpolator = LinearInterpolator()
start()
}
}
progressSlider.value =
progress.toFloat().coerceIn(progressSlider.valueFrom, progressSlider.valueTo)
binding.playbackControlsFragment.songTotalTime.text =
MusicUtil.getReadableDurationString(total.toLong())
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.model.Song
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.destroy
import com.afollestad.materialcab.attached.isActive
@ -109,6 +110,7 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
binding.recyclerView.apply {
layoutManager = LinearLayoutManager(requireContext())
binding.recyclerView.adapter = wrappedAdapter
ThemedFastScroller.create(this)
}
playlistSongAdapter.registerAdapterDataObserver(object :
RecyclerView.AdapterDataObserver() {

View file

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

View file

@ -10,16 +10,16 @@ import code.name.monkey.retromusic.util.PreferenceUtil
class AudioFader {
companion object {
inline fun createFadeAnimator(
fun createFadeAnimator(
fadeInMp: 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? {
val duration = PreferenceUtil.crossFadeDuration * 1000
if (duration == 0) {
return null
}
return ValueAnimator.ofFloat(1f, 0f).apply {
return ValueAnimator.ofFloat(0f, 1f).apply {
this.duration = duration.toLong()
addUpdateListener { animation: ValueAnimator ->
fadeInMp.setVolume(
@ -34,15 +34,14 @@ class AudioFader {
}
}
@JvmStatic
fun startFadeAnimator(
playback: Playback,
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()
if (duration == 0L) {
callback.run()
callback?.run()
return
}
val startValue = if (fadeIn) 0f else 1.0f
@ -50,12 +49,10 @@ class AudioFader {
val animator = ValueAnimator.ofFloat(startValue, endValue)
animator.duration = duration
animator.addUpdateListener { animation: ValueAnimator ->
playback.setVolume(
animation.animatedValue as Float
)
playback.setVolume(animation.animatedValue as Float)
}
animator.doOnEnd {
callback.run()
callback?.run()
}
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.media.RemoteMediaClient
class CastPlayer(castSession: CastSession) : Playback,
RemoteMediaClient.Callback() {
class CastPlayer(castSession: CastSession) : Playback, RemoteMediaClient.Callback() {
override val isInitialized: Boolean = true

View file

@ -37,7 +37,7 @@ class CrossFadePlayer(context: Context) : LocalPlayback(context) {
private var crossFadeAnimator: Animator? = null
override var callbacks: PlaybackCallbacks? = null
private var crossFadeDuration = PreferenceUtil.crossFadeDuration
private var isCrossFading = false
var isCrossFading = false
init {
player1.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK)
@ -303,7 +303,7 @@ class CrossFadePlayer(context: Context) : LocalPlayback(context) {
private fun switchPlayer() {
getNextPlayer()?.start()
crossFade(getCurrentPlayer()!!, getNextPlayer()!!)
crossFade(getNextPlayer()!!, getCurrentPlayer()!!)
currentPlayer =
if (currentPlayer == CurrentPlayer.PLAYER_ONE || currentPlayer == CurrentPlayer.NOT_SET) {
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.app.NotificationManager
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothDevice.EXTRA_DEVICE
@ -39,7 +40,6 @@ import android.widget.Toast
import androidx.core.content.edit
import androidx.core.content.getSystemService
import androidx.media.MediaBrowserServiceCompat
import androidx.media.session.MediaButtonReceiver.handleIntent
import androidx.preference.PreferenceManager
import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.*
@ -94,6 +94,7 @@ import kotlinx.coroutines.Dispatchers.Main
import org.koin.java.KoinJavaComponent.get
import java.util.*
/**
* @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
var repeatMode = 0
@ -277,12 +277,6 @@ class MusicService : MediaBrowserServiceCompat(),
playbackManager.setCallbacks(this)
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())
registerReceiver(widgetIntentReceiver, IntentFilter(APP_WIDGET_UPDATE))
registerReceiver(updateFavoriteReceiver, IntentFilter(FAVORITE_STATE_CHANGED))
@ -291,7 +285,7 @@ class MusicService : MediaBrowserServiceCompat(),
notificationManager = getSystemService()
initNotification()
mediaStoreObserver = MediaStoreObserver(this, playerHandler!!)
throttledSeekHandler = ThrottledSeekHandler(this, playerHandler!!)
throttledSeekHandler = ThrottledSeekHandler(this, Handler(mainLooper))
contentResolver.registerContentObserver(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
true,
mediaStoreObserver)
@ -656,7 +650,6 @@ class MusicService : MediaBrowserServiceCompat(),
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent != null && intent.action != null) {
handleIntent(mediaSession, intent)
serviceScope.launch {
restoreQueuesAndPositionIfNecessary()
when (intent.action) {
@ -761,11 +754,11 @@ class MusicService : MediaBrowserServiceCompat(),
this.position = position
openCurrent { success ->
completion(success)
notifyChange(META_CHANGED)
notHandledMetaChangedForCurrentTrack = false
if (success) {
prepareNextImpl()
}
notifyChange(META_CHANGED)
notHandledMetaChangedForCurrentTrack = false
}
}
@ -777,12 +770,11 @@ class MusicService : MediaBrowserServiceCompat(),
@Synchronized
fun play() {
playbackManager.play(onNotInitialized = { playSongAt(getPosition()) }) {
playbackManager.play { playSongAt(getPosition()) }
if (notHandledMetaChangedForCurrentTrack) {
handleChangeInternal(META_CHANGED)
notHandledMetaChangedForCurrentTrack = false
}
}
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
// So it will use Main dispatcher
// 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 ->
if (success) {
play()
} 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
fun seek(millis: Int): Int {
return try {
@ -1104,9 +1087,9 @@ class MusicService : MediaBrowserServiceCompat(),
// 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
updateMediaSessionMetaData(::updateMediaSessionPlaybackState)
serviceScope.launch(IO) {
savePosition()
savePositionInTrack()
serviceScope.launch(IO) {
val currentSong = currentSong
HistoryStore.getInstance(this@MusicService).addSongId(currentSong.id)
if (songPlayCountHelper.shouldBumpPlayCount()) {
@ -1114,13 +1097,14 @@ class MusicService : MediaBrowserServiceCompat(),
.bumpPlayCount(songPlayCountHelper.song.id)
}
songPlayCountHelper.notifySongChanged(currentSong)
storage.saveSong(currentSong)
}
}
QUEUE_CHANGED -> {
mediaSession?.setQueueTitle(getString(R.string.now_playing_queue))
mediaSession?.setQueue(playingQueue.toMediaSessionQueue())
updateMediaSessionMetaData(::updateMediaSessionPlaybackState) // because playing queue size might have changed
saveState()
saveQueues()
if (playingQueue.size > 0) {
prepareNext()
} else {
@ -1198,6 +1182,8 @@ class MusicService : MediaBrowserServiceCompat(),
seek(progress)
if (wasPlaying) {
play()
} else {
pause()
}
}
}
@ -1218,12 +1204,16 @@ class MusicService : MediaBrowserServiceCompat(),
openQueue(playlistSongs, 0, true)
}
} else {
runOnUiThread {
showToast(R.string.playlist_is_empty, Toast.LENGTH_LONG)
}
}
} else {
runOnUiThread {
showToast(R.string.playlist_is_empty, Toast.LENGTH_LONG)
}
}
}
private fun prepareNext() {
prepareNextImpl()
@ -1247,8 +1237,6 @@ class MusicService : MediaBrowserServiceCompat(),
private fun releaseResources() {
playerHandler?.removeCallbacksAndMessages(null)
musicPlayerHandlerThread?.quitSafely()
queueSaveHandler?.removeCallbacksAndMessages(null)
queueSaveHandlerThread?.quitSafely()
playbackManager.release()
mediaSession?.release()
}
@ -1275,8 +1263,10 @@ class MusicService : MediaBrowserServiceCompat(),
}
private fun saveQueues() {
queueSaveHandler?.removeMessages(SAVE_QUEUES)
queueSaveHandler?.sendEmptyMessage(SAVE_QUEUES)
serviceScope.launch(IO) {
MusicPlaybackQueueStore.getInstance(this@MusicService)
.saveQueues(playingQueue, originalPlayingQueue)
}
}
private fun sendChangeInternal(what: String) {
@ -1314,13 +1304,25 @@ class MusicService : MediaBrowserServiceCompat(),
}
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(
this,
"RetroMusicPlayer"
BuildConfig.APPLICATION_ID,
mediaButtonReceiverComponentName,
mediaButtonReceiverPendingIntent
)
val mediaSessionCallback = MediaSessionCallback(this)
mediaSession?.setCallback(mediaSessionCallback)
mediaSession?.isActive = true
mediaSession?.setMediaButtonReceiver(mediaButtonReceiverPendingIntent)
}
inner class MusicBinder : Binder() {
@ -1368,7 +1370,6 @@ class MusicService : MediaBrowserServiceCompat(),
const val REPEAT_MODE_NONE = 0
const val REPEAT_MODE_ALL = 1
const val REPEAT_MODE_THIS = 2
const val SAVE_QUEUES = 0
private const val MEDIA_SESSION_ACTIONS = (PlaybackStateCompat.ACTION_PLAY
or PlaybackStateCompat.ACTION_PAUSE
or PlaybackStateCompat.ACTION_PLAY_PAUSE

View file

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