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:
OxygenCobalt 2021-03-21 11:12:48 -06:00
parent ba79ac0001
commit f1c40d2539
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
5 changed files with 58 additions and 16 deletions

View file

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

View file

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

View file

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

View file

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

View file

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