diff --git a/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDatabase.kt b/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDatabase.kt index 45eb4fa9..f398589a 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDatabase.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDatabase.kt @@ -231,6 +231,10 @@ class AbsDatabase : Plugin() { if (!success) { call.resolve(JSObject("{\"error\":\"$errorMsg\"}")) } else { + // Remove all local sessions + savedSessions.forEach { + DeviceManager.dbManager.removePlaybackSession(it.id) + } call.resolve() } } diff --git a/android/app/src/main/java/com/audiobookshelf/app/server/ApiHandler.kt b/android/app/src/main/java/com/audiobookshelf/app/server/ApiHandler.kt index 40e2c539..de080551 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/server/ApiHandler.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/server/ApiHandler.kt @@ -323,13 +323,15 @@ class ApiHandler(var ctx:Context) { fun sendSyncLocalSessions(playbackSessions:List, cb: (Boolean, String?) -> Unit) { val payload = JSObject(jacksonMapper.writeValueAsString(LocalSessionsSyncRequestPayload(playbackSessions))) - + Log.d(tag, "Sending ${playbackSessions.size} saved local playback sessions to server") postRequest("/api/session/local-all", payload, null) { if (!it.getString("error").isNullOrEmpty()) { + Log.e(tag, "Failed to sync local sessions") cb(false, it.getString("error")) } else { val response = jacksonMapper.readValue(it.toString()) response.results.forEach { localSessionSyncResult -> + Log.d(tag, "Synced session result ${localSessionSyncResult.id}|${localSessionSyncResult.progressSynced}|${localSessionSyncResult.success}") playbackSessions.find { ps -> ps.id == localSessionSyncResult.id }?.let { session -> if (localSessionSyncResult.progressSynced == true) { val syncResult = SyncResult(true, true, "Progress synced on server") @@ -338,7 +340,6 @@ class ApiHandler(var ctx:Context) { } else if (!localSessionSyncResult.success) { Log.e(tag, "Failed to sync session ${session.displayTitle} with server. Error: ${localSessionSyncResult.error}") } - DeviceManager.dbManager.removePlaybackSession(session.id) } } cb(true, null) diff --git a/components/app/AudioPlayerContainer.vue b/components/app/AudioPlayerContainer.vue index 94a13b4f..623b72ed 100644 --- a/components/app/AudioPlayerContainer.vue +++ b/components/app/AudioPlayerContainer.vue @@ -256,7 +256,7 @@ export default { }) }, pauseItem() { - if (this.$refs.audioPlayer && !this.$refs.audioPlayer.isPaused) { + if (this.$refs.audioPlayer && this.$refs.audioPlayer.isPlaying) { this.$refs.audioPlayer.pause() } }, @@ -285,6 +285,51 @@ export default { }, playbackTimeUpdate(currentTime) { this.$refs.audioPlayer?.seek(currentTime) + }, + /** + * When device gains focus then refresh the timestamps in the audio player + */ + deviceFocused(hasFocus) { + if (hasFocus) { + if (!this.$refs.audioPlayer?.isPlaying) { + const playbackSession = this.$store.state.currentPlaybackSession + if (this.$refs.audioPlayer.isLocalPlayMethod) { + const localLibraryItemId = playbackSession.localLibraryItem?.id + const localEpisodeId = playbackSession.localEpisodeId + if (!localLibraryItemId) { + console.error('[AudioPlayerContainer] device visibility: no local library item for session', JSON.stringify(playbackSession)) + return + } + const localMediaProgress = this.$store.state.globals.localMediaProgress.find((mp) => { + if (localEpisodeId) return mp.localEpisodeId === localEpisodeId + return mp.localLibraryItemId === localLibraryItemId + }) + if (localMediaProgress) { + console.log('[AudioPlayerContainer] device visibility: found local media progress', localMediaProgress.currentTime, 'last time in player is', this.currentTime) + this.$refs.audioPlayer.currentTime = localMediaProgress.currentTime + this.$refs.audioPlayer.timeupdate() + } else { + console.error('[AudioPlayerContainer] device visibility: Local media progress not found') + } + } else { + const libraryItemId = playbackSession.libraryItemId + const episodeId = playbackSession.episodeId + const url = episodeId ? `/api/me/progress/${libraryItemId}/${episodeId}` : `/api/me/progress/${libraryItemId}` + this.$axios + .$get(url) + .then((data) => { + if (!this.$refs.audioPlayer?.isPlaying && data.libraryItemId === libraryItemId) { + console.log('[AudioPlayerContainer] device visibility: got server media progress', data.currentTime, 'last time in player is', this.currentTime) + this.$refs.audioPlayer.currentTime = data.currentTime + this.$refs.audioPlayer.timeupdate() + } + }) + .catch((error) => { + console.error('[AudioPlayerContainer] device visibility: Failed to get progress', error) + }) + } + } + } } }, mounted() { @@ -303,6 +348,7 @@ export default { this.$eventBus.$on('cast-local-item', this.castLocalItem) this.$eventBus.$on('user-settings', this.settingsUpdated) this.$eventBus.$on('playback-time-update', this.playbackTimeUpdate) + this.$eventBus.$on('device-focus-update', this.deviceFocused) }, beforeDestroy() { if (this.onLocalMediaProgressUpdateListener) this.onLocalMediaProgressUpdateListener.remove() @@ -317,6 +363,7 @@ export default { this.$eventBus.$off('cast-local-item', this.castLocalItem) this.$eventBus.$off('user-settings', this.settingsUpdated) this.$eventBus.$off('playback-time-update', this.playbackTimeUpdate) + this.$eventBus.$off('device-focus-update', this.deviceFocused) } } \ No newline at end of file diff --git a/components/cards/LazyBookCard.vue b/components/cards/LazyBookCard.vue index 8341cdc2..61318361 100644 --- a/components/cards/LazyBookCard.vue +++ b/components/cards/LazyBookCard.vue @@ -37,6 +37,12 @@

{{ authorCleaned }}

+ +
+
+ {{ streamIsPlaying ? 'pause_circle' : 'play_circle_filled' }} +
+
@@ -306,14 +312,10 @@ export default { return this.localLibraryItem.media.episodes.find((ep) => ep.serverEpisodeId === this.recentEpisode.id) }, isStreaming() { - if (this.isPodcast) { - return this.$store.getters['getIsMediaStreaming'](this.libraryItemId, this.recentEpisode.id) - } else { - return false // not yet necessary for books - } + return this.store.getters['getIsMediaStreaming'](this.libraryItemId, this.recentEpisode?.id) }, streamIsPlaying() { - return this.$store.state.playerIsPlaying && this.isStreaming + return this.store.state.playerIsPlaying && this.isStreaming }, isMissing() { return this._libraryItem.isMissing @@ -393,6 +395,10 @@ export default { rssFeed() { if (this.booksInSeries) return null return this._libraryItem.rssFeed || null + }, + showPlayButton() { + return false + // return !this.isMissing && !this.isInvalid && !this.isStreaming && (this.numTracks || this.recentEpisode) } }, methods: { @@ -434,6 +440,7 @@ export default { // Server books may have a local library item this.localLibraryItem = localLibraryItem }, + async play() {}, async playEpisode() { await this.$hapticsImpact() const eventBus = this.$eventBus || this.$nuxt.$eventBus diff --git a/layouts/default.vue b/layouts/default.vue index c3dbe210..2ad718b5 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -20,7 +20,8 @@ export default { return { inittingLibraries: false, hasMounted: false, - disconnectTime: 0 + disconnectTime: 0, + timeLostFocus: 0 } }, watch: { @@ -299,9 +300,30 @@ export default { console.log(`[default] local media progress updated for ${newLocalMediaProgress.id}`) this.$store.commit('globals/updateLocalMediaProgress', newLocalMediaProgress) } + }, + async visibilityChanged() { + if (document.visibilityState === 'visible') { + const elapsedTimeOutOfFocus = Date.now() - this.timeLostFocus + console.log(`✅ [default] device visibility: has focus (${elapsedTimeOutOfFocus}ms out of focus)`) + // If device out of focus for more than 30s then reload local media progress + if (elapsedTimeOutOfFocus > 30000) { + console.log(`✅ [default] device visibility: reloading local media progress`) + // Reload local media progresses + await this.$store.dispatch('globals/loadLocalMediaProgress') + } + if (document.visibilityState === 'visible') { + this.$eventBus.$emit('device-focus-update', true) + } + } else { + console.log('⛔️ [default] device visibility: does NOT have focus') + this.timeLostFocus = Date.now() + this.$eventBus.$emit('device-focus-update', false) + } } }, async mounted() { + document.addEventListener('visibilitychange', this.visibilityChanged) + this.$socket.on('user_updated', this.userUpdated) this.$socket.on('user_media_progress_updated', this.userMediaProgressUpdated) @@ -339,6 +361,7 @@ export default { } }, beforeDestroy() { + document.removeEventListener('visibilitychange', this.visibilityChanged) this.$socket.off('user_updated', this.userUpdated) this.$socket.off('user_media_progress_updated', this.userMediaProgressUpdated) }