From 8be08702c1e05ab6390217a779de0bcaa95f4426 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sat, 9 May 2026 16:48:55 -0500 Subject: [PATCH] Fix regain focus check server progress for local media items open in player & sync more recent --- components/app/AudioPlayer.vue | 31 +++--- components/app/AudioPlayerContainer.vue | 132 +++++++++++++++++------- plugins/server.js | 2 +- 3 files changed, 113 insertions(+), 52 deletions(-) diff --git a/components/app/AudioPlayer.vue b/components/app/AudioPlayer.vue index 69c1188b..907a4587 100644 --- a/components/app/AudioPlayer.vue +++ b/components/app/AudioPlayer.vue @@ -69,22 +69,22 @@
- first_page -
+ first_page +
replay {{ jumpBackwardsLabel }}
- {{ seekLoading ? 'autorenew' : !isPlaying ? 'play_arrow' : 'pause' }} + {{ seekLoading ? 'autorenew' : !isPlaying ? 'play_arrow' : 'pause' }}
-
+
forward_media {{ jumpForwardLabel }}
- last_page + last_page
@@ -94,7 +94,7 @@

{{ timeRemainingPretty }}

-
+
@@ -157,6 +157,7 @@ export default { lockUi: false }, isLoading: false, + isCheckingServerProgress: false, isDraggingCursor: false, draggingTouchStartX: 0, draggingTouchStartTime: 0, @@ -270,6 +271,9 @@ export default { return 190 * heightScale } }, + showLoadingState() { + return this.isLoading || this.isCheckingServerProgress + }, showCastBtn() { return this.$store.state.isCastAvailable }, @@ -461,13 +465,13 @@ export default { }, async jumpNextChapter() { await this.$hapticsImpact() - if (this.isLoading) return + if (this.showLoadingState) return if (!this.nextChapter) return this.seek(this.nextChapter.start) }, async jumpChapterStart() { await this.$hapticsImpact() - if (this.isLoading) return + if (this.showLoadingState) return if (!this.currentChapter) { return this.restart() } @@ -497,12 +501,12 @@ export default { }, async jumpBackwards() { await this.$hapticsImpact() - if (this.isLoading) return + if (this.showLoadingState) return AbsAudioPlayer.seekBackward({ value: this.jumpBackwardsTime }) }, async jumpForward() { await this.$hapticsImpact() - if (this.isLoading) return + if (this.showLoadingState) return AbsAudioPlayer.seekForward({ value: this.jumpForwardTime }) }, setStreamReady() { @@ -606,7 +610,7 @@ export default { } }, seek(time) { - if (this.isLoading) return + if (this.showLoadingState) return if (this.seekLoading) { console.error('Already seek loading', this.seekedTime) return @@ -638,11 +642,14 @@ export default { }, async playPauseClick() { await this.$hapticsImpact() - if (this.isLoading) return + if (this.showLoadingState) return this.isPlaying = !!((await AbsAudioPlayer.playPause()) || {}).playing this.isEnded = false }, + setIsCheckingServerProgress(value) { + this.isCheckingServerProgress = !!value + }, play() { AbsAudioPlayer.playPlayer() this.startPlayInterval() diff --git a/components/app/AudioPlayerContainer.vue b/components/app/AudioPlayerContainer.vue index 1040e61b..1dce3f11 100644 --- a/components/app/AudioPlayerContainer.vue +++ b/components/app/AudioPlayerContainer.vue @@ -304,48 +304,102 @@ export default { this.$refs.audioPlayer?.seek(currentTime) }, /** - * When device gains focus then refresh the timestamps in the audio player + * Fetch the current user's media progress from the server for a given library item / episode. + * Returns the server media progress object, or null if the request fails, times out, or the + * response doesn't match the requested library item. + * + * The audio player's loading state is shown while the request is in flight so the user + * doesn't tap play before we have a chance to update the timestamps. The request timeout + * is 7 seconds so a slow/unresponsive server doesn't block the user for long. */ - deviceFocused(hasFocus) { - if (!this.$store.state.currentPlaybackSession) return + async getServerMediaProgress({ libraryItemId, episodeId }) { + if (!libraryItemId) return null + const url = episodeId ? `/api/me/progress/${libraryItemId}/${episodeId}` : `/api/me/progress/${libraryItemId}` - if (hasFocus) { + this.$refs.audioPlayer?.setIsCheckingServerProgress(true) + try { + const data = await this.$nativeHttp.get(url, { connectTimeout: 7000, readTimeout: 7000 }) + if (!data || data.libraryItemId !== libraryItemId) return null + return data + } catch (error) { + console.error('[AudioPlayerContainer] Failed to get server media progress', error) + return null + } finally { + this.$refs.audioPlayer?.setIsCheckingServerProgress(false) + } + }, + /** + * When device gains focus then refresh the timestamps in the audio player + * if local item is open then fetch the server media progress and update if more recent + */ + async deviceFocused(hasFocus) { + if (!this.$store.state.currentPlaybackSession) return + if (!hasFocus) return + // dont refresh timestamps if player is playing + if (this.$refs.audioPlayer?.isPlaying) return + + 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.error('[AudioPlayerContainer] device visibility: Local media progress not found') + return + } + + 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() + + // If the local item came from a server and we're connected, check whether the + // server's progress is more recent (e.g. user kept listening on the server) + // and if so, update the player time and sync the server progress to local DB. + const serverLibraryItemId = playbackSession.libraryItemId + const serverEpisodeId = playbackSession.episodeId + if (!serverLibraryItemId || !this.$store.state.user.user || !this.$store.state.networkConnected) return + + console.log('[AudioPlayerContainer] device visibility: checking server media progress for local item', serverLibraryItemId, serverEpisodeId) + const data = await this.getServerMediaProgress({ libraryItemId: serverLibraryItemId, episodeId: serverEpisodeId }) + if (!data || !data.lastUpdate || data.lastUpdate <= localMediaProgress.lastUpdate) return + + console.log('[AudioPlayerContainer] device visibility: server progress is more recent for local item', data.currentTime, 'vs local', localMediaProgress.currentTime, `(server lastUpdate=${data.lastUpdate} > local lastUpdate=${localMediaProgress.lastUpdate})`) + if (!this.$refs.audioPlayer?.isPlaying && data.currentTime !== localMediaProgress.currentTime) { + // Use seek() so the native audio player's current session is updated + this.$refs.audioPlayer.seek(data.currentTime) + } + + try { + const newLocalMediaProgress = await this.$db.syncServerMediaProgressWithLocalMediaProgress({ + localMediaProgressId: localMediaProgress.id, + mediaProgress: data + }) + if (newLocalMediaProgress?.id) { + this.$store.commit('globals/updateLocalMediaProgress', newLocalMediaProgress) + } + } catch (error) { + console.error('[AudioPlayerContainer] device visibility: Failed to sync server progress to local', error) + } + } else { + // server item so fetch server media progress and update player time + const libraryItemId = playbackSession.libraryItemId + const episodeId = playbackSession.episodeId + console.log('[AudioPlayerContainer] device visibility: checking server media progress for server item', libraryItemId, episodeId) + const data = await this.getServerMediaProgress({ libraryItemId, episodeId }) + if (!data) return 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.$nativeHttp - .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) - }) + console.log('[AudioPlayerContainer] device visibility: got server media progress', data.currentTime, 'last time in player is', this.currentTime) + // Only seek if the difference is greater than 1 second + if (Math.abs(data.currentTime - this.currentTime) > 1) { + // Use seek() so the native audio player's current session is updated + this.$refs.audioPlayer.seek(data.currentTime) } } } diff --git a/plugins/server.js b/plugins/server.js index 90f6f715..fccc06b9 100644 --- a/plugins/server.js +++ b/plugins/server.js @@ -110,7 +110,7 @@ class ServerSocket extends EventEmitter { } onAuthFailed(data) { - console.log('[SOCKET] Auth failed', data) + console.log('[SOCKET] Auth failed: ' + (data?.message || 'Unknown reason')) this.isAuthenticated = false }