diff --git a/client/layouts/default.vue b/client/layouts/default.vue index 53d096f4..71e3b0ed 100644 --- a/client/layouts/default.vue +++ b/client/layouts/default.vue @@ -259,7 +259,6 @@ export default { this.$store.commit('users/updateUser', user) }, userItemProgressUpdate(payload) { - console.log('User item progress update', payload) this.$store.commit('user/updateItemProgress', payload) }, collectionAdded(collection) { diff --git a/client/pages/config/library-stats.vue b/client/pages/config/library-stats.vue index 381c4987..c12bdab0 100644 --- a/client/pages/config/library-stats.vue +++ b/client/pages/config/library-stats.vue @@ -117,7 +117,6 @@ export default { var errorMsg = err.response ? err.response.data || 'Unknown Error' : 'Unknown Error' this.$toast.error(`Failed to get library stats: ${errorMsg}`) }) - console.log('lib stats', this.libraryStats) } }, mounted() { diff --git a/client/pages/item/_id/index.vue b/client/pages/item/_id/index.vue index 2b89670d..82531d2e 100644 --- a/client/pages/item/_id/index.vue +++ b/client/pages/item/_id/index.vue @@ -30,7 +30,6 @@

{{ subtitle }}

-

by {{ author }} @@ -69,7 +68,7 @@ -

+
Duration
@@ -77,7 +76,7 @@ {{ durationPretty }}
-
+
Size
@@ -220,6 +219,10 @@ export default { audiobooks() { return this.media.audiobooks || [] }, + defaultAudiobook() { + if (!this.audiobooks.length) return null + return this.audiobooks[0] + }, title() { return this.mediaMetadata.title || 'No Title' }, @@ -258,33 +261,28 @@ export default { }) }, durationPretty() { - return this.$elapsedPretty(this.media.duration) + if (!this.defaultAudiobook) return 'N/A' + return this.$elapsedPretty(this.defaultAudiobook.duration) }, duration() { - return this.media.duration + if (!this.defaultAudiobook) return 0 + return this.defaultAudiobook.duration }, sizePretty() { - return this.$bytesPretty(this.media.size) + if (!this.defaultAudiobook) return 'N/A' + return this.$bytesPretty(this.defaultAudiobook.size) }, libraryFiles() { return this.libraryItem.libraryFiles || [] }, - otherAudioFiles() { - return this.audioFiles.filter((af) => { - return !this.tracks.find((t) => t.path === af.path) - }) - }, - tracks() { - return this.media.tracks || [] - }, - audioFiles() { - return this.media.audioFiles || [] + audiobooks() { + return this.media.audiobooks || [] }, ebooks() { return this.media.ebooks || [] }, showExperimentalReadAlert() { - return !this.tracks.length && this.ebooks.length && !this.showExperimentalFeatures + return !this.audiobooks.length && this.ebooks.length && !this.showExperimentalFeatures }, description() { return this.mediaMetadata.description || '' @@ -292,14 +290,13 @@ export default { userItemProgress() { return this.$store.getters['user/getUserLibraryItemProgress'](this.libraryItemId) }, - userCurrentTime() { - return this.userItemProgress ? this.userItemProgress.currentTime : 0 - }, userIsFinished() { return this.userItemProgress ? !!this.userItemProgress.isFinished : false }, userTimeRemaining() { - return this.duration - this.userCurrentTime + if (!this.userItemProgress) return 0 + var duration = this.userItemProgress.duration || this.duration + return duration - this.userItemProgress.currentTime }, progressPercent() { return this.userItemProgress ? Math.max(Math.min(1, this.userItemProgress.progress), 0) : 0 diff --git a/client/players/AudioTrack.js b/client/players/AudioTrack.js index 529d5a73..6cf785e2 100644 --- a/client/players/AudioTrack.js +++ b/client/players/AudioTrack.js @@ -26,6 +26,6 @@ export default class AudioTrack { return `${process.env.serverUrl}${this.contentUrl}?token=${this.userToken}` } - return this.contentUrl + '?token=${this.userToken}' + return this.contentUrl + `?token=${this.userToken}` } } \ No newline at end of file diff --git a/client/players/PlayerHandler.js b/client/players/PlayerHandler.js index e5fae273..321951cd 100644 --- a/client/players/PlayerHandler.js +++ b/client/players/PlayerHandler.js @@ -11,6 +11,7 @@ export default class PlayerHandler { this.playerState = 'IDLE' this.isHlsTranscode = false this.currentSessionId = null + this.mediaEntityId = null this.startTime = 0 this.lastSyncTime = 0 @@ -107,7 +108,7 @@ export default class PlayerHandler { this.stopPlayInterval() } if (this.playerState === 'LOADED' || this.playerState === 'PLAYING') { - this.ctx.setDuration(this.player.getDuration()) + this.ctx.setDuration(this.getDuration()) } if (this.playerState !== 'LOADING') { this.ctx.setCurrentTime(this.player.getCurrentTime()) @@ -149,6 +150,7 @@ export default class PlayerHandler { prepareSession(session) { this.startTime = session.currentTime this.currentSessionId = session.id + this.mediaEntityId = session.mediaEntityId console.log('[PlayerHandler] Preparing Session', session) var audioTracks = session.audioTracks.map(at => new AudioTrack(at, this.userToken)) @@ -207,7 +209,9 @@ export default class PlayerHandler { var listeningTimeToAdd = Math.max(0, Math.floor(this.listeningTimeSinceSync)) syncData = { timeListened: listeningTimeToAdd, - currentTime: this.player.getCurrentTime() + duration: this.getDuration(), + mediaEntityId: this.mediaEntityId, + currentTime: this.getCurrentTime() } } this.listeningTimeSinceSync = 0 @@ -224,6 +228,8 @@ export default class PlayerHandler { var listeningTimeToAdd = Math.max(0, Math.floor(this.listeningTimeSinceSync)) var syncData = { timeListened: listeningTimeToAdd, + duration: this.getDuration(), + mediaEntityId: this.mediaEntityId, currentTime } this.listeningTimeSinceSync = 0 diff --git a/server/PlaybackSessionManager.js b/server/PlaybackSessionManager.js index 6ca92d71..3e0190e2 100644 --- a/server/PlaybackSessionManager.js +++ b/server/PlaybackSessionManager.js @@ -80,15 +80,23 @@ class PlaybackSessionManager { } async syncSession(user, session, syncData) { + var libraryItem = this.db.libraryItems.find(li => li.id === session.libraryItemId) + if (!libraryItem) { + Logger.error(`[PlaybackSessionManager] syncSession Library Item not found "${sessino.libraryItemId}"`) + return + } + session.currentTime = syncData.currentTime session.addListeningTime(syncData.timeListened) Logger.debug(`[PlaybackSessionManager] syncSession "${session.id}" | Total Time Listened: ${session.timeListening}`) const itemProgressUpdate = { + mediaEntityId: syncData.mediaEntityId || null, + duration: syncData.duration, currentTime: syncData.currentTime, progress: session.progress } - var wasUpdated = user.createUpdateLibraryItemProgress(session.libraryItemId, itemProgressUpdate) + var wasUpdated = user.createUpdateLibraryItemProgress(libraryItem, itemProgressUpdate) if (wasUpdated) { await this.db.updateEntity('user', user) var itemProgress = user.getLibraryItemProgress(session.libraryItemId) @@ -112,6 +120,8 @@ class PlaybackSessionManager { } saveSession(session) { + if (!session.timeListening) return // Do not save a session with no listening time + if (session.lastSave) { return this.db.updateEntity('session', session) } else { diff --git a/server/controllers/MeController.js b/server/controllers/MeController.js index 6adaaabe..8f88ed4e 100644 --- a/server/controllers/MeController.js +++ b/server/controllers/MeController.js @@ -23,7 +23,7 @@ class MeController { return res.sendStatus(200) } await this.db.updateEntity('user', req.user) - this.clientEmitter(req.user.id, 'user_item_progress_updated', { id: libraryItem.id, data: null }) + // this.clientEmitter(req.user.id, 'user_item_progress_updated', { id: libraryItem.id, data: null }) this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser()) res.sendStatus(200) @@ -35,7 +35,7 @@ class MeController { if (!libraryItem) { return res.status(404).send('Item not found') } - var wasUpdated = req.user.createUpdateLibraryItemProgress(libraryItem.id, req.body) + var wasUpdated = req.user.createUpdateLibraryItemProgress(libraryItem, req.body) if (wasUpdated) { await this.db.updateEntity('user', req.user) this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser()) @@ -54,7 +54,7 @@ class MeController { itemProgressPayloads.forEach((itemProgress) => { var libraryItem = this.db.libraryItems.find(li => li.id === itemProgress.id) // Make sure this library item exists if (libraryItem) { - var wasUpdated = req.user.createUpdateLibraryItemProgress(libraryItem.id, itemProgress) + var wasUpdated = req.user.createUpdateLibraryItemProgress(libraryItem, itemProgress) if (wasUpdated) shouldUpdate = true } else { Logger.error(`[MeController] batchUpdateLibraryItemProgress: Library Item does not exist ${itemProgress.id}`) diff --git a/server/objects/user/LibraryItemProgress.js b/server/objects/user/LibraryItemProgress.js index 127268ea..cd3be7ca 100644 --- a/server/objects/user/LibraryItemProgress.js +++ b/server/objects/user/LibraryItemProgress.js @@ -4,7 +4,9 @@ class LibraryItemProgress { constructor(progress) { this.id = null // Same as library item id this.libraryItemId = null + this.mediaEntityId = null + this.duration = null this.progress = null // 0 to 1 this.currentTime = null // seconds this.isFinished = false @@ -22,6 +24,8 @@ class LibraryItemProgress { return { id: this.id, libraryItemId: this.libraryItemId, + mediaEntityId: this.mediaEntityId, + duration: this.duration, progress: this.progress, currentTime: this.currentTime, isFinished: this.isFinished, @@ -34,6 +38,8 @@ class LibraryItemProgress { construct(progress) { this.id = progress.id this.libraryItemId = progress.libraryItemId + this.mediaEntityId = progress.mediaEntityId || null + this.duration = progress.duration || 0 this.progress = progress.progress this.currentTime = progress.currentTime this.isFinished = !!progress.isFinished @@ -46,9 +52,11 @@ class LibraryItemProgress { return !this.isFinished && this.progress > 0 } - setData(libraryItemId, progress) { + setData(libraryItemId, mediaEntityId, progress) { this.id = libraryItemId this.libraryItemId = libraryItemId + this.mediaEntityId = mediaEntityId + this.duration = progress.duration || 0 this.progress = Math.min(1, (progress.progress || 0)) this.currentTime = progress.currentTime || 0 this.isFinished = !!progress.isFinished || this.progress == 1 diff --git a/server/objects/user/User.js b/server/objects/user/User.js index 2cd40273..1d04dda4 100644 --- a/server/objects/user/User.js +++ b/server/objects/user/User.js @@ -211,11 +211,20 @@ class User { return this.libraryItemProgress.find(lip => lip.id === libraryItemId) } - createUpdateLibraryItemProgress(libraryItemId, updatePayload) { - var itemProgress = this.libraryItemProgress.find(li => li.id === libraryItemId) + createUpdateLibraryItemProgress(libraryItem, updatePayload) { + var itemProgress = this.libraryItemProgress.find(li => li.id === libraryItem.id) if (!itemProgress) { var newItemProgress = new LibraryItemProgress() - newItemProgress.setData(libraryItemId, updatePayload) + + var mediaEntity = null + if (updatePayload.mediaEntityId) mediaEntity = libraryItem.media.getMediaEntityById(updatePayload.mediaEntityId) + if (!mediaEntity) mediaEntity = libraryItem.media.getPlaybackMediaEntity() + if (!mediaEntity) { + Logger.error(`[User] createUpdateLibraryItemProgress invalid library item has no playback media entity "${libraryItem.id}"`) + return false + } + + newItemProgress.setData(libraryItem.id, mediaEntity.id, updatePayload) this.libraryItemProgress.push(newItemProgress) return true } @@ -225,7 +234,7 @@ class User { removeLibraryItemProgress(libraryItemId) { if (!this.libraryItemProgress.some(lip => lip.id == libraryItemId)) return false - this.libraryItemProgress = this.libraryItemProgress.filter(lip => lip != libraryItemId) + this.libraryItemProgress = this.libraryItemProgress.filter(lip => lip.id != libraryItemId) return true } diff --git a/server/utils/dbMigration.js b/server/utils/dbMigration.js index 8abde1d2..c4c5bc17 100644 --- a/server/utils/dbMigration.js +++ b/server/utils/dbMigration.js @@ -338,6 +338,8 @@ function cleanUserObject(db, userObj) { var liProgress = new LibraryItemProgress() // New Progress Object liProgress.id = userAudiobookData.audiobookId // This ID will be updated when library item is created liProgress.libraryItemId = userAudiobookData.audiobookId + liProgress.mediaEntityId = userAudiobookData.audiobookId + liProgress.duration = userAudiobookData.totalDuration liProgress.isFinished = !!userAudiobookData.isRead Object.keys(liProgress.toJSON()).forEach((key) => { if (userAudiobookData[key] !== undefined) {