diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 440314553..8fbbd234a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -136,32 +136,11 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:label="Start">
diff --git a/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt b/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt
index 86f3d1984..e3ed12787 100644
--- a/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt
+++ b/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt
@@ -57,6 +57,7 @@ object IntegerTable {
const val PLAYBACK_NOTIFICATION_CODE = 0xA0A0
/** "Music loading" notification code */
const val INDEXER_NOTIFICATION_CODE = 0xA0A1
+ const val TASKER_ERROR_NOT_RESTORED = 0xA0A2
/** MainActivity Intent request code */
const val REQUEST_CODE = 0xA0C0
/** RepeatMode.NONE */
diff --git a/app/src/main/java/org/oxycblt/auxio/music/service/IndexerServiceFragment.kt b/app/src/main/java/org/oxycblt/auxio/music/service/IndexerServiceFragment.kt
index 6362def6b..1cd81d3ab 100644
--- a/app/src/main/java/org/oxycblt/auxio/music/service/IndexerServiceFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/music/service/IndexerServiceFragment.kt
@@ -96,7 +96,9 @@ constructor(
// Not observing and done loading, exit foreground.
logD("Exiting foreground")
post(observingNotification)
- } else {
+ } else if (!playbackManager.awaitingDeferredPlayback) {
+ // Very possible we are done loading music and now need to avoid downtime
+ // as the player begins to load the playback state.
post(null)
}
}
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/service/ExoPlaybackStateHolder.kt b/app/src/main/java/org/oxycblt/auxio/playback/service/ExoPlaybackStateHolder.kt
index 1fdfee063..3fc1edf46 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/service/ExoPlaybackStateHolder.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/service/ExoPlaybackStateHolder.kt
@@ -42,6 +42,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.yield
+import org.oxycblt.auxio.ForegroundListener
import org.oxycblt.auxio.image.ImageSettings
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicRepository
@@ -84,11 +85,13 @@ class ExoPlaybackStateHolder(
private val restoreScope = CoroutineScope(Dispatchers.IO + saveJob)
private var currentSaveJob: Job? = null
private var openAudioEffectSession = false
+ private var foregroundListener: ForegroundListener? = null
override var sessionOngoing = false
private set
- fun attach() {
+ fun attach(foregroundListener: ForegroundListener) {
+ this.foregroundListener = foregroundListener
imageSettings.registerListener(this)
player.addListener(this)
replayGainProcessor.attach()
@@ -105,6 +108,7 @@ class ExoPlaybackStateHolder(
replayGainProcessor.release()
imageSettings.unregisterListener(this)
player.release()
+ foregroundListener = null
}
override var parent: MusicParent? = null
@@ -168,13 +172,15 @@ class ExoPlaybackStateHolder(
// Apply the saved state on the main thread to prevent code expecting
// state updates on the main thread from crashing.
withContext(Dispatchers.Main) {
+ playbackManager.applySavedState(state, false)
if (action.sessionRequired) {
sessionOngoing = true
+ foregroundListener?.updateForeground(ForegroundListener.Change.MEDIA_SESSION)
}
- playbackManager.applySavedState(state, false)
}
- } else if (action.sessionRequired) {
- error("No playback state to restore, but need to start session")
+ } else {
+ logD("No saved state to restore")
+ foregroundListener?.updateForeground(ForegroundListener.Change.MEDIA_SESSION)
}
}
}
@@ -189,11 +195,15 @@ class ExoPlaybackStateHolder(
// Open -> Try to find the Song for the given file and then play it from all songs
is DeferredPlayback.Open -> {
logD("Opening specified file")
- deviceLibrary.findSongForUri(context, action.uri)?.let { song ->
+ val song = deviceLibrary.findSongForUri(context, action.uri)
+ if (song != null) {
playbackManager.play(
requireNotNull(commandFactory.song(song, ShuffleMode.IMPLICIT)) {
"Invalid playback parameters"
})
+ } else {
+ logD("No song found for uri")
+ foregroundListener?.updateForeground(ForegroundListener.Change.MEDIA_SESSION)
}
}
}
@@ -373,13 +383,36 @@ class ExoPlaybackStateHolder(
rawQueue: RawQueue,
ack: StateAck.NewPlayback?
) {
- this.parent = parent
- player.setMediaItems(rawQueue.heap.map { it.toMediaItem(context, null) })
- if (rawQueue.isShuffled) {
- player.shuffleModeEnabled = true
- player.setShuffleOrder(BetterShuffleOrder(rawQueue.shuffledMapping.toIntArray()))
- } else {
- player.shuffleModeEnabled = false
+ var sendNewPlaybackEvent = false
+ var shouldSeek = false
+ if (this.parent != parent) {
+ this.parent = parent
+ sendNewPlaybackEvent = true
+ }
+ if (rawQueue != resolveQueue()) {
+ player.setMediaItems(rawQueue.heap.map { it.toMediaItem(context, null) })
+ if (rawQueue.isShuffled) {
+ player.shuffleModeEnabled = true
+ player.setShuffleOrder(BetterShuffleOrder(rawQueue.shuffledMapping.toIntArray()))
+ } else {
+ player.shuffleModeEnabled = false
+ }
+ player.seekTo(rawQueue.heapIndex, C.TIME_UNSET)
+ player.prepare()
+ player.pause()
+ sendNewPlaybackEvent = true
+ shouldSeek = true
+ }
+
+ repeatMode(repeatMode)
+ // Positions in milliseconds will drift during tight restores (i.e what the music loader
+ // does to sanitize the state), compare by seconds instead.
+// if (positionMs.msToSecs() != player.currentPosition.msToSecs() || shouldSeek) {
+// player.seekTo(positionMs)
+// }
+
+ if (sendNewPlaybackEvent) {
+ ack?.let { playbackManager.ack(this, it) }
}
player.seekTo(rawQueue.heapIndex, C.TIME_UNSET)
player.prepare()
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt
index bd10eea95..8273cb57a 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt
@@ -69,6 +69,10 @@ interface PlaybackStateManager {
/** Whether there is an ongoing playback session or not. */
val sessionOngoing: Boolean
+ /* Whether the player is awaiting a [DeferredPlayback] to be consumed. */
+ val awaitingDeferredPlayback: Boolean
+
+
/** The audio session ID of the internal player. Null if no internal player exists. */
val currentAudioSessionId: Int?
@@ -395,6 +399,10 @@ class PlaybackStateManagerImpl @Inject constructor() : PlaybackStateManager {
override val sessionOngoing
get() = stateHolder?.sessionOngoing ?: false
+ override val awaitingDeferredPlayback: Boolean
+ get() = pendingDeferredPlayback != null
+
+
override val currentAudioSessionId: Int?
get() = stateHolder?.audioSessionId
diff --git a/app/src/main/java/org/oxycblt/auxio/tasker/RestoreState.kt b/app/src/main/java/org/oxycblt/auxio/tasker/RestoreState.kt
deleted file mode 100644
index dd64b7dd5..000000000
--- a/app/src/main/java/org/oxycblt/auxio/tasker/RestoreState.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (c) 2024 Auxio Project
- * RestoreState.kt is part of Auxio.
- *
- * This program 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 program 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.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package org.oxycblt.auxio.tasker
-
-import android.app.Activity
-import android.content.Context
-import android.content.Intent
-import android.os.Bundle
-import androidx.core.content.ContextCompat
-import com.joaomgcd.taskerpluginlibrary.action.TaskerPluginRunnerActionNoOutputOrInput
-import com.joaomgcd.taskerpluginlibrary.config.TaskerPluginConfig
-import com.joaomgcd.taskerpluginlibrary.config.TaskerPluginConfigHelperNoOutputOrInput
-import com.joaomgcd.taskerpluginlibrary.config.TaskerPluginConfigNoInput
-import com.joaomgcd.taskerpluginlibrary.input.TaskerInput
-import com.joaomgcd.taskerpluginlibrary.runner.TaskerPluginResult
-import com.joaomgcd.taskerpluginlibrary.runner.TaskerPluginResultSucess
-import dagger.hilt.EntryPoints
-import kotlinx.coroutines.runBlocking
-import org.oxycblt.auxio.AuxioService
-import org.oxycblt.auxio.playback.state.DeferredPlayback
-
-class RestoreStateHelper(config: TaskerPluginConfig) :
- TaskerPluginConfigHelperNoOutputOrInput(config) {
- override val runnerClass: Class
- get() = RestoreStateRunner::class.java
-
- override fun addToStringBlurb(input: TaskerInput, blurbBuilder: StringBuilder) {
- blurbBuilder.append("Shuffles All Songs Once the Service is Available")
- }
-}
-
-class RestoreStateConfigBasicAction : Activity(), TaskerPluginConfigNoInput {
- override val context: Context
- get() = applicationContext
-
- private val taskerHelper by lazy { RestoreStateHelper(this) }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- taskerHelper.finishForTasker()
- }
-}
-
-class RestoreStateRunner : TaskerPluginRunnerActionNoOutputOrInput() {
- override fun run(context: Context, input: TaskerInput): TaskerPluginResult {
- ContextCompat.startForegroundService(
- context,
- Intent(context, AuxioService::class.java)
- .putExtra(AuxioService.INTENT_KEY_INTERNAL_START, true))
- val entryPoint = EntryPoints.get(context.applicationContext, TaskerEntryPoint::class.java)
- val playbackManager = entryPoint.playbackManager()
- runBlocking {
- playbackManager.playDeferred(DeferredPlayback.RestoreState(sessionRequired = true))
- }
- while (!playbackManager.sessionOngoing) {}
- Thread.sleep(100)
- return TaskerPluginResultSucess()
- }
-}
diff --git a/app/src/main/java/org/oxycblt/auxio/tasker/ShuffleAll.kt b/app/src/main/java/org/oxycblt/auxio/tasker/ShuffleAll.kt
deleted file mode 100644
index 9ceae90ed..000000000
--- a/app/src/main/java/org/oxycblt/auxio/tasker/ShuffleAll.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (c) 2024 Auxio Project
- * ShuffleAll.kt is part of Auxio.
- *
- * This program 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 program 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.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package org.oxycblt.auxio.tasker
-
-import android.app.Activity
-import android.content.Context
-import android.content.Intent
-import android.os.Bundle
-import androidx.core.content.ContextCompat
-import com.joaomgcd.taskerpluginlibrary.action.TaskerPluginRunnerActionNoOutputOrInput
-import com.joaomgcd.taskerpluginlibrary.config.TaskerPluginConfig
-import com.joaomgcd.taskerpluginlibrary.config.TaskerPluginConfigHelperNoOutputOrInput
-import com.joaomgcd.taskerpluginlibrary.config.TaskerPluginConfigNoInput
-import com.joaomgcd.taskerpluginlibrary.input.TaskerInput
-import com.joaomgcd.taskerpluginlibrary.runner.TaskerPluginResult
-import com.joaomgcd.taskerpluginlibrary.runner.TaskerPluginResultSucess
-import dagger.hilt.EntryPoints
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.runBlocking
-import org.oxycblt.auxio.AuxioService
-import org.oxycblt.auxio.playback.state.DeferredPlayback
-
-class ShuffleAllHelper(config: TaskerPluginConfig) :
- TaskerPluginConfigHelperNoOutputOrInput(config) {
- override val runnerClass: Class
- get() = ShuffleAllRunner::class.java
-
- override fun addToStringBlurb(input: TaskerInput, blurbBuilder: StringBuilder) {
- blurbBuilder.append("Shuffles All Songs Once the Service is Available")
- }
-}
-
-class ShuffleAllConfigBasicAction : Activity(), TaskerPluginConfigNoInput {
- override val context: Context
- get() = applicationContext
-
- private val taskerHelper by lazy { ShuffleAllHelper(this) }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- taskerHelper.finishForTasker()
- }
-}
-
-class ShuffleAllRunner : TaskerPluginRunnerActionNoOutputOrInput() {
- override fun run(context: Context, input: TaskerInput): TaskerPluginResult {
- ContextCompat.startForegroundService(
- context,
- Intent(context, AuxioService::class.java)
- .putExtra(AuxioService.INTENT_KEY_INTERNAL_START, true))
- val entryPoint = EntryPoints.get(context.applicationContext, TaskerEntryPoint::class.java)
- val playbackManager = entryPoint.playbackManager()
- runBlocking(Dispatchers.Main) { playbackManager.playDeferred(DeferredPlayback.ShuffleAll) }
- while (!playbackManager.sessionOngoing) {}
- Thread.sleep(100)
- return TaskerPluginResultSucess()
- }
-}
diff --git a/app/src/main/java/org/oxycblt/auxio/tasker/Start.kt b/app/src/main/java/org/oxycblt/auxio/tasker/Start.kt
index 82e1bb887..9de3661d1 100644
--- a/app/src/main/java/org/oxycblt/auxio/tasker/Start.kt
+++ b/app/src/main/java/org/oxycblt/auxio/tasker/Start.kt
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.tasker
import android.app.Activity
@@ -29,16 +29,21 @@ import com.joaomgcd.taskerpluginlibrary.config.TaskerPluginConfigHelperNoOutputO
import com.joaomgcd.taskerpluginlibrary.config.TaskerPluginConfigNoInput
import com.joaomgcd.taskerpluginlibrary.input.TaskerInput
import com.joaomgcd.taskerpluginlibrary.runner.TaskerPluginResult
+import com.joaomgcd.taskerpluginlibrary.runner.TaskerPluginResultError
import com.joaomgcd.taskerpluginlibrary.runner.TaskerPluginResultSucess
+import dagger.hilt.EntryPoints
+import kotlinx.coroutines.runBlocking
import org.oxycblt.auxio.AuxioService
+import org.oxycblt.auxio.IntegerTable
+import org.oxycblt.auxio.playback.state.DeferredPlayback
-class StartActionHelper(config: TaskerPluginConfig) :
- TaskerPluginConfigHelperNoOutputOrInput(config) {
- override val runnerClass: Class
- get() = StartActionRunner::class.java
+class StartHelper(config: TaskerPluginConfig) :
+ TaskerPluginConfigHelperNoOutputOrInput(config) {
+ override val runnerClass: Class
+ get() = StartStateRunner::class.java
override fun addToStringBlurb(input: TaskerInput, blurbBuilder: StringBuilder) {
- blurbBuilder.append("Starts the Auxio Service. You MUST apply an action after this.")
+ blurbBuilder.append("Shuffles All Songs Once the Service is Available")
}
}
@@ -46,7 +51,7 @@ class StartConfigBasicAction : Activity(), TaskerPluginConfigNoInput {
override val context: Context
get() = applicationContext
- private val taskerHelper by lazy { StartActionHelper(this) }
+ private val taskerHelper by lazy { StartHelper(this) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -54,12 +59,27 @@ class StartConfigBasicAction : Activity(), TaskerPluginConfigNoInput {
}
}
-class StartActionRunner : TaskerPluginRunnerActionNoOutputOrInput() {
+class StartStateRunner : TaskerPluginRunnerActionNoOutputOrInput() {
override fun run(context: Context, input: TaskerInput): TaskerPluginResult {
ContextCompat.startForegroundService(
context,
Intent(context, AuxioService::class.java)
- .putExtra(AuxioService.INTENT_KEY_INTERNAL_START, true))
+ .putExtra(AuxioService.INTENT_KEY_INTERNAL_START, true)
+ )
+ val entryPoint = EntryPoints.get(context.applicationContext, TaskerEntryPoint::class.java)
+ val playbackManager = entryPoint.playbackManager()
+ runBlocking {
+ playbackManager.playDeferred(DeferredPlayback.RestoreState(sessionRequired = true))
+ }
+ while (!playbackManager.sessionOngoing) {
+ if (!playbackManager.awaitingDeferredPlayback) {
+ return TaskerPluginResultError(
+ IntegerTable.TASKER_ERROR_NOT_RESTORED,
+ "No state to restore, did not restart playback."
+ )
+ }
+ }
+ Thread.sleep(100)
return TaskerPluginResultSucess()
}
}
diff --git a/media b/media
index 00124cbac..9fc2401b8 160000
--- a/media
+++ b/media
@@ -1 +1 @@
-Subproject commit 00124cbac493c06a24e19b01893946bdaf8faf58
+Subproject commit 9fc2401b8fdc2b23905402462e775c6db4e1527f