diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1aab4d51e..c1f57fe6a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,11 +3,15 @@
package="org.oxycblt.auxio">
-
+
+
= 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
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt
index d53af1ab1..8ac1eedba 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt
@@ -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
}
}
diff --git a/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt b/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt
index 94b7e4b1d..c5e16fa14 100644
--- a/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt
+++ b/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt
@@ -88,7 +88,7 @@ fun Context.getPlural(@PluralsRes pluralsRes: Int, value: Int): String {
* @throws IllegalStateException If the system service cannot be retrieved.
*/
fun Context.getSystemServiceSafe(serviceClass: KClass): T {
- return checkNotNull(ContextCompat.getSystemService(this, serviceClass.java)) {
+ return requireNotNull(ContextCompat.getSystemService(this, serviceClass.java)) {
"System service ${serviceClass.simpleName} could not be instantiated"
}
}
diff --git a/info/ARCHITECTURE.md b/info/ARCHITECTURE.md
index b174e8d00..ffd45738a 100644
--- a/info/ARCHITECTURE.md
+++ b/info/ARCHITECTURE.md
@@ -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.