Use LocalPlayback class to handle headset disconnection (AudioBecomingNoisy) and focus change, somewhat like in Shuttle and relieve PlaybackHandler of it's duties
This commit is contained in:
parent
14f31eae37
commit
1f7a00e999
11 changed files with 195 additions and 235 deletions
|
@ -14,7 +14,7 @@ android {
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
|
||||||
applicationId "code.name.monkey.retromusic"
|
applicationId "code.name.monkey.retromusic"
|
||||||
versionCode 10584
|
versionCode 10585
|
||||||
versionName '5.9.0'
|
versionName '5.9.0'
|
||||||
|
|
||||||
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')}\"")
|
||||||
|
|
|
@ -70,7 +70,8 @@ class App : Application() {
|
||||||
})
|
})
|
||||||
|
|
||||||
// setting Error activity
|
// setting Error activity
|
||||||
CaocConfig.Builder.create().errorActivity(ErrorActivity::class.java).restartActivity(MainActivity::class.java).apply()
|
CaocConfig.Builder.create().errorActivity(ErrorActivity::class.java)
|
||||||
|
.restartActivity(MainActivity::class.java).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTerminate() {
|
override fun onTerminate() {
|
||||||
|
|
|
@ -17,6 +17,7 @@ package code.name.monkey.retromusic.fragments.base
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.annotation.CallSuper
|
||||||
import androidx.annotation.LayoutRes
|
import androidx.annotation.LayoutRes
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.navigation.navOptions
|
import androidx.navigation.navOptions
|
||||||
|
@ -61,12 +62,14 @@ open class AbsMusicServiceFragment(@LayoutRes layout: Int) : Fragment(layout),
|
||||||
serviceActivity = null
|
serviceActivity = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
serviceActivity?.addMusicServiceEventListener(this)
|
serviceActivity?.addMusicServiceEventListener(this)
|
||||||
maybeShowAnnoyingToasts()
|
maybeShowAnnoyingToasts()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
serviceActivity?.removeMusicServiceEventListener(this)
|
serviceActivity?.removeMusicServiceEventListener(this)
|
||||||
|
|
|
@ -31,7 +31,7 @@ class CastPlayer(castSession: CastSession) : Playback,
|
||||||
|
|
||||||
override val audioSessionId: Int = 0
|
override val audioSessionId: Int = 0
|
||||||
|
|
||||||
private var callbacks: Playback.PlaybackCallbacks? = null
|
override var callbacks: Playback.PlaybackCallbacks? = null
|
||||||
|
|
||||||
override fun setDataSource(
|
override fun setDataSource(
|
||||||
song: Song,
|
song: Song,
|
||||||
|
@ -51,10 +51,6 @@ class CastPlayer(castSession: CastSession) : Playback,
|
||||||
|
|
||||||
override fun setNextDataSource(path: String?) {}
|
override fun setNextDataSource(path: String?) {}
|
||||||
|
|
||||||
override fun setCallbacks(callbacks: Playback.PlaybackCallbacks) {
|
|
||||||
this.callbacks = callbacks
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun start(): Boolean {
|
override fun start(): Boolean {
|
||||||
isActuallyPlaying = true
|
isActuallyPlaying = true
|
||||||
remoteMediaClient?.play()
|
remoteMediaClient?.play()
|
||||||
|
|
|
@ -16,7 +16,6 @@ import code.name.monkey.retromusic.extensions.uri
|
||||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||||
import code.name.monkey.retromusic.model.Song
|
import code.name.monkey.retromusic.model.Song
|
||||||
import code.name.monkey.retromusic.service.AudioFader.Companion.createFadeAnimator
|
import code.name.monkey.retromusic.service.AudioFader.Companion.createFadeAnimator
|
||||||
import code.name.monkey.retromusic.service.playback.Playback
|
|
||||||
import code.name.monkey.retromusic.service.playback.Playback.PlaybackCallbacks
|
import code.name.monkey.retromusic.service.playback.Playback.PlaybackCallbacks
|
||||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
@ -30,7 +29,7 @@ import kotlinx.coroutines.*
|
||||||
* play but with decreasing volume and start the player with the next song with increasing volume
|
* play but with decreasing volume and start the player with the next song with increasing volume
|
||||||
* and vice versa for upcoming song and so on.
|
* and vice versa for upcoming song and so on.
|
||||||
*/
|
*/
|
||||||
class CrossFadePlayer(val context: Context) : Playback, MediaPlayer.OnCompletionListener,
|
class CrossFadePlayer(context: Context) : LocalPlayback(context), MediaPlayer.OnCompletionListener,
|
||||||
MediaPlayer.OnErrorListener {
|
MediaPlayer.OnErrorListener {
|
||||||
|
|
||||||
private var currentPlayer: CurrentPlayer = CurrentPlayer.NOT_SET
|
private var currentPlayer: CurrentPlayer = CurrentPlayer.NOT_SET
|
||||||
|
@ -41,7 +40,7 @@ class CrossFadePlayer(val context: Context) : Playback, MediaPlayer.OnCompletion
|
||||||
private var hasDataSource: Boolean = false /* Whether first player has DataSource */
|
private var hasDataSource: Boolean = false /* Whether first player has DataSource */
|
||||||
private var fadeInAnimator: Animator? = null
|
private var fadeInAnimator: Animator? = null
|
||||||
private var fadeOutAnimator: Animator? = null
|
private var fadeOutAnimator: Animator? = null
|
||||||
private var callbacks: PlaybackCallbacks? = null
|
override var callbacks: PlaybackCallbacks? = null
|
||||||
private var crossFadeDuration = PreferenceUtil.crossFadeDuration
|
private var crossFadeDuration = PreferenceUtil.crossFadeDuration
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -51,6 +50,7 @@ class CrossFadePlayer(val context: Context) : Playback, MediaPlayer.OnCompletion
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun start(): Boolean {
|
override fun start(): Boolean {
|
||||||
|
super.start()
|
||||||
durationListener.start()
|
durationListener.start()
|
||||||
return try {
|
return try {
|
||||||
getCurrentPlayer()?.start()
|
getCurrentPlayer()?.start()
|
||||||
|
@ -67,16 +67,14 @@ class CrossFadePlayer(val context: Context) : Playback, MediaPlayer.OnCompletion
|
||||||
durationListener.stop()
|
durationListener.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setCallbacks(callbacks: PlaybackCallbacks) {
|
|
||||||
this.callbacks = callbacks
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun stop() {
|
override fun stop() {
|
||||||
|
super.stop()
|
||||||
getCurrentPlayer()?.reset()
|
getCurrentPlayer()?.reset()
|
||||||
mIsInitialized = false
|
mIsInitialized = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pause(): Boolean {
|
override fun pause(): Boolean {
|
||||||
|
super.pause()
|
||||||
durationListener.stop()
|
durationListener.stop()
|
||||||
cancelFade()
|
cancelFade()
|
||||||
getCurrentPlayer()?.let {
|
getCurrentPlayer()?.let {
|
||||||
|
|
|
@ -0,0 +1,141 @@
|
||||||
|
package code.name.monkey.retromusic.service
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.media.AudioManager
|
||||||
|
import androidx.annotation.CallSuper
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
|
import androidx.media.AudioAttributesCompat
|
||||||
|
import androidx.media.AudioFocusRequestCompat
|
||||||
|
import androidx.media.AudioManagerCompat
|
||||||
|
import code.name.monkey.retromusic.R
|
||||||
|
import code.name.monkey.retromusic.extensions.showToast
|
||||||
|
import code.name.monkey.retromusic.service.playback.Playback
|
||||||
|
import code.name.monkey.retromusic.util.PreferenceUtil.isAudioFocusEnabled
|
||||||
|
|
||||||
|
abstract class LocalPlayback(val context: Context) : Playback {
|
||||||
|
|
||||||
|
private val becomingNoisyReceiverIntentFilter =
|
||||||
|
IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)
|
||||||
|
|
||||||
|
private val audioManager: AudioManager? = context.getSystemService()
|
||||||
|
|
||||||
|
private var becomingNoisyReceiverRegistered = false
|
||||||
|
private val becomingNoisyReceiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
if (intent.action != null
|
||||||
|
&& intent.action == AudioManager.ACTION_AUDIO_BECOMING_NOISY
|
||||||
|
) {
|
||||||
|
val serviceIntent = Intent(context, MusicService::class.java)
|
||||||
|
serviceIntent.action = MusicService.ACTION_PAUSE
|
||||||
|
context.startService(serviceIntent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isPausedByTransientLossOfFocus = false
|
||||||
|
|
||||||
|
private val audioFocusListener = AudioManager.OnAudioFocusChangeListener { focusChange ->
|
||||||
|
when (focusChange) {
|
||||||
|
AudioManager.AUDIOFOCUS_GAIN -> {
|
||||||
|
if (!isPlaying && isPausedByTransientLossOfFocus) {
|
||||||
|
start()
|
||||||
|
callbacks?.onPlayStateChanged()
|
||||||
|
isPausedByTransientLossOfFocus = false
|
||||||
|
}
|
||||||
|
setVolume(Volume.NORMAL)
|
||||||
|
}
|
||||||
|
AudioManager.AUDIOFOCUS_LOSS -> {
|
||||||
|
// Lost focus for an unbounded amount of time: stop playback and release media playback
|
||||||
|
if (!isAudioFocusEnabled) {
|
||||||
|
pause()
|
||||||
|
callbacks?.onPlayStateChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
|
||||||
|
// Lost focus for a short time, but we have to stop
|
||||||
|
// playback. We don't release the media playback because playback
|
||||||
|
// is likely to resume
|
||||||
|
val wasPlaying = isPlaying
|
||||||
|
pause()
|
||||||
|
callbacks?.onPlayStateChanged()
|
||||||
|
isPausedByTransientLossOfFocus = wasPlaying
|
||||||
|
}
|
||||||
|
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
|
||||||
|
// Lost focus for a short time, but it's ok to keep playing
|
||||||
|
// at an attenuated level
|
||||||
|
setVolume(Volume.DUCK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val audioFocusRequest: AudioFocusRequestCompat =
|
||||||
|
AudioFocusRequestCompat.Builder(AudioManagerCompat.AUDIOFOCUS_GAIN)
|
||||||
|
.setOnAudioFocusChangeListener(audioFocusListener)
|
||||||
|
.setAudioAttributes(
|
||||||
|
AudioAttributesCompat.Builder()
|
||||||
|
.setContentType(AudioAttributesCompat.CONTENT_TYPE_MUSIC).build()
|
||||||
|
).build()
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
override fun start(): Boolean {
|
||||||
|
if (!requestFocus()) {
|
||||||
|
context.showToast(R.string.audio_focus_denied)
|
||||||
|
}
|
||||||
|
registerBecomingNoisyReceiver()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
override fun stop() {
|
||||||
|
abandonFocus()
|
||||||
|
unregisterBecomingNoisyReceiver()
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
override fun pause(): Boolean {
|
||||||
|
unregisterBecomingNoisyReceiver()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unregisterBecomingNoisyReceiver() {
|
||||||
|
if (becomingNoisyReceiverRegistered) {
|
||||||
|
context.unregisterReceiver(becomingNoisyReceiver)
|
||||||
|
becomingNoisyReceiverRegistered = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun registerBecomingNoisyReceiver() {
|
||||||
|
if (!becomingNoisyReceiverRegistered) {
|
||||||
|
context.registerReceiver(
|
||||||
|
becomingNoisyReceiver,
|
||||||
|
becomingNoisyReceiverIntentFilter
|
||||||
|
)
|
||||||
|
becomingNoisyReceiverRegistered = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requestFocus(): Boolean {
|
||||||
|
return AudioManagerCompat.requestAudioFocus(
|
||||||
|
audioManager!!,
|
||||||
|
audioFocusRequest
|
||||||
|
) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun abandonFocus() {
|
||||||
|
AudioManagerCompat.abandonAudioFocusRequest(audioManager!!, audioFocusRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
object Volume {
|
||||||
|
/**
|
||||||
|
* The volume we set the media player to when we lose audio focus, but are
|
||||||
|
* allowed to reduce the volume instead of stopping playback.
|
||||||
|
*/
|
||||||
|
const val DUCK = 0.2f
|
||||||
|
|
||||||
|
/** The volume we set the media player when we have audio focus. */
|
||||||
|
const val NORMAL = 1.0f
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,18 +24,17 @@ import code.name.monkey.retromusic.R
|
||||||
import code.name.monkey.retromusic.extensions.showToast
|
import code.name.monkey.retromusic.extensions.showToast
|
||||||
import code.name.monkey.retromusic.extensions.uri
|
import code.name.monkey.retromusic.extensions.uri
|
||||||
import code.name.monkey.retromusic.model.Song
|
import code.name.monkey.retromusic.model.Song
|
||||||
import code.name.monkey.retromusic.service.playback.Playback
|
|
||||||
import code.name.monkey.retromusic.service.playback.Playback.PlaybackCallbacks
|
import code.name.monkey.retromusic.service.playback.Playback.PlaybackCallbacks
|
||||||
import code.name.monkey.retromusic.util.PreferenceUtil.isGapLessPlayback
|
import code.name.monkey.retromusic.util.PreferenceUtil.isGapLessPlayback
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Andrew Neal, Karim Abou Zeid (kabouzeid)
|
* @author Andrew Neal, Karim Abou Zeid (kabouzeid)
|
||||||
*/
|
*/
|
||||||
class MultiPlayer internal constructor(private val context: Context) : Playback,
|
class MultiPlayer(context: Context) : LocalPlayback(context),
|
||||||
MediaPlayer.OnErrorListener, OnCompletionListener {
|
MediaPlayer.OnErrorListener, OnCompletionListener {
|
||||||
private var mCurrentMediaPlayer = MediaPlayer()
|
private var mCurrentMediaPlayer = MediaPlayer()
|
||||||
private var mNextMediaPlayer: MediaPlayer? = null
|
private var mNextMediaPlayer: MediaPlayer? = null
|
||||||
private var callbacks: PlaybackCallbacks? = null
|
override var callbacks: PlaybackCallbacks? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return True if the player is ready to go, false otherwise
|
* @return True if the player is ready to go, false otherwise
|
||||||
|
@ -154,19 +153,11 @@ class MultiPlayer internal constructor(private val context: Context) : Playback,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the callbacks
|
|
||||||
*
|
|
||||||
* @param callbacks The callbacks to use
|
|
||||||
*/
|
|
||||||
override fun setCallbacks(callbacks: PlaybackCallbacks) {
|
|
||||||
this.callbacks = callbacks
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts or resumes playback.
|
* Starts or resumes playback.
|
||||||
*/
|
*/
|
||||||
override fun start(): Boolean {
|
override fun start(): Boolean {
|
||||||
|
super.start()
|
||||||
return try {
|
return try {
|
||||||
mCurrentMediaPlayer.start()
|
mCurrentMediaPlayer.start()
|
||||||
true
|
true
|
||||||
|
@ -179,6 +170,7 @@ class MultiPlayer internal constructor(private val context: Context) : Playback,
|
||||||
* Resets the MediaPlayer to its uninitialized state.
|
* Resets the MediaPlayer to its uninitialized state.
|
||||||
*/
|
*/
|
||||||
override fun stop() {
|
override fun stop() {
|
||||||
|
super.stop()
|
||||||
mCurrentMediaPlayer.reset()
|
mCurrentMediaPlayer.reset()
|
||||||
isInitialized = false
|
isInitialized = false
|
||||||
}
|
}
|
||||||
|
@ -198,6 +190,7 @@ class MultiPlayer internal constructor(private val context: Context) : Playback,
|
||||||
* Pauses playback. Call start() to resume.
|
* Pauses playback. Call start() to resume.
|
||||||
*/
|
*/
|
||||||
override fun pause(): Boolean {
|
override fun pause(): Boolean {
|
||||||
|
super.pause()
|
||||||
return try {
|
return try {
|
||||||
mCurrentMediaPlayer.pause()
|
mCurrentMediaPlayer.pause()
|
||||||
true
|
true
|
||||||
|
|
|
@ -25,7 +25,6 @@ import android.database.ContentObserver
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
import android.media.AudioManager.OnAudioFocusChangeListener
|
|
||||||
import android.os.*
|
import android.os.*
|
||||||
import android.os.Build.VERSION
|
import android.os.Build.VERSION
|
||||||
import android.os.Build.VERSION_CODES
|
import android.os.Build.VERSION_CODES
|
||||||
|
@ -40,10 +39,6 @@ import android.util.Log
|
||||||
import android.widget.Toast
|
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.AudioAttributesCompat
|
|
||||||
import androidx.media.AudioAttributesCompat.CONTENT_TYPE_MUSIC
|
|
||||||
import androidx.media.AudioFocusRequestCompat
|
|
||||||
import androidx.media.AudioManagerCompat
|
|
||||||
import androidx.media.MediaBrowserServiceCompat
|
import androidx.media.MediaBrowserServiceCompat
|
||||||
import androidx.media.session.MediaButtonReceiver.handleIntent
|
import androidx.media.session.MediaButtonReceiver.handleIntent
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
@ -162,17 +157,7 @@ class MusicService : MediaBrowserServiceCompat(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private var audioManager: AudioManager? = null
|
|
||||||
get() {
|
|
||||||
if (field == null) {
|
|
||||||
field = getSystemService()
|
|
||||||
}
|
|
||||||
return field
|
|
||||||
}
|
|
||||||
|
|
||||||
private val becomingNoisyReceiverIntentFilter =
|
|
||||||
IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)
|
|
||||||
private var becomingNoisyReceiverRegistered = false
|
|
||||||
private val bluetoothConnectedIntentFilter = IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED)
|
private val bluetoothConnectedIntentFilter = IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED)
|
||||||
private var bluetoothConnectedRegistered = false
|
private var bluetoothConnectedRegistered = false
|
||||||
private val headsetReceiverIntentFilter = IntentFilter(Intent.ACTION_HEADSET_PLUG)
|
private val headsetReceiverIntentFilter = IntentFilter(Intent.ACTION_HEADSET_PLUG)
|
||||||
|
@ -185,20 +170,9 @@ class MusicService : MediaBrowserServiceCompat(),
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
var playingQueue = ArrayList<Song>()
|
var playingQueue = ArrayList<Song>()
|
||||||
var isPausedByTransientLossOfFocus = false
|
|
||||||
private val becomingNoisyReceiver = object : BroadcastReceiver() {
|
private var playerHandler: Handler? = null
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
|
||||||
if (intent.action != null
|
|
||||||
&& intent.action == AudioManager.ACTION_AUDIO_BECOMING_NOISY
|
|
||||||
) {
|
|
||||||
pause()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private var playerHandler: PlaybackHandler? = null
|
|
||||||
private val audioFocusListener = OnAudioFocusChangeListener { focusChange ->
|
|
||||||
playerHandler?.obtainMessage(FOCUS_CHANGE, focusChange, 0)?.sendToTarget()
|
|
||||||
}
|
|
||||||
private var playingNotification: PlayingNotification? = null
|
private var playingNotification: PlayingNotification? = null
|
||||||
|
|
||||||
private val updateFavoriteReceiver = object : BroadcastReceiver() {
|
private val updateFavoriteReceiver = object : BroadcastReceiver() {
|
||||||
|
@ -288,7 +262,7 @@ class MusicService : MediaBrowserServiceCompat(),
|
||||||
wakeLock?.setReferenceCounted(false)
|
wakeLock?.setReferenceCounted(false)
|
||||||
musicPlayerHandlerThread = HandlerThread("PlaybackHandler")
|
musicPlayerHandlerThread = HandlerThread("PlaybackHandler")
|
||||||
musicPlayerHandlerThread?.start()
|
musicPlayerHandlerThread?.start()
|
||||||
playerHandler = PlaybackHandler(this, mainLooper)
|
playerHandler = Handler(musicPlayerHandlerThread!!.looper)
|
||||||
|
|
||||||
playbackManager = PlaybackManager(this)
|
playbackManager = PlaybackManager(this)
|
||||||
playbackManager.setCallbacks(this)
|
playbackManager.setCallbacks(this)
|
||||||
|
@ -330,10 +304,6 @@ class MusicService : MediaBrowserServiceCompat(),
|
||||||
unregisterReceiver(widgetIntentReceiver)
|
unregisterReceiver(widgetIntentReceiver)
|
||||||
unregisterReceiver(updateFavoriteReceiver)
|
unregisterReceiver(updateFavoriteReceiver)
|
||||||
unregisterReceiver(lockScreenReceiver)
|
unregisterReceiver(lockScreenReceiver)
|
||||||
if (becomingNoisyReceiverRegistered) {
|
|
||||||
unregisterReceiver(becomingNoisyReceiver)
|
|
||||||
becomingNoisyReceiverRegistered = false
|
|
||||||
}
|
|
||||||
if (headsetReceiverRegistered) {
|
if (headsetReceiverRegistered) {
|
||||||
unregisterReceiver(headsetReceiver)
|
unregisterReceiver(headsetReceiver)
|
||||||
headsetReceiverRegistered = false
|
headsetReceiverRegistered = false
|
||||||
|
@ -554,7 +524,7 @@ class MusicService : MediaBrowserServiceCompat(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val isLastTrack: Boolean
|
private val isLastTrack: Boolean
|
||||||
get() = getPosition() == playingQueue.size - 1
|
get() = getPosition() == playingQueue.size - 1
|
||||||
|
|
||||||
val isPlaying: Boolean
|
val isPlaying: Boolean
|
||||||
|
@ -585,7 +555,7 @@ class MusicService : MediaBrowserServiceCompat(),
|
||||||
notifyChange(QUEUE_CHANGED)
|
notifyChange(QUEUE_CHANGED)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun notifyChange(what: String) {
|
private fun notifyChange(what: String) {
|
||||||
handleAndSendChangeInternal(what)
|
handleAndSendChangeInternal(what)
|
||||||
sendPublicIntent(what)
|
sendPublicIntent(what)
|
||||||
}
|
}
|
||||||
|
@ -712,17 +682,40 @@ class MusicService : MediaBrowserServiceCompat(),
|
||||||
|
|
||||||
override fun onTrackEnded() {
|
override fun onTrackEnded() {
|
||||||
acquireWakeLock()
|
acquireWakeLock()
|
||||||
playerHandler?.sendEmptyMessage(TRACK_ENDED)
|
// if there is a timer finished, don't continue
|
||||||
|
if (pendingQuit
|
||||||
|
|| repeatMode == REPEAT_MODE_NONE && isLastTrack
|
||||||
|
) {
|
||||||
|
notifyChange(PLAY_STATE_CHANGED)
|
||||||
|
seek(0)
|
||||||
|
if (pendingQuit) {
|
||||||
|
pendingQuit = false
|
||||||
|
quit()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
playNextSong(false)
|
||||||
|
}
|
||||||
|
releaseWakeLock()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTrackEndedWithCrossfade() {
|
override fun onTrackEndedWithCrossfade() {
|
||||||
trackEndedByCrossfade = true
|
trackEndedByCrossfade = true
|
||||||
acquireWakeLock()
|
onTrackEnded()
|
||||||
playerHandler?.sendEmptyMessage(TRACK_ENDED)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTrackWentToNext() {
|
override fun onTrackWentToNext() {
|
||||||
playerHandler?.sendEmptyMessage(TRACK_WENT_TO_NEXT)
|
if (pendingQuit || repeatMode == REPEAT_MODE_NONE && isLastTrack) {
|
||||||
|
pause(false)
|
||||||
|
seek(0)
|
||||||
|
if (pendingQuit) {
|
||||||
|
pendingQuit = false
|
||||||
|
quit()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
position = nextPosition
|
||||||
|
prepareNextImpl()
|
||||||
|
notifyChange(META_CHANGED)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPlayStateChanged() {
|
override fun onPlayStateChanged() {
|
||||||
|
@ -775,7 +768,6 @@ class MusicService : MediaBrowserServiceCompat(),
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pause(force: Boolean = false) {
|
fun pause(force: Boolean = false) {
|
||||||
isPausedByTransientLossOfFocus = false
|
|
||||||
playbackManager.pause(force) {
|
playbackManager.pause(force) {
|
||||||
notifyChange(PLAY_STATE_CHANGED)
|
notifyChange(PLAY_STATE_CHANGED)
|
||||||
}
|
}
|
||||||
|
@ -783,30 +775,13 @@ class MusicService : MediaBrowserServiceCompat(),
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun play() {
|
fun play() {
|
||||||
if (requestFocus()) {
|
|
||||||
playbackManager.play(onNotInitialized = { playSongAt(getPosition()) }) {
|
playbackManager.play(onNotInitialized = { playSongAt(getPosition()) }) {
|
||||||
|
|
||||||
if (!becomingNoisyReceiverRegistered) {
|
|
||||||
registerReceiver(
|
|
||||||
becomingNoisyReceiver,
|
|
||||||
becomingNoisyReceiverIntentFilter
|
|
||||||
)
|
|
||||||
becomingNoisyReceiverRegistered = true
|
|
||||||
}
|
|
||||||
if (notHandledMetaChangedForCurrentTrack) {
|
if (notHandledMetaChangedForCurrentTrack) {
|
||||||
handleChangeInternal(META_CHANGED)
|
handleChangeInternal(META_CHANGED)
|
||||||
notHandledMetaChangedForCurrentTrack = false
|
notHandledMetaChangedForCurrentTrack = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// fixes a bug where the volume would stay ducked because the
|
|
||||||
// AudioManager.AUDIOFOCUS_GAIN event is not sent
|
|
||||||
playerHandler?.removeMessages(DUCK)
|
|
||||||
playerHandler?.sendEmptyMessage(UNDUCK)
|
|
||||||
}
|
}
|
||||||
notifyChange(PLAY_STATE_CHANGED)
|
notifyChange(PLAY_STATE_CHANGED)
|
||||||
} else {
|
|
||||||
showToast(R.string.audio_focus_denied)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun playNextSong(force: Boolean) {
|
fun playNextSong(force: Boolean) {
|
||||||
|
@ -829,14 +804,12 @@ class MusicService : MediaBrowserServiceCompat(),
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun prepareNextImpl() {
|
fun prepareNextImpl() {
|
||||||
val start = System.currentTimeMillis()
|
|
||||||
try {
|
try {
|
||||||
val nextPosition = getNextPosition(false)
|
val nextPosition = getNextPosition(false)
|
||||||
playbackManager.setNextDataSource(getSongAt(nextPosition).uri.toString())
|
playbackManager.setNextDataSource(getSongAt(nextPosition).uri.toString())
|
||||||
this.nextPosition = nextPosition
|
this.nextPosition = nextPosition
|
||||||
} catch (ignored: Exception) {
|
} catch (ignored: Exception) {
|
||||||
}
|
}
|
||||||
println("Time Prepare Next: ${System.currentTimeMillis() - start}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toggleFavorite() {
|
fun toggleFavorite() {
|
||||||
|
@ -860,16 +833,11 @@ class MusicService : MediaBrowserServiceCompat(),
|
||||||
stopForeground(true)
|
stopForeground(true)
|
||||||
isForeground = false
|
isForeground = false
|
||||||
notificationManager?.cancel(PlayingNotification.NOTIFICATION_ID)
|
notificationManager?.cancel(PlayingNotification.NOTIFICATION_ID)
|
||||||
AudioManagerCompat.abandonAudioFocusRequest(audioManager!!,
|
|
||||||
AudioFocusRequestCompat.Builder(AudioManagerCompat.AUDIOFOCUS_GAIN)
|
|
||||||
.setOnAudioFocusChangeListener(audioFocusListener)
|
|
||||||
.setAudioAttributes(
|
|
||||||
AudioAttributesCompat.Builder().setContentType(CONTENT_TYPE_MUSIC).build()
|
|
||||||
).build())
|
|
||||||
stopSelf()
|
stopSelf()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun releaseWakeLock() {
|
private fun releaseWakeLock() {
|
||||||
if (wakeLock!!.isHeld) {
|
if (wakeLock!!.isHeld) {
|
||||||
wakeLock?.release()
|
wakeLock?.release()
|
||||||
}
|
}
|
||||||
|
@ -1273,17 +1241,6 @@ class MusicService : MediaBrowserServiceCompat(),
|
||||||
mediaSession?.release()
|
mediaSession?.release()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun requestFocus(): Boolean {
|
|
||||||
return AudioManagerCompat.requestAudioFocus(
|
|
||||||
audioManager!!,
|
|
||||||
AudioFocusRequestCompat.Builder(AudioManagerCompat.AUDIOFOCUS_GAIN)
|
|
||||||
.setOnAudioFocusChangeListener(audioFocusListener)
|
|
||||||
.setAudioAttributes(
|
|
||||||
AudioAttributesCompat.Builder().setContentType(CONTENT_TYPE_MUSIC).build()
|
|
||||||
).build()
|
|
||||||
) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun restoreState() {
|
private fun restoreState() {
|
||||||
shuffleMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(
|
shuffleMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(
|
||||||
SAVED_SHUFFLE_MODE, 0
|
SAVED_SHUFFLE_MODE, 0
|
||||||
|
@ -1393,12 +1350,6 @@ class MusicService : MediaBrowserServiceCompat(),
|
||||||
const val SAVED_POSITION_IN_TRACK = "POSITION_IN_TRACK"
|
const val SAVED_POSITION_IN_TRACK = "POSITION_IN_TRACK"
|
||||||
const val SAVED_SHUFFLE_MODE = "SHUFFLE_MODE"
|
const val SAVED_SHUFFLE_MODE = "SHUFFLE_MODE"
|
||||||
const val SAVED_REPEAT_MODE = "REPEAT_MODE"
|
const val SAVED_REPEAT_MODE = "REPEAT_MODE"
|
||||||
const val RELEASE_WAKELOCK = 0
|
|
||||||
const val TRACK_ENDED = 1
|
|
||||||
const val TRACK_WENT_TO_NEXT = 2
|
|
||||||
const val FOCUS_CHANGE = 3
|
|
||||||
const val DUCK = 4
|
|
||||||
const val UNDUCK = 5
|
|
||||||
const val SHUFFLE_MODE_NONE = 0
|
const val SHUFFLE_MODE_NONE = 0
|
||||||
const val SHUFFLE_MODE_SHUFFLE = 1
|
const val SHUFFLE_MODE_SHUFFLE = 1
|
||||||
const val REPEAT_MODE_NONE = 0
|
const val REPEAT_MODE_NONE = 0
|
||||||
|
|
|
@ -1,123 +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.media.AudioManager
|
|
||||||
import android.os.Handler
|
|
||||||
import android.os.Looper
|
|
||||||
import android.os.Message
|
|
||||||
import code.name.monkey.retromusic.util.PreferenceUtil.isAudioDucking
|
|
||||||
import code.name.monkey.retromusic.util.PreferenceUtil.isAudioFocusEnabled
|
|
||||||
import java.lang.ref.WeakReference
|
|
||||||
|
|
||||||
internal class PlaybackHandler(service: MusicService, looper: Looper) : Handler(looper) {
|
|
||||||
private val mService: WeakReference<MusicService>
|
|
||||||
private var currentDuckVolume = 1.0f
|
|
||||||
|
|
||||||
override fun handleMessage(msg: Message) {
|
|
||||||
val service = mService.get() ?: return
|
|
||||||
when (msg.what) {
|
|
||||||
MusicService.DUCK -> {
|
|
||||||
if (isAudioDucking) {
|
|
||||||
currentDuckVolume -= .05f
|
|
||||||
if (currentDuckVolume > .2f) {
|
|
||||||
sendEmptyMessageDelayed(MusicService.DUCK, 10)
|
|
||||||
} else {
|
|
||||||
currentDuckVolume = .2f
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
currentDuckVolume = 1f
|
|
||||||
}
|
|
||||||
service.playback?.setVolume(currentDuckVolume)
|
|
||||||
}
|
|
||||||
MusicService.UNDUCK -> {
|
|
||||||
if (isAudioDucking) {
|
|
||||||
currentDuckVolume += .03f
|
|
||||||
if (currentDuckVolume < 1f) {
|
|
||||||
sendEmptyMessageDelayed(MusicService.UNDUCK, 10)
|
|
||||||
} else {
|
|
||||||
currentDuckVolume = 1f
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
currentDuckVolume = 1f
|
|
||||||
}
|
|
||||||
service.playback?.setVolume(currentDuckVolume)
|
|
||||||
}
|
|
||||||
MusicService.TRACK_WENT_TO_NEXT ->
|
|
||||||
if (service.pendingQuit || service.repeatMode == MusicService.REPEAT_MODE_NONE && service.isLastTrack) {
|
|
||||||
service.pause(false)
|
|
||||||
service.seek(0)
|
|
||||||
if (service.pendingQuit) {
|
|
||||||
service.pendingQuit = false
|
|
||||||
service.quit()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
service.position = service.nextPosition
|
|
||||||
service.prepareNextImpl()
|
|
||||||
service.notifyChange(MusicService.META_CHANGED)
|
|
||||||
}
|
|
||||||
MusicService.TRACK_ENDED -> {
|
|
||||||
// if there is a timer finished, don't continue
|
|
||||||
if (service.pendingQuit
|
|
||||||
|| service.repeatMode == MusicService.REPEAT_MODE_NONE && service.isLastTrack
|
|
||||||
) {
|
|
||||||
service.notifyChange(MusicService.PLAY_STATE_CHANGED)
|
|
||||||
service.seek(0)
|
|
||||||
if (service.pendingQuit) {
|
|
||||||
service.pendingQuit = false
|
|
||||||
service.quit()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
service.playNextSong(false)
|
|
||||||
}
|
|
||||||
sendEmptyMessage(MusicService.RELEASE_WAKELOCK)
|
|
||||||
}
|
|
||||||
MusicService.RELEASE_WAKELOCK -> service.releaseWakeLock()
|
|
||||||
MusicService.FOCUS_CHANGE -> when (msg.arg1) {
|
|
||||||
AudioManager.AUDIOFOCUS_GAIN -> {
|
|
||||||
if (!service.isPlaying && service.isPausedByTransientLossOfFocus) {
|
|
||||||
service.play()
|
|
||||||
service.isPausedByTransientLossOfFocus = false
|
|
||||||
}
|
|
||||||
removeMessages(MusicService.DUCK)
|
|
||||||
sendEmptyMessage(MusicService.UNDUCK)
|
|
||||||
}
|
|
||||||
AudioManager.AUDIOFOCUS_LOSS -> {
|
|
||||||
// Lost focus for an unbounded amount of time: stop playback and release media playback
|
|
||||||
if (!isAudioFocusEnabled) {
|
|
||||||
service.pause(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
|
|
||||||
// Lost focus for a short time, but we have to stop
|
|
||||||
// playback. We don't release the media playback because playback
|
|
||||||
// is likely to resume
|
|
||||||
val wasPlaying = service.isPlaying
|
|
||||||
service.pause(true)
|
|
||||||
service.isPausedByTransientLossOfFocus = wasPlaying
|
|
||||||
}
|
|
||||||
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
|
|
||||||
// Lost focus for a short time, but it's ok to keep playing
|
|
||||||
// at an attenuated level
|
|
||||||
removeMessages(MusicService.UNDUCK)
|
|
||||||
sendEmptyMessage(MusicService.DUCK)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
mService = WeakReference(service)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -42,7 +42,7 @@ class PlaybackManager(val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setCallbacks(callbacks: Playback.PlaybackCallbacks) {
|
fun setCallbacks(callbacks: Playback.PlaybackCallbacks) {
|
||||||
playback?.setCallbacks(callbacks)
|
playback?.callbacks = callbacks
|
||||||
}
|
}
|
||||||
|
|
||||||
fun play(onNotInitialized: () -> Unit = {}, onPlay: () -> Unit = {}) {
|
fun play(onNotInitialized: () -> Unit = {}, onPlay: () -> Unit = {}) {
|
||||||
|
|
|
@ -31,7 +31,7 @@ interface Playback {
|
||||||
|
|
||||||
fun setNextDataSource(path: String?)
|
fun setNextDataSource(path: String?)
|
||||||
|
|
||||||
fun setCallbacks(callbacks: PlaybackCallbacks)
|
var callbacks: PlaybackCallbacks?
|
||||||
|
|
||||||
fun start(): Boolean
|
fun start(): Boolean
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue