mirror of
https://gitlab.com/ultrasonic/ultrasonic.git
synced 2026-10-22 23:42:14 +03:00
feat(bookmark): auto-save bookmark on pause for cross-client resume
This commit is contained in:
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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in new issue
Block a user