mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-29 06:37:16 +02:00
Sync local media open in player when socket reconnects after 30s+
This commit is contained in:
@@ -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
@@ -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
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user