mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-12-20 19:06:06 +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,44 +149,14 @@ 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
|
|
||||||
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)
|
res.sendStatus(200)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async closeSessionRequest(user, session, syncData, res) {
|
async closeSessionRequest(user, session, syncData, res) {
|
||||||
await this.closeSession(user, session, syncData)
|
await this.closeSession(user, session, syncData)
|
||||||
|
@ -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