From 9896e4381b8ebf02919a16ed8dffe86a7a41fac9 Mon Sep 17 00:00:00 2001 From: advplyr Date: Mon, 21 Oct 2024 17:48:02 -0500 Subject: [PATCH] Update:Setup variables to control when a media item is marked as finished. By time remaining or progress percentage #837 --- client/players/PlayerHandler.js | 2 -- server/managers/PlaybackSessionManager.js | 6 ++++- server/models/MediaProgress.js | 33 ++++++++++++++++++++--- server/models/User.js | 30 ++++++++++++--------- 4 files changed, 52 insertions(+), 19 deletions(-) diff --git a/client/players/PlayerHandler.js b/client/players/PlayerHandler.js index a327f831..ba71fc6c 100644 --- a/client/players/PlayerHandler.js +++ b/client/players/PlayerHandler.js @@ -297,7 +297,6 @@ export default class PlayerHandler { if (listeningTimeToAdd > 20) { syncData = { timeListened: listeningTimeToAdd, - duration: this.getDuration(), currentTime: this.getCurrentTime() } } @@ -317,7 +316,6 @@ export default class PlayerHandler { const listeningTimeToAdd = Math.max(0, Math.floor(this.listeningTimeSinceSync)) const syncData = { timeListened: listeningTimeToAdd, - duration: this.getDuration(), currentTime } diff --git a/server/managers/PlaybackSessionManager.js b/server/managers/PlaybackSessionManager.js index 4318841e..a26a5c81 100644 --- a/server/managers/PlaybackSessionManager.js +++ b/server/managers/PlaybackSessionManager.js @@ -343,9 +343,13 @@ class PlaybackSessionManager { const updateResponse = await user.createUpdateMediaProgressFromPayload({ libraryItemId: libraryItem.id, episodeId: session.episodeId, - duration: syncData.duration, + // duration no longer required (v2.15.1) but used if available + duration: syncData.duration || libraryItem.media.duration || 0, currentTime: syncData.currentTime, progress: session.progress + // TODO: Add support for passing in these values from library settings + // markAsFinishedTimeRemaining: 5, + // markAsFinishedPercentageComplete: 95 }) if (updateResponse.mediaProgress) { SocketAuthority.clientEmitter(user.id, 'user_item_progress_updated', { diff --git a/server/models/MediaProgress.js b/server/models/MediaProgress.js index 196353d8..052c8f74 100644 --- a/server/models/MediaProgress.js +++ b/server/models/MediaProgress.js @@ -1,4 +1,6 @@ const { DataTypes, Model } = require('sequelize') +const Logger = require('../Logger') +const { isNullOrNaN } = require('../utils') class MediaProgress extends Model { constructor(values, options) { @@ -183,10 +185,16 @@ class MediaProgress extends Model { } } + get progress() { + // Value between 0 and 1 + if (!this.duration) return 0 + return Math.max(0, Math.min(this.currentTime / this.duration, 1)) + } + /** * Apply update to media progress * - * @param {Object} progress + * @param {import('./User').ProgressUpdatePayload} progressPayload * @returns {Promise} */ applyProgressUpdate(progressPayload) { @@ -219,8 +227,27 @@ class MediaProgress extends Model { } const timeRemaining = this.duration - this.currentTime - // Set to finished if time remaining is less than 5 seconds - if (!this.isFinished && this.duration && timeRemaining < 5) { + + // Check if progress is far enough to mark as finished + // - If markAsFinishedPercentageComplete is provided, use that otherwise use markAsFinishedTimeRemaining (default 5 seconds) + let shouldMarkAsFinished = false + if (!this.isFinished && this.duration) { + if (!isNullOrNaN(progressPayload.markAsFinishedPercentageComplete)) { + const markAsFinishedPercentageComplete = Number(progressPayload.markAsFinishedPercentageComplete) / 100 + shouldMarkAsFinished = markAsFinishedPercentageComplete <= this.progress + if (shouldMarkAsFinished) { + Logger.debug(`[MediaProgress] Marking media progress as finished because progress (${this.progress}) is greater than ${markAsFinishedPercentageComplete}`) + } + } else { + const markAsFinishedTimeRemaining = isNullOrNaN(progressPayload.markAsFinishedTimeRemaining) ? 5 : Number(progressPayload.markAsFinishedTimeRemaining) + shouldMarkAsFinished = timeRemaining <= markAsFinishedTimeRemaining + if (shouldMarkAsFinished) { + Logger.debug(`[MediaProgress] Marking media progress as finished because time remaining (${timeRemaining}) is less than ${markAsFinishedTimeRemaining} seconds`) + } + } + } + + if (shouldMarkAsFinished) { this.isFinished = true this.finishedAt = this.finishedAt || Date.now() this.extraData.progress = 1 diff --git a/server/models/User.js b/server/models/User.js index 4333db88..aa63aea8 100644 --- a/server/models/User.js +++ b/server/models/User.js @@ -14,6 +14,23 @@ const { DataTypes, Model } = sequelize * @property {number} createdAt */ +/** + * @typedef ProgressUpdatePayload + * @property {string} libraryItemId + * @property {string} [episodeId] + * @property {number} [duration] + * @property {number} [progress] + * @property {number} [currentTime] + * @property {boolean} [isFinished] + * @property {boolean} [hideFromContinueListening] + * @property {string} [ebookLocation] + * @property {number} [ebookProgress] + * @property {string} [finishedAt] + * @property {number} [lastUpdate] + * @property {number} [markAsFinishedTimeRemaining] + * @property {number} [markAsFinishedPercentageComplete] + */ + class User extends Model { constructor(values, options) { super(values, options) @@ -515,19 +532,6 @@ class User extends Model { /** * TODO: Uses old model and should account for the different between ebook/audiobook progress * - * @typedef ProgressUpdatePayload - * @property {string} libraryItemId - * @property {string} [episodeId] - * @property {number} [duration] - * @property {number} [progress] - * @property {number} [currentTime] - * @property {boolean} [isFinished] - * @property {boolean} [hideFromContinueListening] - * @property {string} [ebookLocation] - * @property {number} [ebookProgress] - * @property {string} [finishedAt] - * @property {number} [lastUpdate] - * * @param {ProgressUpdatePayload} progressPayload * @returns {Promise<{ mediaProgress: import('./MediaProgress'), error: [string], statusCode: [number] }>} */