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) }}
+
+
+ {{ $elapsedPretty(selectedSeries.progress.totalDurationListened).replace(' hr', 'h').replace(' min', 'm') }}
+ /
+
+
+ {{ $elapsedPretty(selectedSeries.totalDuration).replace(' hr', 'h').replace(' min', 'm') }}
+
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
+ }
/**
*