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:
Prathamesh More 2022-05-28 16:24:23 +05:30
parent 14f31eae37
commit 1f7a00e999
11 changed files with 195 additions and 235 deletions

View file

@ -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')}\"")

View file

@ -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() {

View file

@ -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)

View file

@ -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()

View file

@ -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 {

View file

@ -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
}
}

View file

@ -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

View file

@ -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,31 +775,14 @@ 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) {
playSongAt(getNextPosition(force)) playSongAt(getNextPosition(force))
@ -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

View file

@ -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)
}
}

View file

@ -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 = {}) {

View file

@ -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