Add wakelock
Actually add a wakelock to the music load process to prevent it from stopping the CPU mid-load.
This commit is contained in:
parent
ba79ac0001
commit
f1c40d2539
5 changed files with 58 additions and 16 deletions
|
@ -3,11 +3,15 @@
|
|||
package="org.oxycblt.auxio">
|
||||
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<queries />
|
||||
|
||||
<!--
|
||||
TODO: Bite the bullet and just use SAF so you dont have to rely on a flag that is
|
||||
probably going to be removed by Android 12/13 (Would also eliminate the dialogs dependency)
|
||||
-->
|
||||
<application
|
||||
android:name=".AuxioApp"
|
||||
android:allowBackup="true"
|
||||
|
|
|
@ -284,7 +284,7 @@ class PlaybackStateManager private constructor() {
|
|||
*/
|
||||
fun prev() {
|
||||
// If enabled, rewind before skipping back if the position is past 3 seconds [3000ms]
|
||||
if (settingsManager.rewindWithPrev && mPosition >= 3000) {
|
||||
if (settingsManager.rewindWithPrev && mPosition >= REWIND_THRESHOLD) {
|
||||
rewind()
|
||||
} else {
|
||||
// Only decrement the index if there's a song to move back to AND if we are not exiting
|
||||
|
@ -808,6 +808,8 @@ class PlaybackStateManager private constructor() {
|
|||
}
|
||||
|
||||
companion object {
|
||||
private const val REWIND_THRESHOLD = 3000L
|
||||
|
||||
@Volatile
|
||||
private var INSTANCE: PlaybackStateManager? = null
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import android.content.Intent
|
|||
import android.content.IntentFilter
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.media.AudioManager
|
||||
import android.media.audiofx.Visualizer
|
||||
import android.media.MediaPlayer
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.PowerManager
|
||||
|
@ -59,16 +59,18 @@ import org.oxycblt.auxio.ui.getSystemServiceSafe
|
|||
* @author OxygenCobalt
|
||||
*/
|
||||
class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Callback, SettingsManager.Callback {
|
||||
private val player: SimpleExoPlayer by lazy(::newPlayer)
|
||||
private val playbackManager = PlaybackStateManager.getInstance()
|
||||
private val settingsManager = SettingsManager.getInstance()
|
||||
|
||||
private lateinit var player: SimpleExoPlayer
|
||||
private lateinit var mediaSession: MediaSessionCompat
|
||||
private lateinit var notificationManager: NotificationManager
|
||||
|
||||
private lateinit var notification: PlaybackNotification
|
||||
private lateinit var notificationManager: NotificationManager
|
||||
|
||||
private lateinit var audioReactor: AudioReactor
|
||||
private lateinit var systemReceiver: SystemEventReceiver
|
||||
private lateinit var wakeLock: PowerManager.WakeLock
|
||||
private val systemReceiver = SystemEventReceiver()
|
||||
|
||||
private val playbackManager = PlaybackStateManager.getInstance()
|
||||
private val settingsManager = SettingsManager.getInstance()
|
||||
|
||||
private var isForeground = false
|
||||
|
||||
|
@ -92,6 +94,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
|||
|
||||
// --- PLAYER SETUP ---
|
||||
|
||||
player = newPlayer()
|
||||
player.addListener(this@PlaybackService)
|
||||
player.setAudioAttributes(
|
||||
AudioAttributes.Builder()
|
||||
|
@ -102,10 +105,11 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
|||
)
|
||||
|
||||
audioReactor = AudioReactor(this, player)
|
||||
wakeLock = getSystemServiceSafe(PowerManager::class).newWakeLock(
|
||||
PowerManager.PARTIAL_WAKE_LOCK, this::class.simpleName
|
||||
)
|
||||
|
||||
// --- SYSTEM RECEIVER SETUP ---
|
||||
|
||||
systemReceiver = SystemEventReceiver()
|
||||
// --- CALLBACKS ---
|
||||
|
||||
// Set up the media button callbacks
|
||||
mediaSession = MediaSessionCompat(this, packageName).apply {
|
||||
|
@ -119,6 +123,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
|||
}
|
||||
}
|
||||
|
||||
// Then the notif/headset callbacks
|
||||
IntentFilter().apply {
|
||||
addAction(PlaybackNotification.ACTION_LOOP)
|
||||
addAction(PlaybackNotification.ACTION_SHUFFLE)
|
||||
|
@ -165,6 +170,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
|||
player.release()
|
||||
mediaSession.release()
|
||||
audioReactor.release()
|
||||
wakeLock.release()
|
||||
|
||||
playbackManager.removeCallback(this)
|
||||
settingsManager.removeCallback(this)
|
||||
|
@ -185,8 +191,13 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
|||
|
||||
override fun onPlaybackStateChanged(state: Int) {
|
||||
when (state) {
|
||||
Player.STATE_READY -> startPollingPosition()
|
||||
Player.STATE_READY -> {
|
||||
startPollingPosition()
|
||||
releaseWakelock()
|
||||
}
|
||||
|
||||
Player.STATE_ENDED -> playbackManager.next()
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
@ -196,6 +207,9 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
|||
if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT) {
|
||||
playbackManager.clearLoopMode()
|
||||
}
|
||||
|
||||
// We use the wakelock to ensure that the CPU is active while music is being loaded
|
||||
holdWakelock()
|
||||
}
|
||||
|
||||
override fun onPlayerError(error: ExoPlaybackException) {
|
||||
|
@ -376,7 +390,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
|||
val pollFlow = flow {
|
||||
while (true) {
|
||||
emit(player.currentPosition)
|
||||
delay(500)
|
||||
delay(POS_POLL_INTERVAL)
|
||||
}
|
||||
}.conflate()
|
||||
|
||||
|
@ -423,6 +437,26 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
|||
isForeground = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Hold the wakelock for the default amount of time [25 Seconds]
|
||||
*/
|
||||
private fun holdWakelock() {
|
||||
logD("Holding wakelock.")
|
||||
|
||||
wakeLock.acquire(WAKELOCK_TIME)
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the wakelock if its currently being held.
|
||||
*/
|
||||
private fun releaseWakelock() {
|
||||
logD("Attempting to release the wakelock.")
|
||||
|
||||
if (wakeLock.isHeld) {
|
||||
wakeLock.release()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a media button intent.
|
||||
*/
|
||||
|
@ -547,5 +581,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
|||
companion object {
|
||||
private const val DISCONNECTED = 0
|
||||
private const val CONNECTED = 1
|
||||
private const val WAKELOCK_TIME = 25000L
|
||||
private const val POS_POLL_INTERVAL = 500L
|
||||
}
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ fun Context.getPlural(@PluralsRes pluralsRes: Int, value: Int): String {
|
|||
* @throws IllegalStateException If the system service cannot be retrieved.
|
||||
*/
|
||||
fun <T : Any> Context.getSystemServiceSafe(serviceClass: KClass<T>): T {
|
||||
return checkNotNull(ContextCompat.getSystemService(this, serviceClass.java)) {
|
||||
return requireNotNull(ContextCompat.getSystemService(this, serviceClass.java)) {
|
||||
"System service ${serviceClass.simpleName} could not be instantiated"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -110,7 +110,7 @@ Auxio's playback system is somewhat unorthodox, as it avoids a lot of the built-
|
|||
PlaybackStateManager───────────────────┘
|
||||
```
|
||||
|
||||
`PlaybackStateManager` is the shared object that contains the master copy of the playback state, doing all operations on it. This object should ***NEVER*** be used in a UI, as it does not sanitize input and can cause major problems if a Volatile UI interacts with it. It's callback system is also prone to memory leaks if not cleared when done. `PlaybackViewModel` should be used instead, as it exposes stable data and safe functions that UI's can use to interact with the playback state.
|
||||
`PlaybackStateManager` is the shared object that contains the master copy of the playback state, doing all operations on it. This object should ***NEVER*** be used in a UI, as it does not sanitize input and can cause major problems if a Volatile UI interacts with it. It's callback system is also prone to memory leaks if not cleared when done. `PlaybackViewModel` should be used instead, as it exposes stable data and safe functions that UIs can use to interact with the playback state.
|
||||
|
||||
`PlaybackService`'s job is to use the playback state to manage the ExoPlayer instance and notification and also modify the state depending on system events, such as when a button is pressed on a headset. It should **never** be bound to, mostly because there is no need given that `PlaybackViewModel` exposes the same data in a much safer fashion.
|
||||
|
||||
|
|
Loading…
Reference in a new issue