commit
bc39d3a462
20 changed files with 315 additions and 172 deletions
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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 =
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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'
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue