Sync local media open in player when socket reconnects after 30s+

This commit is contained in:
advplyr
2026-05-10 11:05:17 -05:00
parent 8be08702c1
commit 68dafbba75
2 changed files with 101 additions and 49 deletions
+74 -48
View File
@@ -48,6 +48,9 @@ export default {
}, },
isIos() { isIos() {
return this.$platform === 'ios' return this.$platform === 'ios'
},
currentPlaybackSession() {
return this.$store.state.currentPlaybackSession
} }
}, },
methods: { methods: {
@@ -312,7 +315,10 @@ export default {
* doesn't tap play before we have a chance to update the timestamps. The request timeout * 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. * is 7 seconds so a slow/unresponsive server doesn't block the user for long.
*/ */
async getServerMediaProgress({ libraryItemId, episodeId }) { async getServerMediaProgressForCurrentSession() {
if (!this.$store.state.user.user || !this.$store.state.networkConnected) return null
const libraryItemId = this.currentPlaybackSession?.libraryItemId
const episodeId = this.currentPlaybackSession?.episodeId
if (!libraryItemId) return null if (!libraryItemId) return null
const url = episodeId ? `/api/me/progress/${libraryItemId}/${episodeId}` : `/api/me/progress/${libraryItemId}` const url = episodeId ? `/api/me/progress/${libraryItemId}/${episodeId}` : `/api/me/progress/${libraryItemId}`
@@ -328,28 +334,75 @@ export default {
this.$refs.audioPlayer?.setIsCheckingServerProgress(false) this.$refs.audioPlayer?.setIsCheckingServerProgress(false)
} }
}, },
getLocalMediaProgressForCurrentSession() {
if (!this.currentPlaybackSession) return null
return this.$store.getters['globals/getLocalMediaProgressById'](this.currentPlaybackSession.localLibraryItem?.id, this.currentPlaybackSession.localEpisodeId)
},
/** /**
* When device gains focus then refresh the timestamps in the audio player * Sync the server media progress with the local media progress
*/
async syncServerMediaProgressWithLocalMediaProgress(localMediaProgressId, serverMediaProgress) {
try {
const newLocalMediaProgress = await this.$db.syncServerMediaProgressWithLocalMediaProgress({
localMediaProgressId,
mediaProgress: serverMediaProgress
})
if (newLocalMediaProgress?.id) {
this.$store.commit('globals/updateLocalMediaProgress', newLocalMediaProgress)
}
} catch (error) {
console.error('[AudioPlayerContainer] Failed to sync server progress with local media progress', error)
}
},
/**
* Check if the server media progress is more recent than the local media progress and sync if so
*/
async checkSyncServerProgressWithLocalProgress(localMediaProgress) {
if (!localMediaProgress) return
console.log('[AudioPlayerContainer] checkSyncServerProgressWithLocalProgress: checking server media progress for local media item open in player')
const serverMediaProgress = await this.getServerMediaProgressForCurrentSession()
if (!serverMediaProgress?.lastUpdate || serverMediaProgress.lastUpdate <= localMediaProgress.lastUpdate) return
console.log('[AudioPlayerContainer] checkSyncServerProgressWithLocalProgress: server progress is more recent than local progress. Server current time:', serverMediaProgress.currentTime, 'vs local', localMediaProgress.currentTime, `(server lastUpdate=${serverMediaProgress.lastUpdate} > local lastUpdate=${localMediaProgress.lastUpdate})`)
if (!this.$refs.audioPlayer?.isPlaying && serverMediaProgress.currentTime !== localMediaProgress.currentTime) {
// Use seek() so the native audio player's current session is updated
this.$refs.audioPlayer.seek(serverMediaProgress.currentTime)
}
await this.syncServerMediaProgressWithLocalMediaProgress(localMediaProgress.id, serverMediaProgress)
},
/**
* When socket is reconnected after a delay, if a local media item is open in the player (paused)
* we fetch the server media progress and sync it if it is more recent than the local progress
*
* If there is no socket connection we may have missed external progress updates
*/
async socketReconnected() {
if (!this.currentPlaybackSession) return
// dont update timestamps if player is playing
if (this.$refs.audioPlayer?.isPlaying) return
if (this.$refs.audioPlayer.isLocalPlayMethod) {
const localMediaProgress = this.getLocalMediaProgressForCurrentSession()
if (!localMediaProgress) {
console.error('[AudioPlayerContainer] socket reconnected: Local media progress not found')
return
}
await this.checkSyncServerProgressWithLocalProgress(localMediaProgress)
}
},
/**
* When device re-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 * if local item is open then fetch the server media progress and update if more recent
*/ */
async deviceFocused(hasFocus) { async deviceFocused(hasFocus) {
if (!this.$store.state.currentPlaybackSession) return if (!this.currentPlaybackSession || !hasFocus) return
if (!hasFocus) return // dont update timestamps if player is playing
// dont refresh timestamps if player is playing
if (this.$refs.audioPlayer?.isPlaying) return if (this.$refs.audioPlayer?.isPlaying) return
const playbackSession = this.$store.state.currentPlaybackSession
if (this.$refs.audioPlayer.isLocalPlayMethod) { if (this.$refs.audioPlayer.isLocalPlayMethod) {
const localLibraryItemId = playbackSession.localLibraryItem?.id const localMediaProgress = this.getLocalMediaProgressForCurrentSession()
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) { if (!localMediaProgress) {
console.error('[AudioPlayerContainer] device visibility: Local media progress not found') console.error('[AudioPlayerContainer] device visibility: Local media progress not found')
return return
@@ -359,40 +412,11 @@ export default {
this.$refs.audioPlayer.currentTime = localMediaProgress.currentTime this.$refs.audioPlayer.currentTime = localMediaProgress.currentTime
this.$refs.audioPlayer.timeupdate() this.$refs.audioPlayer.timeupdate()
// If the local item came from a server and we're connected, check whether the await this.checkSyncServerProgressWithLocalProgress(localMediaProgress)
// 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 { } else {
// server item so fetch server media progress and update player time // server item so fetch server media progress and update player time
const libraryItemId = playbackSession.libraryItemId console.log('[AudioPlayerContainer] device visibility: checking server media progress for server media item open in player')
const episodeId = playbackSession.episodeId const data = await this.getServerMediaProgressForCurrentSession()
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 (!data) return
if (!this.$refs.audioPlayer?.isPlaying) { if (!this.$refs.audioPlayer?.isPlaying) {
console.log('[AudioPlayerContainer] device visibility: got server media progress', data.currentTime, 'last time in player is', this.currentTime) console.log('[AudioPlayerContainer] device visibility: got server media progress', data.currentTime, 'last time in player is', this.currentTime)
@@ -422,6 +446,7 @@ export default {
this.$eventBus.$on('user-settings', this.settingsUpdated) this.$eventBus.$on('user-settings', this.settingsUpdated)
this.$eventBus.$on('playback-time-update', this.playbackTimeUpdate) this.$eventBus.$on('playback-time-update', this.playbackTimeUpdate)
this.$eventBus.$on('device-focus-update', this.deviceFocused) this.$eventBus.$on('device-focus-update', this.deviceFocused)
this.$eventBus.$on('socket-reconnected', this.socketReconnected)
}, },
beforeDestroy() { beforeDestroy() {
this.onLocalMediaProgressUpdateListener?.remove() this.onLocalMediaProgressUpdateListener?.remove()
@@ -437,6 +462,7 @@ export default {
this.$eventBus.$off('user-settings', this.settingsUpdated) this.$eventBus.$off('user-settings', this.settingsUpdated)
this.$eventBus.$off('playback-time-update', this.playbackTimeUpdate) this.$eventBus.$off('playback-time-update', this.playbackTimeUpdate)
this.$eventBus.$off('device-focus-update', this.deviceFocused) this.$eventBus.$off('device-focus-update', this.deviceFocused)
this.$eventBus.$off('socket-reconnected', this.socketReconnected)
} }
} }
</script> </script>
+27 -1
View File
@@ -24,6 +24,7 @@ export default {
inittingLibraries: false, inittingLibraries: false,
hasMounted: false, hasMounted: false,
disconnectTime: 0, disconnectTime: 0,
socketDisconnectedTime: 0,
timeLostFocus: 0, timeLostFocus: 0,
currentLang: null currentLang: null
} }
@@ -44,7 +45,7 @@ export default {
} else { } else {
var timeSinceDisconnect = Date.now() - this.disconnectTime var timeSinceDisconnect = Date.now() - this.disconnectTime
if (timeSinceDisconnect > 5000) { if (timeSinceDisconnect > 5000) {
console.log('Time since disconnect was', timeSinceDisconnect, 'sync with server') console.log('[default] Time since disconnect was', timeSinceDisconnect, 'sync with server')
setTimeout(() => { setTimeout(() => {
this.syncLocalSessions(false) this.syncLocalSessions(false)
}, 4000) }, 4000)
@@ -55,6 +56,28 @@ export default {
this.disconnectTime = Date.now() this.disconnectTime = Date.now()
} }
} }
},
socketConnected: {
handler(newVal, oldVal) {
if (!this.hasMounted) {
// watcher runs before mount, handling libraries/connection should be handled in mount
return
}
if (newVal) {
// if we havent been receiving socket events then external progress updates may have been missed
const timeSinceDisconnect = Date.now() - this.socketDisconnectedTime
if (timeSinceDisconnect > 30000 && this.isPlayerOpen) {
console.log('[default] socket reconnected after ' + timeSinceDisconnect + 'ms and player is open, triggering server media progress sync')
// used for triggering a server media progress sync if local media item is open in player
this.$eventBus.$emit('socket-reconnected')
} else {
console.log('[default] socket reconnected after ' + timeSinceDisconnect + 'ms')
}
} else {
console.log('[default] socket disconnected')
this.socketDisconnectedTime = Date.now()
}
}
} }
}, },
computed: { computed: {
@@ -67,6 +90,9 @@ export default {
networkConnected() { networkConnected() {
return this.$store.state.networkConnected return this.$store.state.networkConnected
}, },
socketConnected() {
return this.$store.state.socketConnected
},
user() { user() {
return this.$store.state.user.user return this.$store.state.user.user
}, },