Update:Handle multiple sessions open, sync when paused, show alert of multiple sessions open when both are playing #1660

This commit is contained in:
advplyr 2023-05-27 17:21:43 -05:00
parent 9712bdf5f0
commit 53c96b2540
5 changed files with 44 additions and 10 deletions

View File

@ -268,6 +268,10 @@ export default {
seek(time) { seek(time) {
this.playerHandler.seek(time) this.playerHandler.seek(time)
}, },
playbackTimeUpdate(time) {
// When updating progress from another session
this.playerHandler.seek(time, false)
},
setCurrentTime(time) { setCurrentTime(time) {
this.currentTime = time this.currentTime = time
if (this.$refs.audioPlayer) { if (this.$refs.audioPlayer) {
@ -477,12 +481,14 @@ export default {
mounted() { mounted() {
this.$eventBus.$on('cast-session-active', this.castSessionActive) this.$eventBus.$on('cast-session-active', this.castSessionActive)
this.$eventBus.$on('playback-seek', this.seek) this.$eventBus.$on('playback-seek', this.seek)
this.$eventBus.$on('playback-time-update', this.playbackTimeUpdate)
this.$eventBus.$on('play-item', this.playLibraryItem) this.$eventBus.$on('play-item', this.playLibraryItem)
this.$eventBus.$on('pause-item', this.pauseItem) this.$eventBus.$on('pause-item', this.pauseItem)
}, },
beforeDestroy() { beforeDestroy() {
this.$eventBus.$off('cast-session-active', this.castSessionActive) this.$eventBus.$off('cast-session-active', this.castSessionActive)
this.$eventBus.$off('playback-seek', this.seek) this.$eventBus.$off('playback-seek', this.seek)
this.$eventBus.$off('playback-time-update', this.playbackTimeUpdate)
this.$eventBus.$off('play-item', this.playLibraryItem) this.$eventBus.$off('play-item', this.playLibraryItem)
this.$eventBus.$off('pause-item', this.pauseItem) this.$eventBus.$off('pause-item', this.pauseItem)
} }

View File

@ -35,7 +35,9 @@ export default {
isSocketConnected: false, isSocketConnected: false,
isFirstSocketConnection: true, isFirstSocketConnection: true,
socketConnectionToastId: null, socketConnectionToastId: null,
currentLang: null currentLang: null,
multiSessionOtherSessionId: null, // Used for multiple sessions open warning toast
multiSessionCurrentSessionId: null // Used for multiple sessions open warning toast
} }
}, },
watch: { watch: {
@ -300,14 +302,27 @@ export default {
this.$store.commit('users/updateUserOnline', user) this.$store.commit('users/updateUserOnline', user)
}, },
userSessionClosed(sessionId) { userSessionClosed(sessionId) {
// If this session or other session is closed then dismiss multiple sessions warning toast
if (sessionId === this.multiSessionOtherSessionId || this.multiSessionCurrentSessionId === sessionId) {
this.multiSessionOtherSessionId = null
this.multiSessionCurrentSessionId = null
this.$toast.dismiss('multiple-sessions')
}
if (this.$refs.streamContainer) this.$refs.streamContainer.sessionClosedEvent(sessionId) if (this.$refs.streamContainer) this.$refs.streamContainer.sessionClosedEvent(sessionId)
}, },
userMediaProgressUpdate(payload) { userMediaProgressUpdate(payload) {
this.$store.commit('user/updateMediaProgress', payload) this.$store.commit('user/updateMediaProgress', payload)
if (payload.data) { if (payload.data) {
if (this.$store.getters['getIsMediaStreaming'](payload.data.libraryItemId, payload.data.episodeId)) { if (this.$store.getters['getIsMediaStreaming'](payload.data.libraryItemId, payload.data.episodeId) && this.$store.state.playbackSessionId !== payload.sessionId) {
// TODO: Update currently open session if being played from another device this.multiSessionOtherSessionId = payload.sessionId
this.multiSessionCurrentSessionId = this.$store.state.playbackSessionId
console.log(`Media progress was updated from another session (${this.multiSessionOtherSessionId}) for currently open media. Device description=${payload.deviceDescription}. Current session id=${this.multiSessionCurrentSessionId}`)
if (this.$store.state.streamIsPlaying) {
this.$toast.update('multiple-sessions', { content: `Another session is open for this item on device ${payload.deviceDescription}`, options: { timeout: 20000, type: 'warning', pauseOnFocusLoss: false } }, true)
} else {
this.$eventBus.$emit('playback-time-update', payload.data.currentTime)
}
} }
} }
}, },

View File

@ -52,6 +52,11 @@ export default class PlayerHandler {
return this.libraryItem.media.episodes.find(ep => ep.id === this.episodeId) return this.libraryItem.media.episodes.find(ep => ep.id === this.episodeId)
} }
setSessionId(sessionId) {
this.currentSessionId = sessionId
this.ctx.$store.commit('setPlaybackSessionId', sessionId)
}
load(libraryItem, episodeId, playWhenReady, playbackRate, startTimeOverride = undefined) { load(libraryItem, episodeId, playWhenReady, playbackRate, startTimeOverride = undefined) {
this.libraryItem = libraryItem this.libraryItem = libraryItem
this.isVideo = libraryItem.mediaType === 'video' this.isVideo = libraryItem.mediaType === 'video'
@ -182,7 +187,7 @@ export default class PlayerHandler {
} }
async prepare(forceTranscode = false) { async prepare(forceTranscode = false) {
this.currentSessionId = null // Reset session this.setSessionId(null) // Reset session
const payload = { const payload = {
deviceInfo: { deviceInfo: {
@ -218,7 +223,7 @@ export default class PlayerHandler {
prepareSession(session) { prepareSession(session) {
this.failedProgressSyncs = 0 this.failedProgressSyncs = 0
this.startTime = this.startTimeOverride !== undefined ? this.startTimeOverride : session.currentTime this.startTime = this.startTimeOverride !== undefined ? this.startTimeOverride : session.currentTime
this.currentSessionId = session.id this.setSessionId(session.id)
this.displayTitle = session.displayTitle this.displayTitle = session.displayTitle
this.displayAuthor = session.displayAuthor this.displayAuthor = session.displayAuthor
@ -263,7 +268,7 @@ export default class PlayerHandler {
this.player = null this.player = null
this.playerState = 'IDLE' this.playerState = 'IDLE'
this.libraryItem = null this.libraryItem = null
this.currentSessionId = null this.setSessionId(null)
this.startTime = 0 this.startTime = 0
this.stopPlayInterval() this.stopPlayInterval()
} }
@ -300,7 +305,7 @@ export default class PlayerHandler {
if (this.player) { if (this.player) {
const listeningTimeToAdd = Math.max(0, Math.floor(this.listeningTimeSinceSync)) const listeningTimeToAdd = Math.max(0, Math.floor(this.listeningTimeSinceSync))
// When opening player and quickly closing dont save progress // When opening player and quickly closing dont save progress
if (listeningTimeToAdd > 20 || this.lastSyncTime > 0) { if (listeningTimeToAdd > 20) {
syncData = { syncData = {
timeListened: listeningTimeToAdd, timeListened: listeningTimeToAdd,
duration: this.getDuration(), duration: this.getDuration(),
@ -390,13 +395,13 @@ export default class PlayerHandler {
this.player.setPlaybackRate(playbackRate) this.player.setPlaybackRate(playbackRate)
} }
seek(time) { seek(time, shouldSync = true) {
if (!this.player) return if (!this.player) return
this.player.seek(time, this.playerPlaying) this.player.seek(time, this.playerPlaying)
this.ctx.setCurrentTime(time) this.ctx.setCurrentTime(time)
// Update progress if paused // Update progress if paused
if (!this.playerPlaying) { if (!this.playerPlaying && shouldSync) {
this.sendProgressSync(time) this.sendProgressSync(time)
} }
} }

View File

@ -6,6 +6,7 @@ export const state = () => ({
Source: null, Source: null,
versionData: null, versionData: null,
serverSettings: null, serverSettings: null,
playbackSessionId: null,
streamLibraryItem: null, streamLibraryItem: null,
streamEpisodeId: null, streamEpisodeId: null,
streamIsPlaying: false, streamIsPlaying: false,
@ -150,6 +151,9 @@ export const mutations = {
if (!settings) return if (!settings) return
state.serverSettings = settings state.serverSettings = settings
}, },
setPlaybackSessionId(state, playbackSessionId) {
state.playbackSessionId = playbackSessionId
},
setMediaPlaying(state, payload) { setMediaPlaying(state, payload) {
if (!payload) { if (!payload) {
state.streamLibraryItem = null state.streamLibraryItem = null

View File

@ -130,6 +130,8 @@ class PlaybackSessionManager {
const itemProgress = user.getMediaProgress(session.libraryItemId, session.episodeId) const itemProgress = user.getMediaProgress(session.libraryItemId, session.episodeId)
SocketAuthority.clientEmitter(user.id, 'user_item_progress_updated', { SocketAuthority.clientEmitter(user.id, 'user_item_progress_updated', {
id: itemProgress.id, id: itemProgress.id,
sessionId: session.id,
deviceDescription: session.deviceDescription,
data: itemProgress.toJSON() data: itemProgress.toJSON()
}) })
} }
@ -239,6 +241,8 @@ class PlaybackSessionManager {
const itemProgress = user.getMediaProgress(session.libraryItemId, session.episodeId) const itemProgress = user.getMediaProgress(session.libraryItemId, session.episodeId)
SocketAuthority.clientEmitter(user.id, 'user_item_progress_updated', { SocketAuthority.clientEmitter(user.id, 'user_item_progress_updated', {
id: itemProgress.id, id: itemProgress.id,
sessionId: session.id,
deviceDescription: session.deviceDescription,
data: itemProgress.toJSON() data: itemProgress.toJSON()
}) })
} }
@ -306,7 +310,7 @@ class PlaybackSessionManager {
// See https://github.com/advplyr/audiobookshelf/issues/868 // See https://github.com/advplyr/audiobookshelf/issues/868
// Remove playback sessions with listening time too high // Remove playback sessions with listening time too high
async removeInvalidSessions() { async removeInvalidSessions() {
const selectFunc = (session) => isNaN(session.timeListening) || Number(session.timeListening) > 3600000000 const selectFunc = (session) => isNaN(session.timeListening) || Number(session.timeListening) > 36000000
const numSessionsRemoved = await this.db.removeEntities('session', selectFunc, true) const numSessionsRemoved = await this.db.removeEntities('session', selectFunc, true)
if (numSessionsRemoved) { if (numSessionsRemoved) {
Logger.info(`[PlaybackSessionManager] Removed ${numSessionsRemoved} invalid playback sessions`) Logger.info(`[PlaybackSessionManager] Removed ${numSessionsRemoved} invalid playback sessions`)