mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-03-31 19:26:51 +02:00
Add sync local media progress routes for offline mobile playback session support
This commit is contained in:
parent
fc228013d3
commit
2a386ca2a9
12
server/Db.js
12
server/Db.js
@ -13,6 +13,7 @@ const Library = require('./objects/Library')
|
||||
const Author = require('./objects/entities/Author')
|
||||
const Series = require('./objects/entities/Series')
|
||||
const ServerSettings = require('./objects/ServerSettings')
|
||||
const PlaybackSession = require('./objects/PlaybackSession')
|
||||
|
||||
class Db {
|
||||
constructor() {
|
||||
@ -188,6 +189,17 @@ class Db {
|
||||
getLibraryItem(id) {
|
||||
return this.libraryItems.find(li => li.id === id)
|
||||
}
|
||||
getPlaybackSession(id) {
|
||||
return this.sessionsDb.select((pb) => pb.id == id).then((results) => {
|
||||
if (results.data.length) {
|
||||
return new PlaybackSession(results.data[0])
|
||||
}
|
||||
return null
|
||||
}).catch((error) => {
|
||||
Logger.error('Failed to get session', error)
|
||||
return null
|
||||
})
|
||||
}
|
||||
|
||||
async updateLibraryItem(libraryItem) {
|
||||
return this.updateLibraryItems([libraryItem])
|
||||
|
@ -151,5 +151,63 @@ class MeController {
|
||||
settings: req.user.settings
|
||||
})
|
||||
}
|
||||
|
||||
// POST: api/me/sync-local-progress
|
||||
async syncLocalMediaProgress(req, res) {
|
||||
if (!req.body.localMediaProgress) {
|
||||
Logger.error(`[MeController] syncLocalMediaProgress invalid post body`)
|
||||
return res.sendStatus(500)
|
||||
}
|
||||
const updatedLocalMediaProgress = []
|
||||
var numServerProgressUpdates = 0
|
||||
var localMediaProgress = req.body.localMediaProgress || []
|
||||
localMediaProgress.forEach(localProgress => {
|
||||
if (!localProgress.libraryItemId) {
|
||||
Logger.error(`[MeController] syncLocalMediaProgress invalid local media progress object`, localProgress)
|
||||
return
|
||||
}
|
||||
var libraryItem = this.db.getLibraryItem(localProgress.libraryItemId)
|
||||
if (!libraryItem) {
|
||||
Logger.error(`[MeController] syncLocalMediaProgress invalid local media progress object no library item`, localProgress)
|
||||
return
|
||||
}
|
||||
|
||||
var mediaProgress = req.user.getMediaProgress(localProgress.libraryItemId, localProgress.episodeId)
|
||||
if (!mediaProgress) {
|
||||
// New media progress from mobile
|
||||
Logger.debug(`[MeController] syncLocalMediaProgress local progress is new - creating ${localProgress.id}`)
|
||||
req.user.createUpdateMediaProgress(libraryItem, localProgress, localProgress.episodeId)
|
||||
numServerProgressUpdates++
|
||||
} else if (mediaProgress.lastUpdate < localProgress.lastUpdate) {
|
||||
Logger.debug(`[MeController] syncLocalMediaProgress local progress is more recent - updating ${mediaProgress.id}`)
|
||||
req.user.createUpdateMediaProgress(libraryItem, localProgress, localProgress.episodeId)
|
||||
numServerProgressUpdates++
|
||||
} else if (mediaProgress.lastUpdate > localProgress.lastUpdate) {
|
||||
var updateTimeDifference = mediaProgress.lastUpdate - localProgress.lastUpdate
|
||||
Logger.debug(`[MeController] syncLocalMediaProgress server progress is more recent by ${updateTimeDifference}ms - ${mediaProgress.id}`)
|
||||
|
||||
for (const key in localProgress) {
|
||||
if (mediaProgress[key] != undefined && localProgress[key] !== mediaProgress[key]) {
|
||||
// Logger.debug(`[MeController] syncLocalMediaProgress key ${key} changed from ${localProgress[key]} to ${mediaProgress[key]} - ${mediaProgress.id}`)
|
||||
localProgress[key] = mediaProgress[key]
|
||||
}
|
||||
}
|
||||
updatedLocalMediaProgress.push(localProgress)
|
||||
} else {
|
||||
Logger.debug(`[MeController] syncLocalMediaProgress server and local are in sync - ${mediaProgress.id}`)
|
||||
}
|
||||
})
|
||||
|
||||
Logger.debug(`[MeController] syncLocalMediaProgress server updates = ${numServerProgressUpdates}, local updates = ${updatedLocalMediaProgress.length}`)
|
||||
if (numServerProgressUpdates > 0) {
|
||||
await this.db.updateEntity('user', req.user)
|
||||
this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
|
||||
}
|
||||
|
||||
res.json({
|
||||
numServerProgressUpdates,
|
||||
localProgressUpdates: updatedLocalMediaProgress
|
||||
})
|
||||
}
|
||||
}
|
||||
module.exports = new MeController()
|
@ -17,6 +17,11 @@ class SessionController {
|
||||
this.playbackSessionManager.closeSessionRequest(req.user, req.session, req.body, res)
|
||||
}
|
||||
|
||||
// POST: api/session/local
|
||||
syncLocal(req, res) {
|
||||
this.playbackSessionManager.syncLocalSessionRequest(req.user, req.body, res)
|
||||
}
|
||||
|
||||
middleware(req, res, next) {
|
||||
var playbackSession = this.playbackSessionManager.getSession(req.params.id)
|
||||
if (!playbackSession) return res.sendStatus(404)
|
||||
|
@ -37,6 +37,40 @@ class PlaybackSessionManager {
|
||||
}
|
||||
}
|
||||
|
||||
async syncLocalSessionRequest(user, sessionJson, res) {
|
||||
var libraryItem = this.db.getLibraryItem(sessionJson.libraryItemId)
|
||||
|
||||
var session = await this.db.getPlaybackSession(sessionJson.id)
|
||||
if (!session) {
|
||||
// New session from local
|
||||
session = new PlaybackSession(sessionJson)
|
||||
await this.db.insertEntity('session', session)
|
||||
} else {
|
||||
session.timeListening = sessionJson.timeListening
|
||||
session.updatedAt = sessionJson.updatedAt
|
||||
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
|
||||
}
|
||||
var wasUpdated = user.createUpdateMediaProgress(libraryItem, itemProgressUpdate, session.episodeId)
|
||||
if (wasUpdated) {
|
||||
await this.db.updateEntity('user', user)
|
||||
var itemProgress = user.getMediaProgress(session.libraryItemId, session.episodeId)
|
||||
this.clientEmitter(user.id, 'user_item_progress_updated', {
|
||||
id: itemProgress.id,
|
||||
data: itemProgress.toJSON()
|
||||
})
|
||||
}
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
async closeSessionRequest(user, session, syncData, res) {
|
||||
await this.closeSession(user, session, syncData)
|
||||
res.sendStatus(200)
|
||||
|
@ -264,6 +264,8 @@ class User {
|
||||
return true
|
||||
}
|
||||
var wasUpdated = itemProgress.update(updatePayload)
|
||||
|
||||
if (updatePayload.lastUpdate) itemProgress.lastUpdate = updatePayload.lastUpdate // For local to keep update times in sync
|
||||
return wasUpdated
|
||||
}
|
||||
|
||||
@ -340,32 +342,5 @@ class User {
|
||||
removeBookmark(libraryItemId, time) {
|
||||
this.bookmarks = this.bookmarks.filter(bm => (bm.libraryItemId !== libraryItemId || bm.time !== time))
|
||||
}
|
||||
|
||||
// TODO: re-do mobile sync
|
||||
syncLocalUserAudiobookData(localUserAudiobookData, audiobook) {
|
||||
// if (!localUserAudiobookData || !localUserAudiobookData.audiobookId) {
|
||||
// Logger.error(`[User] Invalid local user audiobook data`, localUserAudiobookData)
|
||||
// return false
|
||||
// }
|
||||
// if (!this.audiobooks) this.audiobooks = {}
|
||||
|
||||
// if (!this.audiobooks[localUserAudiobookData.audiobookId]) {
|
||||
// this.audiobooks[localUserAudiobookData.audiobookId] = new UserAudiobookData(localUserAudiobookData)
|
||||
// return true
|
||||
// }
|
||||
|
||||
// var userAbD = this.audiobooks[localUserAudiobookData.audiobookId]
|
||||
// if (userAbD.lastUpdate >= localUserAudiobookData.lastUpdate) {
|
||||
// // Server audiobook data is more recent
|
||||
// return false
|
||||
// }
|
||||
|
||||
// // Local Data More recent
|
||||
// var wasUpdated = this.audiobooks[localUserAudiobookData.audiobookId].update(localUserAudiobookData)
|
||||
// if (wasUpdated) {
|
||||
// Logger.debug(`[User] syncLocalUserAudiobookData local data was more recent for "${audiobook.title}"`)
|
||||
// }
|
||||
// return wasUpdated
|
||||
}
|
||||
}
|
||||
module.exports = User
|
@ -135,6 +135,7 @@ class ApiRouter {
|
||||
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/settings', MeController.updateSettings.bind(this))
|
||||
this.router.post('/me/sync-local-progress', MeController.syncLocalMediaProgress.bind(this))
|
||||
|
||||
//
|
||||
// Backup Routes
|
||||
@ -169,6 +170,7 @@ class ApiRouter {
|
||||
//
|
||||
this.router.post('/session/:id/sync', SessionController.middleware.bind(this), SessionController.sync.bind(this))
|
||||
this.router.post('/session/:id/close', SessionController.middleware.bind(this), SessionController.close.bind(this))
|
||||
this.router.post('/session/local', SessionController.syncLocal.bind(this))
|
||||
|
||||
//
|
||||
// Podcast Routes
|
||||
@ -192,9 +194,6 @@ class ApiRouter {
|
||||
this.router.get('/search/podcast', MiscController.findPodcasts.bind(this))
|
||||
this.router.get('/search/authors', MiscController.findAuthor.bind(this))
|
||||
this.router.get('/tags', MiscController.getAllTags.bind(this))
|
||||
|
||||
// OLD
|
||||
// this.router.post('/syncUserAudiobookData', this.syncUserAudiobookData.bind(this))
|
||||
}
|
||||
|
||||
async getDirectories(dir, relpath, excludedDirs, level = 0) {
|
||||
@ -226,38 +225,6 @@ class ApiRouter {
|
||||
}
|
||||
}
|
||||
|
||||
async syncUserAudiobookData(req, res) {
|
||||
// if (!req.body.data) {
|
||||
// return res.status(403).send('Invalid local user audiobook data')
|
||||
// }
|
||||
|
||||
// var hasUpdates = false
|
||||
|
||||
// // Local user audiobook data use the latest update
|
||||
// req.body.data.forEach((uab) => {
|
||||
// if (!uab || !uab.audiobookId) {
|
||||
// Logger.error('[ApiController] Invalid user audiobook data', uab)
|
||||
// return
|
||||
// }
|
||||
// var audiobook = this.db.audiobooks.find(ab => ab.id === uab.audiobookId)
|
||||
// if (!audiobook) {
|
||||
// Logger.info('[ApiController] syncUserAudiobookData local audiobook data audiobook no longer exists', uab.audiobookId)
|
||||
// return
|
||||
// }
|
||||
// if (req.user.syncLocalUserAudiobookData(uab, audiobook)) {
|
||||
// this.clientEmitter(req.user.id, 'current_user_audiobook_update', { id: uab.audiobookId, data: uab })
|
||||
// hasUpdates = true
|
||||
// }
|
||||
// })
|
||||
|
||||
// if (hasUpdates) {
|
||||
// await this.db.updateEntity('user', req.user)
|
||||
// }
|
||||
|
||||
// var allUserAudiobookData = Object.values(req.user.audiobooksToJSON())
|
||||
// res.json(allUserAudiobookData)
|
||||
}
|
||||
|
||||
//
|
||||
// Helper Methods
|
||||
//
|
||||
|
@ -18,7 +18,6 @@ class StaticRouter {
|
||||
|
||||
var remainingPath = req.params['0']
|
||||
var fullPath = Path.join(item.path, remainingPath)
|
||||
console.log('fullpath', fullPath)
|
||||
res.sendFile(fullPath)
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user