feat(bookmark): auto-save bookmark on pause for cross-client resume

This commit is contained in:
Sam Mingo authored and birdbird committed 2026-10-04 17:56:22 +00:00
1 parent bac2296991
commit 90ceb6d2e0
5 files changed
+58

No files matched your search

@@ -42,6 +42,8 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.Credentials
@@ -75,6 +77,8 @@ import org.moire.ultrasonic.util.toMediaItem
import org.moire.ultrasonic.util.toTrack
import timber.log.Timber
private const val BOOKMARK_INTERVAL_MS = 30_000L
@SuppressLint("UnsafeOptInUsageError")
@Suppress("TooManyFunctions") // Service lifecycle and Player.Listener callbacks
class PlaybackService :
@@ -100,6 +104,7 @@ class PlaybackService :
private var backendSwitchGeneration = 0L
private var accessStateSubscription: Job? = null
private var bookmarkJob: Job? = null
// Recovery state for failed playback items (see recoverFromPlaybackError).
private val mainHandler = Handler(Looper.getMainLooper())
@@ -152,6 +157,7 @@ class PlaybackService :
private fun releasePlayerAndSession() {
Timber.i("Releasing player and session")
stopPeriodicBookmarkSave()
cancel()
cancelPendingRecovery()
backendSwitchGeneration++
@@ -732,6 +738,9 @@ class PlaybackService :
}
}
previousMediaItem = mediaItem
if (Settings.shouldSaveBookmarkOnPause && player.isPlaying) {
startPeriodicBookmarkSave()
}
// Since we cannot update the metadata of the media item after creation,
// we cannot set change the rating on it
@@ -750,6 +759,14 @@ class PlaybackService :
updateWidgetPlayerState(isPlaying)
cacheNextSongs()
scrobblePlayerState()
if (Settings.shouldSaveBookmarkOnPause) {
if (isPlaying) {
startPeriodicBookmarkSave()
} else if (player.playbackState == Player.STATE_READY) {
stopPeriodicBookmarkSave()
saveBookmark()
}
}
}
override fun onRepeatModeChanged(repeatMode: Int) {
@@ -767,6 +784,35 @@ class PlaybackService :
}
}
private fun startPeriodicBookmarkSave() {
bookmarkJob?.cancel()
bookmarkJob = launch {
delay(BOOKMARK_INTERVAL_MS)
while (isActive) {
saveBookmark()
delay(BOOKMARK_INTERVAL_MS)
}
}
}
private fun stopPeriodicBookmarkSave() {
bookmarkJob?.cancel()
bookmarkJob = null
}
private fun saveBookmark() {
val track = player.currentMediaItem?.toTrack() ?: return
val positionMs = player.currentPosition
launch {
runCatching {
withContext(Dispatchers.IO) {
musicServiceFactory.getMusicService()
.createBookmark(track.id, positionMs.toInt())
}
}.onFailure { Timber.w(it, "Failed to save bookmark") }
}
}
private fun scrobblePlayerState() {
val track = player.currentMediaItem?.toTrack() ?: return
when {
@@ -236,6 +236,9 @@ object Settings {
var shouldClearBookmark
by BooleanSetting(getKey(R.string.setting_key_clear_bookmark), false)
var shouldSaveBookmarkOnPause
by BooleanSetting(getKey(R.string.setting_key_save_bookmark_on_pause), false)
var shouldAskForShareDetails
by BooleanSetting(getKey(R.string.setting_key_ask_for_share_details), true)
@@ -41,6 +41,7 @@
<string name="setting_key.chat_refresh_interval" translatable="false">chatRefreshInterval</string>
<string name="setting_key.directory_cache_time" translatable="false">directoryCacheTime</string>
<string name="setting_key.clear_bookmark" translatable="false">clearBookmark</string>
<string name="setting_key.save_bookmark_on_pause" translatable="false">saveBookmarkOnPause</string>
<string name="setting_key.disc_sort" translatable="false">discAndTrackSort</string>
<string name="setting_key.ask_for_share_details" translatable="false">sharingAlwaysAskForDetails</string>
<string name="setting_key.default_share_description" translatable="false">sharingDefaultDescription</string>
@@ -210,6 +210,8 @@
<string name="settings.chat_refresh">Chat Refresh Interval</string>
<string name="settings.clear_bookmark">Clear Bookmark</string>
<string name="settings.clear_bookmark_summary">Clear bookmark upon completion of playback of a song</string>
<string name="settings.save_bookmark_on_pause">Save Bookmark on Pause</string>
<string name="settings.save_bookmark_on_pause_summary">Automatically save playback position when pausing, enabling resume from another client</string>
<string name="settings.clear_search_history">Clear Search History</string>
<string name="settings.clear_image_cache">Clear Image Cache</string>
<string name="settings.clear_image_cache_summary">Delete %1$s of cached images</string>
+6
View File
@@ -84,6 +84,12 @@
a:summary="@string/settings.clear_bookmark_summary"
a:title="@string/settings.clear_bookmark"
app:iconSpaceReserved="false"/>
<CheckBoxPreference
a:defaultValue="false"
a:key="@string/setting_key.save_bookmark_on_pause"
a:summary="@string/settings.save_bookmark_on_pause_summary"
a:title="@string/settings.save_bookmark_on_pause"
app:iconSpaceReserved="false"/>
<ListPreference
a:defaultValue="5000"
a:entries="@array/incrementTimeNames"