From 43217657d76a228e526362adb3d8bdb19717063b Mon Sep 17 00:00:00 2001 From: advplyr Date: Thu, 4 Jul 2024 11:19:29 -0500 Subject: [PATCH 1/5] Update media item shares to close when changing shares on same device --- server/controllers/ShareController.js | 13 ++++--------- server/managers/ShareManager.js | 11 ++++++++++- server/models/LibraryItem.js | 1 - 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/server/controllers/ShareController.js b/server/controllers/ShareController.js index 3022cb7f..0770bfa4 100644 --- a/server/controllers/ShareController.js +++ b/server/controllers/ShareController.js @@ -42,19 +42,14 @@ class ShareController { const playbackSession = ShareManager.findPlaybackSessionBySessionId(req.cookies.share_session_id) if (playbackSession) { - const playbackSessionMediaItemShare = ShareManager.findByMediaItemId(playbackSession.mediaItemId) - if (!playbackSessionMediaItemShare) { - Logger.error(`[ShareController] Share playback session ${req.cookies.share_session_id} media item share not found with id ${playbackSession.mediaItemId}`) - return res.sendStatus(500) - } - if (playbackSessionMediaItemShare.slug === slug) { + if (mediaItemShare.id === playbackSession.mediaItemShareId) { Logger.debug(`[ShareController] Found share playback session ${req.cookies.share_session_id}`) mediaItemShare.playbackSession = playbackSession.toJSONForClient() return res.json(mediaItemShare) } else { - // TODO: Close old session and use same session id - Logger.info(`[ShareController] Share playback session found with id ${req.cookies.share_session_id} but media item share slug ${playbackSessionMediaItemShare.slug} does not match requested slug ${slug}`) - res.clearCookie('share_session_id') + // Changed media item share - close other session + Logger.debug(`[ShareController] Other playback session is already open for share session. Closing session "${playbackSession.displayTitle}"`) + ShareManager.closeSharePlaybackSession(playbackSession) } } else { Logger.info(`[ShareController] Share playback session not found with id ${req.cookies.share_session_id}`) diff --git a/server/managers/ShareManager.js b/server/managers/ShareManager.js index da3b6fc5..f6827973 100644 --- a/server/managers/ShareManager.js +++ b/server/managers/ShareManager.js @@ -25,10 +25,19 @@ class ShareManager { * @param {import('../objects/PlaybackSession')} playbackSession */ addOpenSharePlaybackSession(playbackSession) { - Logger.info(`[ShareManager] Adding new open share playback session ${playbackSession.shareSessionId}`) + Logger.info(`[ShareManager] Adding new open share playback session "${playbackSession.displayTitle}"`) this.openSharePlaybackSessions.push(playbackSession) } + /** + * + * @param {import('../objects/PlaybackSession')} playbackSession + */ + closeSharePlaybackSession(playbackSession) { + Logger.info(`[ShareManager] Closing share playback session "${playbackSession.displayTitle}"`) + this.openSharePlaybackSessions = this.openSharePlaybackSessions.filter((s) => s.id !== playbackSession.id) + } + /** * Find an open media item share by media item ID * @param {string} mediaItemId diff --git a/server/models/LibraryItem.js b/server/models/LibraryItem.js index cffcb80a..098eacdf 100644 --- a/server/models/LibraryItem.js +++ b/server/models/LibraryItem.js @@ -568,7 +568,6 @@ class LibraryItem extends Model { oldLibraryItem.numEpisodesIncomplete = li.numEpisodesIncomplete } if (li.mediaType === 'book' && options.include?.includes?.('share')) { - console.log('Lookup share for media item id', li.mediaId) oldLibraryItem.mediaItemShare = ShareManager.findByMediaItemId(li.mediaId) } From fed5ff4863434d8477f33093ec104b0b0c719069 Mon Sep 17 00:00:00 2001 From: advplyr Date: Thu, 4 Jul 2024 12:00:54 -0500 Subject: [PATCH 2/5] Add:Daily cron that closes stale open playback sessions --- server/Server.js | 2 +- server/controllers/ShareController.js | 1 + server/managers/CronManager.js | 23 ++++++++++++++++++++++- server/managers/PlaybackSessionManager.js | 19 +++++++++++++++++++ server/managers/ShareManager.js | 13 +++++++++++++ 5 files changed, 56 insertions(+), 2 deletions(-) diff --git a/server/Server.js b/server/Server.js index 69061573..9b164291 100644 --- a/server/Server.js +++ b/server/Server.js @@ -74,7 +74,7 @@ class Server { this.podcastManager = new PodcastManager(this.watcher, this.notificationManager) this.audioMetadataManager = new AudioMetadataMangaer() this.rssFeedManager = new RssFeedManager() - this.cronManager = new CronManager(this.podcastManager) + this.cronManager = new CronManager(this.podcastManager, this.playbackSessionManager) this.apiCacheManager = new ApiCacheManager() this.binaryManager = new BinaryManager() diff --git a/server/controllers/ShareController.js b/server/controllers/ShareController.js index 0770bfa4..0dbec374 100644 --- a/server/controllers/ShareController.js +++ b/server/controllers/ShareController.js @@ -233,6 +233,7 @@ class ShareController { } playbackSession.currentTime = Math.min(currentTime, playbackSession.duration) + playbackSession.updatedAt = Date.now() Logger.debug(`[ShareController] Update share playback session ${req.cookies.share_session_id} currentTime: ${playbackSession.currentTime}`) res.sendStatus(204) } diff --git a/server/managers/CronManager.js b/server/managers/CronManager.js index 961bf2eb..b35cf804 100644 --- a/server/managers/CronManager.js +++ b/server/managers/CronManager.js @@ -4,9 +4,14 @@ const Logger = require('../Logger') const Database = require('../Database') const LibraryScanner = require('../scanner/LibraryScanner') +const ShareManager = require('./ShareManager') + class CronManager { - constructor(podcastManager) { + constructor(podcastManager, playbackSessionManager) { + /** @type {import('./PodcastManager')} */ this.podcastManager = podcastManager + /** @type {import('./PlaybackSessionManager')} */ + this.playbackSessionManager = playbackSessionManager this.libraryScanCrons = [] this.podcastCrons = [] @@ -19,10 +24,26 @@ class CronManager { * @param {import('../objects/Library')[]} libraries */ async init(libraries) { + this.initOpenSessionCleanupCron() this.initLibraryScanCrons(libraries) await this.initPodcastCrons() } + /** + * Initialize open session cleanup cron + * Runs every day at 00:30 + * Closes open share sessions that have not been updated in 24 hours + * Closes open playback sessions that have not been updated in 36 hours + * TODO: Clients should re-open the session if it is closed so that stale sessions can be closed sooner + */ + initOpenSessionCleanupCron() { + cron.schedule('30 0 * * *', async () => { + Logger.debug('[CronManager] Open session cleanup cron executing') + ShareManager.closeStaleOpenShareSessions() + await this.playbackSessionManager.closeStaleOpenSessions() + }) + } + /** * Initialize library scan crons * @param {import('../objects/Library')[]} libraries diff --git a/server/managers/PlaybackSessionManager.js b/server/managers/PlaybackSessionManager.js index 414f7f71..73a04324 100644 --- a/server/managers/PlaybackSessionManager.js +++ b/server/managers/PlaybackSessionManager.js @@ -21,6 +21,8 @@ class PlaybackSessionManager { this.StreamsPath = Path.join(global.MetadataPath, 'streams') this.oldPlaybackSessionMap = {} // TODO: Remove after updated mobile versions + + /** @type {PlaybackSession[]} */ this.sessions = [] } @@ -346,6 +348,10 @@ class PlaybackSessionManager { } } + /** + * + * @param {string} sessionId + */ async removeSession(sessionId) { const session = this.sessions.find((s) => s.id === sessionId) if (!session) return @@ -378,5 +384,18 @@ class PlaybackSessionManager { Logger.error(`[PlaybackSessionManager] cleanOrphanStreams failed`, error) } } + + /** + * Close all open sessions that have not been updated in the last 36 hours + */ + async closeStaleOpenSessions() { + const updatedAtTimeCutoff = Date.now() - 1000 * 60 * 60 * 36 + const staleSessions = this.sessions.filter((session) => session.updatedAt < updatedAtTimeCutoff) + for (const session of staleSessions) { + const sessionLastUpdate = new Date(session.updatedAt) + Logger.info(`[PlaybackSessionManager] Closing stale session "${session.displayTitle}" (${session.id}) last updated at ${sessionLastUpdate}`) + await this.removeSession(session.id) + } + } } module.exports = PlaybackSessionManager diff --git a/server/managers/ShareManager.js b/server/managers/ShareManager.js index f6827973..8e771dab 100644 --- a/server/managers/ShareManager.js +++ b/server/managers/ShareManager.js @@ -162,5 +162,18 @@ class ShareManager { destroyMediaItemShare(mediaItemShareId) { return Database.models.mediaItemShare.destroy({ where: { id: mediaItemShareId } }) } + + /** + * Close open share sessions that have not been updated in the last 24 hours + */ + closeStaleOpenShareSessions() { + const updatedAtTimeCutoff = Date.now() - 1000 * 60 * 60 * 24 + const staleSessions = this.openSharePlaybackSessions.filter((session) => session.updatedAt < updatedAtTimeCutoff) + for (const session of staleSessions) { + const sessionLastUpdate = new Date(session.updatedAt) + Logger.info(`[PlaybackSessionManager] Closing stale session "${session.displayTitle}" (${session.id}) last updated at ${sessionLastUpdate}`) + this.closeSharePlaybackSession(session) + } + } } module.exports = new ShareManager() From d5f991ae4af03ba0fd5f5acb90f8133732106116 Mon Sep 17 00:00:00 2001 From: advplyr Date: Thu, 4 Jul 2024 15:28:44 -0500 Subject: [PATCH 3/5] Fix media share player screen height on android browsers --- client/pages/share/_slug.vue | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/client/pages/share/_slug.vue b/client/pages/share/_slug.vue index 13a91baa..384a9513 100644 --- a/client/pages/share/_slug.vue +++ b/client/pages/share/_slug.vue @@ -1,7 +1,7 @@