Update listening sessions per device and show open sessions

This commit is contained in:
advplyr 2023-04-08 18:01:24 -05:00
parent 8fca84e4bd
commit 25ca950dd0
11 changed files with 195 additions and 39 deletions

View file

@ -14,7 +14,7 @@ class SessionController {
return res.sendStatus(404)
}
var listeningSessions = []
let listeningSessions = []
if (req.query.user) {
listeningSessions = await this.getUserListeningSessionsHelper(req.query.user)
} else {
@ -42,6 +42,25 @@ class SessionController {
res.json(payload)
}
getOpenSessions(req, res) {
if (!req.user.isAdminOrUp) {
Logger.error(`[SessionController] getOpenSessions: Non-admin user requested open session data ${req.user.id}/"${req.user.username}"`)
return res.sendStatus(404)
}
const openSessions = this.playbackSessionManager.sessions.map(se => {
const user = this.db.users.find(u => u.id === se.userId) || null
return {
...se.toJSON(),
user: user ? { id: user.id, username: user.username } : null
}
})
res.json({
sessions: openSessions
})
}
getOpenSession(req, res) {
var libraryItem = this.db.getLibraryItem(req.session.libraryItemId)
var sessionForClient = req.session.toJSONForClient(libraryItem)

View file

@ -14,7 +14,6 @@ const PlaybackSession = require('../objects/PlaybackSession')
const DeviceInfo = require('../objects/DeviceInfo')
const Stream = require('../objects/Stream')
class PlaybackSessionManager {
constructor(db) {
this.db = db
@ -31,13 +30,14 @@ class PlaybackSessionManager {
}
getStream(sessionId) {
const session = this.getSession(sessionId)
return session ? session.stream : null
return session?.stream || null
}
getDeviceInfo(req) {
const ua = uaParserJs(req.headers['user-agent'])
const ip = requestIp.getClientIp(req)
const clientDeviceInfo = req.body ? req.body.deviceInfo || null : null // From mobile client
const clientDeviceInfo = req.body?.deviceInfo || null
const deviceInfo = new DeviceInfo()
deviceInfo.setData(ip, ua, clientDeviceInfo, serverVersion)
@ -138,18 +138,6 @@ class PlaybackSessionManager {
}
async syncLocalSessionRequest(user, sessionJson, res) {
// If server session is open for this same media item then close it
const userSessionForThisItem = this.sessions.find(playbackSession => {
if (playbackSession.userId !== user.id) return false
if (sessionJson.episodeId) return playbackSession.episodeId !== sessionJson.episodeId
return playbackSession.libraryItemId === sessionJson.libraryItemId
})
if (userSessionForThisItem) {
Logger.info(`[PlaybackSessionManager] syncLocalSessionRequest: Closing open session "${userSessionForThisItem.displayTitle}" for user "${user.username}"`)
await this.closeSession(user, userSessionForThisItem, null)
}
// Sync
const result = await this.syncLocalSession(user, sessionJson)
if (result.error) {
res.status(500).send(result.error)
@ -164,8 +152,8 @@ class PlaybackSessionManager {
}
async startSession(user, deviceInfo, libraryItem, episodeId, options) {
// Close any sessions already open for user
const userSessions = this.sessions.filter(playbackSession => playbackSession.userId === user.id)
// Close any sessions already open for user and device
const userSessions = this.sessions.filter(playbackSession => playbackSession.userId === user.id && playbackSession.deviceId === deviceInfo.deviceId)
for (const session of userSessions) {
Logger.info(`[PlaybackSessionManager] startSession: Closing open session "${session.displayTitle}" for user "${user.username}" (Device: ${session.deviceDescription})`)
await this.closeSession(user, session, null)
@ -268,6 +256,7 @@ class PlaybackSessionManager {
}
Logger.debug(`[PlaybackSessionManager] closeSession "${session.id}"`)
SocketAuthority.adminEmitter('user_stream_update', user.toJSONForPublic(this.sessions, this.db.libraryItems))
SocketAuthority.clientEmitter(session.userId, 'user_session_closed', session.id)
return this.removeSession(session.id)
}

View file

@ -1,5 +1,6 @@
class DeviceInfo {
constructor(deviceInfo = null) {
this.deviceId = null
this.ipAddress = null
// From User Agent (see: https://www.npmjs.com/package/ua-parser-js)
@ -32,6 +33,7 @@ class DeviceInfo {
toJSON() {
const obj = {
deviceId: this.deviceId,
ipAddress: this.ipAddress,
browserName: this.browserName,
browserVersion: this.browserVersion,
@ -60,23 +62,42 @@ class DeviceInfo {
return `${this.osName} ${this.osVersion} / ${this.browserName}`
}
// When client doesn't send a device id
getTempDeviceId() {
const keys = [
this.browserName,
this.browserVersion,
this.osName,
this.osVersion,
this.clientVersion,
this.manufacturer,
this.model,
this.sdkVersion,
this.ipAddress
].map(k => k || '')
return 'temp-' + Buffer.from(keys.join('-'), 'utf-8').toString('base64')
}
setData(ip, ua, clientDeviceInfo, serverVersion) {
this.deviceId = clientDeviceInfo?.deviceId || null
this.ipAddress = ip || null
const uaObj = ua || {}
this.browserName = uaObj.browser.name || null
this.browserVersion = uaObj.browser.version || null
this.osName = uaObj.os.name || null
this.osVersion = uaObj.os.version || null
this.deviceType = uaObj.device.type || null
this.browserName = ua?.browser.name || null
this.browserVersion = ua?.browser.version || null
this.osName = ua?.os.name || null
this.osVersion = ua?.os.version || null
this.deviceType = ua?.device.type || null
const cdi = clientDeviceInfo || {}
this.clientVersion = cdi.clientVersion || null
this.manufacturer = cdi.manufacturer || null
this.model = cdi.model || null
this.sdkVersion = cdi.sdkVersion || null
this.clientVersion = clientDeviceInfo?.clientVersion || null
this.manufacturer = clientDeviceInfo?.manufacturer || null
this.model = clientDeviceInfo?.model || null
this.sdkVersion = clientDeviceInfo?.sdkVersion || null
this.serverVersion = serverVersion || null
if (!this.deviceId) {
this.deviceId = this.getTempDeviceId()
}
}
}
module.exports = DeviceInfo

View file

@ -55,7 +55,7 @@ class PlaybackSession {
libraryItemId: this.libraryItemId,
episodeId: this.episodeId,
mediaType: this.mediaType,
mediaMetadata: this.mediaMetadata ? this.mediaMetadata.toJSON() : null,
mediaMetadata: this.mediaMetadata?.toJSON() || null,
chapters: (this.chapters || []).map(c => ({ ...c })),
displayTitle: this.displayTitle,
displayAuthor: this.displayAuthor,
@ -63,7 +63,7 @@ class PlaybackSession {
duration: this.duration,
playMethod: this.playMethod,
mediaPlayer: this.mediaPlayer,
deviceInfo: this.deviceInfo ? this.deviceInfo.toJSON() : null,
deviceInfo: this.deviceInfo?.toJSON() || null,
date: this.date,
dayOfWeek: this.dayOfWeek,
timeListening: this.timeListening,
@ -82,7 +82,7 @@ class PlaybackSession {
libraryItemId: this.libraryItemId,
episodeId: this.episodeId,
mediaType: this.mediaType,
mediaMetadata: this.mediaMetadata ? this.mediaMetadata.toJSON() : null,
mediaMetadata: this.mediaMetadata?.toJSON() || null,
chapters: (this.chapters || []).map(c => ({ ...c })),
displayTitle: this.displayTitle,
displayAuthor: this.displayAuthor,
@ -90,7 +90,7 @@ class PlaybackSession {
duration: this.duration,
playMethod: this.playMethod,
mediaPlayer: this.mediaPlayer,
deviceInfo: this.deviceInfo ? this.deviceInfo.toJSON() : null,
deviceInfo: this.deviceInfo?.toJSON() || null,
date: this.date,
dayOfWeek: this.dayOfWeek,
timeListening: this.timeListening,
@ -151,6 +151,10 @@ class PlaybackSession {
return Math.max(0, Math.min(this.currentTime / this.duration, 1))
}
get deviceId() {
return this.deviceInfo?.deviceId
}
get deviceDescription() {
if (!this.deviceInfo) return 'No Device Info'
return this.deviceInfo.deviceDescription

View file

@ -214,6 +214,7 @@ class ApiRouter {
//
this.router.get('/sessions', SessionController.getAllWithUserData.bind(this))
this.router.delete('/sessions/:id', SessionController.middleware.bind(this), SessionController.delete.bind(this))
this.router.get('/sessions/open', SessionController.getOpenSessions.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