diff --git a/client/components/app/BookShelfToolbar.vue b/client/components/app/BookShelfToolbar.vue index b7ecff624..f79346fe9 100644 --- a/client/components/app/BookShelfToolbar.vue +++ b/client/components/app/BookShelfToolbar.vue @@ -44,6 +44,14 @@
{{ $formatNumber(numShowing) }}
+
diff --git a/server/controllers/LibraryController.js b/server/controllers/LibraryController.js index 55ef45690..4a1f873cc 100644 --- a/server/controllers/LibraryController.js +++ b/server/controllers/LibraryController.js @@ -789,10 +789,15 @@ class LibraryController { const seriesJson = series.toOldJSON() if (include.includes('progress')) { const libraryItemsFinished = libraryItemsInSeries.filter((li) => !!req.user.getMediaProgress(li.media.id)?.isFinished) + const totalListened = libraryItemsInSeries.reduce((acc, li) => { + const p = req.user.getMediaProgress(li.media.id) + return acc + (p?.isFinished ? (li.media.duration || 0) : (p?.currentTime || 0)) + }, 0) seriesJson.progress = { libraryItemIds: libraryItemsInSeries.map((li) => li.id), libraryItemIdsFinished: libraryItemsFinished.map((li) => li.id), - isFinished: libraryItemsFinished.length >= libraryItemsInSeries.length + isFinished: libraryItemsFinished.length >= libraryItemsInSeries.length, + totalDurationListened: totalListened } } @@ -801,6 +806,9 @@ class LibraryController { seriesJson.rssFeed = feedObj?.toOldJSONMinified() || null } + // populate total duration (same unit as libraryItem.duration) + seriesJson.totalDuration = await Database.seriesModel.getTotalDurationById(series.id) + res.json(seriesJson) } diff --git a/server/models/Series.js b/server/models/Series.js index 6ca288464..c6b92ad13 100644 --- a/server/models/Series.js +++ b/server/models/Series.js @@ -65,6 +65,37 @@ class Series extends Model { series.books = await series.getBooksExpandedWithLibraryItem() return series } + + /** + * + * @param {string} seriesId + * @returns {Promise} total duration (same unit as libraryItem.duration) + */ + static async getTotalDurationById(seriesId) { + const { book: bookModel, libraryItem: libraryItemModel, bookSeries: bookSeriesModel } = this.sequelize.models + + // Sum durations on libraryItems for books that belong to the series. + // This relies on Sequelize associations existing between: + // libraryItem -> book, book -> bookSeries (or bookSeries as a through model). + const total = await libraryItemModel.sum('duration', { + where: { mediaType: 'book' }, + include: [ + { + model: bookModel, + attributes: [], + include: [ + { + model: bookSeriesModel, + attributes: [], + where: { seriesId } + } + ] + } + ] + }) + + return Number(total) || 0 + } /** *