mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Update:API endpoint for syncing multiple local sessions. New API endpoint to get current user. Deprecate /me/sync-local-progress endpoint
This commit is contained in:
		
							parent
							
								
									debf0f495d
								
							
						
					
					
						commit
						f9e6655359
					
				| @ -6,6 +6,10 @@ const { isObject, toNumber } = require('../utils/index') | |||||||
| class MeController { | class MeController { | ||||||
|   constructor() { } |   constructor() { } | ||||||
| 
 | 
 | ||||||
|  |   getCurrentUser(req, res) { | ||||||
|  |     res.json(req.user.toJSONForBrowser()) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   // GET: api/me/listening-sessions
 |   // GET: api/me/listening-sessions
 | ||||||
|   async getListeningSessions(req, res) { |   async getListeningSessions(req, res) { | ||||||
|     var listeningSessions = await this.getUserListeningSessionsHelper(req.user.id) |     var listeningSessions = await this.getUserListeningSessionsHelper(req.user.id) | ||||||
| @ -184,6 +188,7 @@ class MeController { | |||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   // TODO: Deprecated. Removed from Android. Only used in iOS app now.
 | ||||||
|   // POST: api/me/sync-local-progress
 |   // POST: api/me/sync-local-progress
 | ||||||
|   async syncLocalMediaProgress(req, res) { |   async syncLocalMediaProgress(req, res) { | ||||||
|     if (!req.body.localMediaProgress) { |     if (!req.body.localMediaProgress) { | ||||||
|  | |||||||
| @ -75,6 +75,11 @@ class SessionController { | |||||||
|     this.playbackSessionManager.syncLocalSessionRequest(req.user, req.body, res) |     this.playbackSessionManager.syncLocalSessionRequest(req.user, req.body, res) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   // POST: api/session/local-all
 | ||||||
|  |   syncLocalSessions(req, res) { | ||||||
|  |     this.playbackSessionManager.syncLocalSessionsRequest(req, res) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   openSessionMiddleware(req, res, next) { |   openSessionMiddleware(req, res, next) { | ||||||
|     var playbackSession = this.playbackSessionManager.getSession(req.params.id) |     var playbackSession = this.playbackSessionManager.getSession(req.params.id) | ||||||
|     if (!playbackSession) return res.sendStatus(404) |     if (!playbackSession) return res.sendStatus(404) | ||||||
|  | |||||||
| @ -21,7 +21,6 @@ class PlaybackSessionManager { | |||||||
|     this.StreamsPath = Path.join(global.MetadataPath, 'streams') |     this.StreamsPath = Path.join(global.MetadataPath, 'streams') | ||||||
| 
 | 
 | ||||||
|     this.sessions = [] |     this.sessions = [] | ||||||
|     this.localSessionLock = {} |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   getSession(sessionId) { |   getSession(sessionId) { | ||||||
| @ -61,18 +60,84 @@ class PlaybackSessionManager { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async syncLocalSessionRequest(user, sessionJson, res) { |   async syncLocalSessionsRequest(req, res) { | ||||||
|     if (this.localSessionLock[sessionJson.id]) { |     const user = req.user | ||||||
|       Logger.debug(`[PlaybackSessionManager] syncLocalSessionRequest: Local session is locked and already syncing`) |     const sessions = req.body.sessions || [] | ||||||
|       return res.status(500).send('Local session is locked and already syncing') | 
 | ||||||
|  |     const syncResults = [] | ||||||
|  |     for (const sessionJson of sessions) { | ||||||
|  |       Logger.info(`[PlaybackSessionManager] Syncing local session "${sessionJson.displayTitle}" (${sessionJson.id})`) | ||||||
|  |       const result = await this.syncLocalSession(user, sessionJson) | ||||||
|  |       syncResults.push(result) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     res.json({ | ||||||
|  |       results: syncResults | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async syncLocalSession(user, sessionJson) { | ||||||
|     const libraryItem = this.db.getLibraryItem(sessionJson.libraryItemId) |     const libraryItem = this.db.getLibraryItem(sessionJson.libraryItemId) | ||||||
|     if (!libraryItem) { |     const episode = (sessionJson.episodeId && libraryItem && libraryItem.isPodcast) ? libraryItem.media.getEpisode(sessionJson.episodeId) : null | ||||||
|       Logger.error(`[PlaybackSessionManager] syncLocalSessionRequest: Library item not found for session "${sessionJson.libraryItemId}"`) |     if (!libraryItem || (libraryItem.isPodcast && !episode)) { | ||||||
|       return res.status(500).send('Library item not found') |       Logger.error(`[PlaybackSessionManager] syncLocalSession: Media item not found for session "${sessionJson.displayTitle}" (${sessionJson.id})`) | ||||||
|  |       return { | ||||||
|  |         id: sessionJson.id, | ||||||
|  |         success: false, | ||||||
|  |         error: 'Media item not found' | ||||||
|  |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     let session = await this.db.getPlaybackSession(sessionJson.id) | ||||||
|  |     if (!session) { | ||||||
|  |       // New session from local
 | ||||||
|  |       session = new PlaybackSession(sessionJson) | ||||||
|  |       Logger.debug(`[PlaybackSessionManager] Inserting new session for "${session.displayTitle}" (${session.id})`) | ||||||
|  |       await this.db.insertEntity('session', session) | ||||||
|  |     } else { | ||||||
|  |       session.currentTime = sessionJson.currentTime | ||||||
|  |       session.timeListening = sessionJson.timeListening | ||||||
|  |       session.updatedAt = sessionJson.updatedAt | ||||||
|  |       session.date = date.format(new Date(), 'YYYY-MM-DD') | ||||||
|  |       session.dayOfWeek = date.format(new Date(), 'dddd') | ||||||
|  | 
 | ||||||
|  |       Logger.debug(`[PlaybackSessionManager] Updated session for "${session.displayTitle}" (${session.id})`) | ||||||
|  |       await this.db.updateEntity('session', session) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const result = { | ||||||
|  |       id: session.id, | ||||||
|  |       success: true, | ||||||
|  |       progressSynced: false | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const userProgressForItem = user.getMediaProgress(session.libraryItemId, session.episodeId) | ||||||
|  |     if (userProgressForItem) { | ||||||
|  |       if (userProgressForItem.lastUpdate > session.updatedAt) { | ||||||
|  |         Logger.debug(`[PlaybackSessionManager] Not updating progress for "${session.displayTitle}" because it has been updated more recently`) | ||||||
|  |       } else { | ||||||
|  |         Logger.debug(`[PlaybackSessionManager] Updating progress for "${session.displayTitle}" with current time ${session.currentTime} (previously ${userProgressForItem.currentTime})`) | ||||||
|  |         result.progressSynced = user.createUpdateMediaProgress(libraryItem, session.mediaProgressObject, session.episodeId) | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       Logger.debug(`[PlaybackSessionManager] Creating new media progress for media item "${session.displayTitle}"`) | ||||||
|  |       result.progressSynced = user.createUpdateMediaProgress(libraryItem, session.mediaProgressObject, session.episodeId) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Update user and emit socket event
 | ||||||
|  |     if (result.progressSynced) { | ||||||
|  |       await this.db.updateEntity('user', user) | ||||||
|  |       const itemProgress = user.getMediaProgress(session.libraryItemId, session.episodeId) | ||||||
|  |       SocketAuthority.clientEmitter(user.id, 'user_item_progress_updated', { | ||||||
|  |         id: itemProgress.id, | ||||||
|  |         data: itemProgress.toJSON() | ||||||
|  |       }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return result | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async syncLocalSessionRequest(user, sessionJson, res) { | ||||||
|     // If server session is open for this same media item then close it
 |     // If server session is open for this same media item then close it
 | ||||||
|     const userSessionForThisItem = this.sessions.find(playbackSession => { |     const userSessionForThisItem = this.sessions.find(playbackSession => { | ||||||
|       if (playbackSession.userId !== user.id) return false |       if (playbackSession.userId !== user.id) return false | ||||||
| @ -84,43 +149,13 @@ class PlaybackSessionManager { | |||||||
|       await this.closeSession(user, userSessionForThisItem, null) |       await this.closeSession(user, userSessionForThisItem, null) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     this.localSessionLock[sessionJson.id] = true // Lock local session
 |     // Sync
 | ||||||
| 
 |     const result = await this.syncLocalSession(user, sessionJson) | ||||||
|     let session = await this.db.getPlaybackSession(sessionJson.id) |     if (result.error) { | ||||||
|     if (!session) { |       res.status(500).send(result.error) | ||||||
|       // New session from local
 |  | ||||||
|       session = new PlaybackSession(sessionJson) |  | ||||||
|       await this.db.insertEntity('session', session) |  | ||||||
|     } else { |     } else { | ||||||
|       session.currentTime = sessionJson.currentTime |       res.sendStatus(200) | ||||||
|       session.timeListening = sessionJson.timeListening |  | ||||||
|       session.updatedAt = sessionJson.updatedAt |  | ||||||
|       session.date = date.format(new Date(), 'YYYY-MM-DD') |  | ||||||
|       session.dayOfWeek = date.format(new Date(), 'dddd') |  | ||||||
|       await this.db.updateEntity('session', session) |  | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     session.currentTime = sessionJson.currentTime |  | ||||||
| 
 |  | ||||||
|     const itemProgressUpdate = { |  | ||||||
|       duration: session.duration, |  | ||||||
|       currentTime: session.currentTime, |  | ||||||
|       progress: session.progress, |  | ||||||
|       lastUpdate: session.updatedAt // Keep media progress update times the same as local
 |  | ||||||
|     } |  | ||||||
|     const wasUpdated = user.createUpdateMediaProgress(libraryItem, itemProgressUpdate, session.episodeId) |  | ||||||
|     if (wasUpdated) { |  | ||||||
|       await this.db.updateEntity('user', user) |  | ||||||
|       const itemProgress = user.getMediaProgress(session.libraryItemId, session.episodeId) |  | ||||||
|       SocketAuthority.clientEmitter(user.id, 'user_item_progress_updated', { |  | ||||||
|         id: itemProgress.id, |  | ||||||
|         data: itemProgress.toJSON() |  | ||||||
|       }) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     delete this.localSessionLock[sessionJson.id] // Unlock local session
 |  | ||||||
| 
 |  | ||||||
|     res.sendStatus(200) |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async closeSessionRequest(user, session, syncData, res) { |   async closeSessionRequest(user, session, syncData, res) { | ||||||
|  | |||||||
| @ -141,6 +141,11 @@ class PlaybackSession { | |||||||
|     this.updatedAt = session.updatedAt || null |     this.updatedAt = session.updatedAt || null | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   get mediaItemId() { | ||||||
|  |     if (this.episodeId) return `${this.libraryItemId}-${this.episodeId}` | ||||||
|  |     return this.libraryItemId | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   get progress() { // Value between 0 and 1
 |   get progress() { // Value between 0 and 1
 | ||||||
|     if (!this.duration) return 0 |     if (!this.duration) return 0 | ||||||
|     return Math.max(0, Math.min(this.currentTime / this.duration, 1)) |     return Math.max(0, Math.min(this.currentTime / this.duration, 1)) | ||||||
| @ -151,6 +156,15 @@ class PlaybackSession { | |||||||
|     return this.deviceInfo.deviceDescription |     return this.deviceInfo.deviceDescription | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   get mediaProgressObject() { | ||||||
|  |     return { | ||||||
|  |       duration: this.duration, | ||||||
|  |       currentTime: this.currentTime, | ||||||
|  |       progress: this.progress, | ||||||
|  |       lastUpdate: this.updatedAt | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   setData(libraryItem, user, mediaPlayer, deviceInfo, startTime, episodeId = null) { |   setData(libraryItem, user, mediaPlayer, deviceInfo, startTime, episodeId = null) { | ||||||
|     this.id = getId('play') |     this.id = getId('play') | ||||||
|     this.userId = user.id |     this.userId = user.id | ||||||
|  | |||||||
| @ -162,6 +162,7 @@ class ApiRouter { | |||||||
|     //
 |     //
 | ||||||
|     // Current User Routes (Me)
 |     // Current User Routes (Me)
 | ||||||
|     //
 |     //
 | ||||||
|  |     this.router.get('/me', MeController.getCurrentUser.bind(this)) | ||||||
|     this.router.get('/me/listening-sessions', MeController.getListeningSessions.bind(this)) |     this.router.get('/me/listening-sessions', MeController.getListeningSessions.bind(this)) | ||||||
|     this.router.get('/me/listening-stats', MeController.getListeningStats.bind(this)) |     this.router.get('/me/listening-stats', MeController.getListeningStats.bind(this)) | ||||||
|     this.router.get('/me/progress/:id/remove-from-continue-listening', MeController.removeItemFromContinueListening.bind(this)) |     this.router.get('/me/progress/:id/remove-from-continue-listening', MeController.removeItemFromContinueListening.bind(this)) | ||||||
| @ -174,8 +175,8 @@ class ApiRouter { | |||||||
|     this.router.patch('/me/item/:id/bookmark', MeController.updateBookmark.bind(this)) |     this.router.patch('/me/item/:id/bookmark', MeController.updateBookmark.bind(this)) | ||||||
|     this.router.delete('/me/item/:id/bookmark/:time', MeController.removeBookmark.bind(this)) |     this.router.delete('/me/item/:id/bookmark/:time', MeController.removeBookmark.bind(this)) | ||||||
|     this.router.patch('/me/password', MeController.updatePassword.bind(this)) |     this.router.patch('/me/password', MeController.updatePassword.bind(this)) | ||||||
|     this.router.patch('/me/settings', MeController.updateSettings.bind(this)) // TODO: Remove after mobile release v0.9.61-beta
 |     this.router.patch('/me/settings', MeController.updateSettings.bind(this)) // TODO: Deprecated. Remove after mobile release v0.9.61-beta
 | ||||||
|     this.router.post('/me/sync-local-progress', MeController.syncLocalMediaProgress.bind(this)) |     this.router.post('/me/sync-local-progress', MeController.syncLocalMediaProgress.bind(this)) // TODO: Deprecated. Removed from Android. Only used in iOS app now.
 | ||||||
|     this.router.get('/me/items-in-progress', MeController.getAllLibraryItemsInProgress.bind(this)) |     this.router.get('/me/items-in-progress', MeController.getAllLibraryItemsInProgress.bind(this)) | ||||||
|     this.router.get('/me/series/:id/remove-from-continue-listening', MeController.removeSeriesFromContinueListening.bind(this)) |     this.router.get('/me/series/:id/remove-from-continue-listening', MeController.removeSeriesFromContinueListening.bind(this)) | ||||||
|     this.router.get('/me/series/:id/readd-to-continue-listening', MeController.readdSeriesFromContinueListening.bind(this)) |     this.router.get('/me/series/:id/readd-to-continue-listening', MeController.readdSeriesFromContinueListening.bind(this)) | ||||||
| @ -215,11 +216,12 @@ class ApiRouter { | |||||||
|     //
 |     //
 | ||||||
|     this.router.get('/sessions', SessionController.getAllWithUserData.bind(this)) |     this.router.get('/sessions', SessionController.getAllWithUserData.bind(this)) | ||||||
|     this.router.delete('/sessions/:id', SessionController.middleware.bind(this), SessionController.delete.bind(this)) |     this.router.delete('/sessions/:id', SessionController.middleware.bind(this), SessionController.delete.bind(this)) | ||||||
|  |     this.router.post('/session/local', SessionController.syncLocal.bind(this)) | ||||||
|  |     this.router.post('/session/local-all', SessionController.syncLocalSessions.bind(this)) | ||||||
|     // TODO: Update these endpoints because they are only for open playback sessions
 |     // TODO: Update these endpoints because they are only for open playback sessions
 | ||||||
|     this.router.get('/session/:id', SessionController.openSessionMiddleware.bind(this), SessionController.getOpenSession.bind(this)) |     this.router.get('/session/:id', SessionController.openSessionMiddleware.bind(this), SessionController.getOpenSession.bind(this)) | ||||||
|     this.router.post('/session/:id/sync', SessionController.openSessionMiddleware.bind(this), SessionController.sync.bind(this)) |     this.router.post('/session/:id/sync', SessionController.openSessionMiddleware.bind(this), SessionController.sync.bind(this)) | ||||||
|     this.router.post('/session/:id/close', SessionController.openSessionMiddleware.bind(this), SessionController.close.bind(this)) |     this.router.post('/session/:id/close', SessionController.openSessionMiddleware.bind(this), SessionController.close.bind(this)) | ||||||
|     this.router.post('/session/local', SessionController.syncLocal.bind(this)) |  | ||||||
| 
 | 
 | ||||||
|     //
 |     //
 | ||||||
|     // Podcast Routes
 |     // Podcast Routes
 | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user