mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-01-03 00:06:46 +01:00
Update listening sessions per device and show open sessions
This commit is contained in:
parent
8fca84e4bd
commit
25ca950dd0
@ -460,6 +460,13 @@ export default {
|
|||||||
showFailedProgressSyncs() {
|
showFailedProgressSyncs() {
|
||||||
if (!isNaN(this.syncFailedToast)) this.$toast.dismiss(this.syncFailedToast)
|
if (!isNaN(this.syncFailedToast)) this.$toast.dismiss(this.syncFailedToast)
|
||||||
this.syncFailedToast = this.$toast('Progress is not being synced. Restart playback', { timeout: false, type: 'error' })
|
this.syncFailedToast = this.$toast('Progress is not being synced. Restart playback', { timeout: false, type: 'error' })
|
||||||
|
},
|
||||||
|
sessionClosedEvent(sessionId) {
|
||||||
|
if (this.playerHandler.currentSessionId === sessionId) {
|
||||||
|
console.log('sessionClosedEvent closing current session', sessionId)
|
||||||
|
this.playerHandler.resetPlayer() // Closes player without reporting to server
|
||||||
|
this.$store.commit('setMediaPlaying', null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
@ -98,7 +98,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<ui-btn small color="error" @click.stop="deleteSessionClick">{{ $strings.ButtonDelete }}</ui-btn>
|
<ui-btn v-if="!isOpenSession" small color="error" @click.stop="deleteSessionClick">{{ $strings.ButtonDelete }}</ui-btn>
|
||||||
|
<ui-btn v-else small color="error" @click.stop="closeSessionClick">Close Open Session</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</modals-modal>
|
</modals-modal>
|
||||||
@ -157,6 +158,9 @@ export default {
|
|||||||
},
|
},
|
||||||
timeFormat() {
|
timeFormat() {
|
||||||
return this.$store.state.serverSettings.timeFormat
|
return this.$store.state.serverSettings.timeFormat
|
||||||
|
},
|
||||||
|
isOpenSession() {
|
||||||
|
return !!this._session.open
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -188,6 +192,24 @@ export default {
|
|||||||
var errMsg = error.response ? error.response.data || '' : ''
|
var errMsg = error.response ? error.response.data || '' : ''
|
||||||
this.$toast.error(errMsg || this.$strings.ToastSessionDeleteFailed)
|
this.$toast.error(errMsg || this.$strings.ToastSessionDeleteFailed)
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
closeSessionClick() {
|
||||||
|
this.processing = true
|
||||||
|
this.$axios
|
||||||
|
.$post(`/api/session/${this._session.id}/close`)
|
||||||
|
.then(() => {
|
||||||
|
this.$toast.success('Session closed')
|
||||||
|
this.show = false
|
||||||
|
this.$emit('closedSession')
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to close session', error)
|
||||||
|
const errMsg = error.response?.data || ''
|
||||||
|
this.$toast.error(errMsg || 'Failed to close open session')
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.processing = false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {}
|
mounted() {}
|
||||||
|
@ -299,8 +299,17 @@ export default {
|
|||||||
userStreamUpdate(user) {
|
userStreamUpdate(user) {
|
||||||
this.$store.commit('users/updateUserOnline', user)
|
this.$store.commit('users/updateUserOnline', user)
|
||||||
},
|
},
|
||||||
|
userSessionClosed(sessionId) {
|
||||||
|
if (this.$refs.streamContainer) this.$refs.streamContainer.sessionClosedEvent(sessionId)
|
||||||
|
},
|
||||||
userMediaProgressUpdate(payload) {
|
userMediaProgressUpdate(payload) {
|
||||||
this.$store.commit('user/updateMediaProgress', payload)
|
this.$store.commit('user/updateMediaProgress', payload)
|
||||||
|
|
||||||
|
if (payload.data) {
|
||||||
|
if (this.$store.getters['getIsMediaStreaming'](payload.data.libraryItemId, payload.data.episodeId)) {
|
||||||
|
// TODO: Update currently open session if being played from another device
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
collectionAdded(collection) {
|
collectionAdded(collection) {
|
||||||
if (this.currentLibraryId !== collection.libraryId) return
|
if (this.currentLibraryId !== collection.libraryId) return
|
||||||
@ -405,6 +414,7 @@ export default {
|
|||||||
this.socket.on('user_online', this.userOnline)
|
this.socket.on('user_online', this.userOnline)
|
||||||
this.socket.on('user_offline', this.userOffline)
|
this.socket.on('user_offline', this.userOffline)
|
||||||
this.socket.on('user_stream_update', this.userStreamUpdate)
|
this.socket.on('user_stream_update', this.userStreamUpdate)
|
||||||
|
this.socket.on('user_session_closed', this.userSessionClosed)
|
||||||
this.socket.on('user_item_progress_updated', this.userMediaProgressUpdate)
|
this.socket.on('user_item_progress_updated', this.userMediaProgressUpdate)
|
||||||
|
|
||||||
// Collection Listeners
|
// Collection Listeners
|
||||||
|
@ -52,9 +52,53 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p v-else class="text-white text-opacity-50">{{ $strings.MessageNoListeningSessions }}</p>
|
<p v-else class="text-white text-opacity-50">{{ $strings.MessageNoListeningSessions }}</p>
|
||||||
|
|
||||||
|
<!-- open listening sessions table -->
|
||||||
|
<p v-if="openListeningSessions.length" class="text-lg mb-4 mt-8">Open Listening Sessions</p>
|
||||||
|
<div v-if="openListeningSessions.length" class="block max-w-full">
|
||||||
|
<table class="userSessionsTable">
|
||||||
|
<tr class="bg-primary bg-opacity-40">
|
||||||
|
<th class="w-48 min-w-48 text-left">{{ $strings.LabelItem }}</th>
|
||||||
|
<th class="w-20 min-w-20 text-left hidden md:table-cell">{{ $strings.LabelUser }}</th>
|
||||||
|
<th class="w-32 min-w-32 text-left hidden md:table-cell">{{ $strings.LabelPlayMethod }}</th>
|
||||||
|
<th class="w-32 min-w-32 text-left hidden sm:table-cell">{{ $strings.LabelDeviceInfo }}</th>
|
||||||
|
<th class="w-32 min-w-32">{{ $strings.LabelTimeListened }}</th>
|
||||||
|
<th class="w-16 min-w-16">{{ $strings.LabelLastTime }}</th>
|
||||||
|
<th class="flex-grow hidden sm:table-cell">{{ $strings.LabelLastUpdate }}</th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr v-for="session in openListeningSessions" :key="`open-${session.id}`" class="cursor-pointer" @click="showSession(session)">
|
||||||
|
<td class="py-1 max-w-48">
|
||||||
|
<p class="text-xs text-gray-200 truncate">{{ session.displayTitle }}</p>
|
||||||
|
<p class="text-xs text-gray-400 truncate">{{ session.displayAuthor }}</p>
|
||||||
|
</td>
|
||||||
|
<td class="hidden md:table-cell">
|
||||||
|
<p v-if="filteredUserUsername" class="text-xs">{{ filteredUserUsername }}</p>
|
||||||
|
<p v-else class="text-xs">{{ session.user ? session.user.username : 'N/A' }}</p>
|
||||||
|
</td>
|
||||||
|
<td class="hidden md:table-cell">
|
||||||
|
<p class="text-xs">{{ getPlayMethodName(session.playMethod) }}</p>
|
||||||
|
</td>
|
||||||
|
<td class="hidden sm:table-cell">
|
||||||
|
<p class="text-xs" v-html="getDeviceInfoString(session.deviceInfo)" />
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<p class="text-xs font-mono">{{ $elapsedPretty(session.timeListening) }}</p>
|
||||||
|
</td>
|
||||||
|
<td class="text-center hover:underline" @click.stop="clickCurrentTime(session)">
|
||||||
|
<p class="text-xs font-mono">{{ $secondsToTimestamp(session.currentTime) }}</p>
|
||||||
|
</td>
|
||||||
|
<td class="text-center hidden sm:table-cell">
|
||||||
|
<ui-tooltip v-if="session.updatedAt" direction="top" :text="$formatDatetime(session.updatedAt, dateFormat, timeFormat)">
|
||||||
|
<p class="text-xs text-gray-200">{{ $dateDistanceFromNow(session.updatedAt) }}</p>
|
||||||
|
</ui-tooltip>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</app-settings-content>
|
</app-settings-content>
|
||||||
|
|
||||||
<modals-listening-session-modal v-model="showSessionModal" :session="selectedSession" @removedSession="removedSession" />
|
<modals-listening-session-modal v-model="showSessionModal" :session="selectedSession" @removedSession="removedSession" @closedSession="closedSession" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -81,6 +125,7 @@ export default {
|
|||||||
showSessionModal: false,
|
showSessionModal: false,
|
||||||
selectedSession: null,
|
selectedSession: null,
|
||||||
listeningSessions: [],
|
listeningSessions: [],
|
||||||
|
openListeningSessions: [],
|
||||||
numPages: 0,
|
numPages: 0,
|
||||||
total: 0,
|
total: 0,
|
||||||
currentPage: 0,
|
currentPage: 0,
|
||||||
@ -114,6 +159,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
closedSession() {
|
||||||
|
this.loadOpenSessions()
|
||||||
|
},
|
||||||
removedSession() {
|
removedSession() {
|
||||||
// If on last page and this was the last session then load prev page
|
// If on last page and this was the last session then load prev page
|
||||||
if (this.currentPage == this.numPages - 1) {
|
if (this.currentPage == this.numPages - 1) {
|
||||||
@ -222,7 +270,7 @@ export default {
|
|||||||
async loadSessions(page) {
|
async loadSessions(page) {
|
||||||
var userFilterQuery = this.selectedUser ? `&user=${this.selectedUser}` : ''
|
var userFilterQuery = this.selectedUser ? `&user=${this.selectedUser}` : ''
|
||||||
const data = await this.$axios.$get(`/api/sessions?page=${page}&itemsPerPage=${this.itemsPerPage}${userFilterQuery}`).catch((err) => {
|
const data = await this.$axios.$get(`/api/sessions?page=${page}&itemsPerPage=${this.itemsPerPage}${userFilterQuery}`).catch((err) => {
|
||||||
console.error('Failed to load listening sesions', err)
|
console.error('Failed to load listening sessions', err)
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
if (!data) {
|
if (!data) {
|
||||||
@ -236,8 +284,24 @@ export default {
|
|||||||
this.listeningSessions = data.sessions
|
this.listeningSessions = data.sessions
|
||||||
this.userFilter = data.userFilter
|
this.userFilter = data.userFilter
|
||||||
},
|
},
|
||||||
|
async loadOpenSessions() {
|
||||||
|
const data = await this.$axios.$get('/api/sessions/open').catch((err) => {
|
||||||
|
console.error('Failed to load open sessions', err)
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
if (!data) {
|
||||||
|
this.$toast.error('Failed to load open sessions')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.openListeningSessions = (data.sessions || []).map((s) => {
|
||||||
|
s.open = true
|
||||||
|
return s
|
||||||
|
})
|
||||||
|
},
|
||||||
init() {
|
init() {
|
||||||
this.loadSessions(0)
|
this.loadSessions(0)
|
||||||
|
this.loadOpenSessions()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
@ -173,16 +173,28 @@ export default class PlayerHandler {
|
|||||||
this.ctx.setBufferTime(buffertime)
|
this.ctx.setBufferTime(buffertime)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDeviceId() {
|
||||||
|
let deviceId = localStorage.getItem('absDeviceId')
|
||||||
|
if (!deviceId) {
|
||||||
|
deviceId = this.ctx.$randomId()
|
||||||
|
localStorage.setItem('absDeviceId', deviceId)
|
||||||
|
}
|
||||||
|
return deviceId
|
||||||
|
}
|
||||||
|
|
||||||
async prepare(forceTranscode = false) {
|
async prepare(forceTranscode = false) {
|
||||||
var payload = {
|
const payload = {
|
||||||
|
deviceInfo: {
|
||||||
|
deviceId: this.getDeviceId()
|
||||||
|
},
|
||||||
supportedMimeTypes: this.player.playableMimeTypes,
|
supportedMimeTypes: this.player.playableMimeTypes,
|
||||||
mediaPlayer: this.isCasting ? 'chromecast' : 'html5',
|
mediaPlayer: this.isCasting ? 'chromecast' : 'html5',
|
||||||
forceTranscode,
|
forceTranscode,
|
||||||
forceDirectPlay: this.isCasting || this.isVideo // TODO: add transcode support for chromecast
|
forceDirectPlay: this.isCasting || this.isVideo // TODO: add transcode support for chromecast
|
||||||
}
|
}
|
||||||
|
|
||||||
var path = this.episodeId ? `/api/items/${this.libraryItem.id}/play/${this.episodeId}` : `/api/items/${this.libraryItem.id}/play`
|
const path = this.episodeId ? `/api/items/${this.libraryItem.id}/play/${this.episodeId}` : `/api/items/${this.libraryItem.id}/play`
|
||||||
var session = await this.ctx.$axios.$post(path, payload).catch((error) => {
|
const session = await this.ctx.$axios.$post(path, payload).catch((error) => {
|
||||||
console.error('Failed to start stream', error)
|
console.error('Failed to start stream', error)
|
||||||
})
|
})
|
||||||
this.prepareSession(session)
|
this.prepareSession(session)
|
||||||
@ -238,6 +250,10 @@ export default class PlayerHandler {
|
|||||||
closePlayer() {
|
closePlayer() {
|
||||||
console.log('[PlayerHandler] Close Player')
|
console.log('[PlayerHandler] Close Player')
|
||||||
this.sendCloseSession()
|
this.sendCloseSession()
|
||||||
|
this.resetPlayer()
|
||||||
|
}
|
||||||
|
|
||||||
|
resetPlayer() {
|
||||||
if (this.player) {
|
if (this.player) {
|
||||||
this.player.destroy()
|
this.player.destroy()
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import cronParser from 'cron-parser'
|
import cronParser from 'cron-parser'
|
||||||
|
import { nanoid } from 'nanoid'
|
||||||
|
|
||||||
|
Vue.prototype.$randomId = () => nanoid()
|
||||||
|
|
||||||
Vue.prototype.$bytesPretty = (bytes, decimals = 2) => {
|
Vue.prototype.$bytesPretty = (bytes, decimals = 2) => {
|
||||||
if (isNaN(bytes) || bytes == 0) {
|
if (isNaN(bytes) || bytes == 0) {
|
||||||
|
@ -14,7 +14,7 @@ class SessionController {
|
|||||||
return res.sendStatus(404)
|
return res.sendStatus(404)
|
||||||
}
|
}
|
||||||
|
|
||||||
var listeningSessions = []
|
let listeningSessions = []
|
||||||
if (req.query.user) {
|
if (req.query.user) {
|
||||||
listeningSessions = await this.getUserListeningSessionsHelper(req.query.user)
|
listeningSessions = await this.getUserListeningSessionsHelper(req.query.user)
|
||||||
} else {
|
} else {
|
||||||
@ -42,6 +42,25 @@ class SessionController {
|
|||||||
res.json(payload)
|
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) {
|
getOpenSession(req, res) {
|
||||||
var libraryItem = this.db.getLibraryItem(req.session.libraryItemId)
|
var libraryItem = this.db.getLibraryItem(req.session.libraryItemId)
|
||||||
var sessionForClient = req.session.toJSONForClient(libraryItem)
|
var sessionForClient = req.session.toJSONForClient(libraryItem)
|
||||||
|
@ -14,7 +14,6 @@ const PlaybackSession = require('../objects/PlaybackSession')
|
|||||||
const DeviceInfo = require('../objects/DeviceInfo')
|
const DeviceInfo = require('../objects/DeviceInfo')
|
||||||
const Stream = require('../objects/Stream')
|
const Stream = require('../objects/Stream')
|
||||||
|
|
||||||
|
|
||||||
class PlaybackSessionManager {
|
class PlaybackSessionManager {
|
||||||
constructor(db) {
|
constructor(db) {
|
||||||
this.db = db
|
this.db = db
|
||||||
@ -31,13 +30,14 @@ class PlaybackSessionManager {
|
|||||||
}
|
}
|
||||||
getStream(sessionId) {
|
getStream(sessionId) {
|
||||||
const session = this.getSession(sessionId)
|
const session = this.getSession(sessionId)
|
||||||
return session ? session.stream : null
|
return session?.stream || null
|
||||||
}
|
}
|
||||||
|
|
||||||
getDeviceInfo(req) {
|
getDeviceInfo(req) {
|
||||||
const ua = uaParserJs(req.headers['user-agent'])
|
const ua = uaParserJs(req.headers['user-agent'])
|
||||||
const ip = requestIp.getClientIp(req)
|
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()
|
const deviceInfo = new DeviceInfo()
|
||||||
deviceInfo.setData(ip, ua, clientDeviceInfo, serverVersion)
|
deviceInfo.setData(ip, ua, clientDeviceInfo, serverVersion)
|
||||||
@ -138,18 +138,6 @@ class PlaybackSessionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async syncLocalSessionRequest(user, sessionJson, res) {
|
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)
|
const result = await this.syncLocalSession(user, sessionJson)
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
res.status(500).send(result.error)
|
res.status(500).send(result.error)
|
||||||
@ -164,8 +152,8 @@ class PlaybackSessionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async startSession(user, deviceInfo, libraryItem, episodeId, options) {
|
async startSession(user, deviceInfo, libraryItem, episodeId, options) {
|
||||||
// Close any sessions already open for user
|
// Close any sessions already open for user and device
|
||||||
const userSessions = this.sessions.filter(playbackSession => playbackSession.userId === user.id)
|
const userSessions = this.sessions.filter(playbackSession => playbackSession.userId === user.id && playbackSession.deviceId === deviceInfo.deviceId)
|
||||||
for (const session of userSessions) {
|
for (const session of userSessions) {
|
||||||
Logger.info(`[PlaybackSessionManager] startSession: Closing open session "${session.displayTitle}" for user "${user.username}" (Device: ${session.deviceDescription})`)
|
Logger.info(`[PlaybackSessionManager] startSession: Closing open session "${session.displayTitle}" for user "${user.username}" (Device: ${session.deviceDescription})`)
|
||||||
await this.closeSession(user, session, null)
|
await this.closeSession(user, session, null)
|
||||||
@ -268,6 +256,7 @@ class PlaybackSessionManager {
|
|||||||
}
|
}
|
||||||
Logger.debug(`[PlaybackSessionManager] closeSession "${session.id}"`)
|
Logger.debug(`[PlaybackSessionManager] closeSession "${session.id}"`)
|
||||||
SocketAuthority.adminEmitter('user_stream_update', user.toJSONForPublic(this.sessions, this.db.libraryItems))
|
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)
|
return this.removeSession(session.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
class DeviceInfo {
|
class DeviceInfo {
|
||||||
constructor(deviceInfo = null) {
|
constructor(deviceInfo = null) {
|
||||||
|
this.deviceId = null
|
||||||
this.ipAddress = null
|
this.ipAddress = null
|
||||||
|
|
||||||
// From User Agent (see: https://www.npmjs.com/package/ua-parser-js)
|
// From User Agent (see: https://www.npmjs.com/package/ua-parser-js)
|
||||||
@ -32,6 +33,7 @@ class DeviceInfo {
|
|||||||
|
|
||||||
toJSON() {
|
toJSON() {
|
||||||
const obj = {
|
const obj = {
|
||||||
|
deviceId: this.deviceId,
|
||||||
ipAddress: this.ipAddress,
|
ipAddress: this.ipAddress,
|
||||||
browserName: this.browserName,
|
browserName: this.browserName,
|
||||||
browserVersion: this.browserVersion,
|
browserVersion: this.browserVersion,
|
||||||
@ -60,23 +62,42 @@ class DeviceInfo {
|
|||||||
return `${this.osName} ${this.osVersion} / ${this.browserName}`
|
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) {
|
setData(ip, ua, clientDeviceInfo, serverVersion) {
|
||||||
|
this.deviceId = clientDeviceInfo?.deviceId || null
|
||||||
this.ipAddress = ip || null
|
this.ipAddress = ip || null
|
||||||
|
|
||||||
const uaObj = ua || {}
|
this.browserName = ua?.browser.name || null
|
||||||
this.browserName = uaObj.browser.name || null
|
this.browserVersion = ua?.browser.version || null
|
||||||
this.browserVersion = uaObj.browser.version || null
|
this.osName = ua?.os.name || null
|
||||||
this.osName = uaObj.os.name || null
|
this.osVersion = ua?.os.version || null
|
||||||
this.osVersion = uaObj.os.version || null
|
this.deviceType = ua?.device.type || null
|
||||||
this.deviceType = uaObj.device.type || null
|
|
||||||
|
|
||||||
const cdi = clientDeviceInfo || {}
|
this.clientVersion = clientDeviceInfo?.clientVersion || null
|
||||||
this.clientVersion = cdi.clientVersion || null
|
this.manufacturer = clientDeviceInfo?.manufacturer || null
|
||||||
this.manufacturer = cdi.manufacturer || null
|
this.model = clientDeviceInfo?.model || null
|
||||||
this.model = cdi.model || null
|
this.sdkVersion = clientDeviceInfo?.sdkVersion || null
|
||||||
this.sdkVersion = cdi.sdkVersion || null
|
|
||||||
|
|
||||||
this.serverVersion = serverVersion || null
|
this.serverVersion = serverVersion || null
|
||||||
|
|
||||||
|
if (!this.deviceId) {
|
||||||
|
this.deviceId = this.getTempDeviceId()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
module.exports = DeviceInfo
|
module.exports = DeviceInfo
|
@ -55,7 +55,7 @@ class PlaybackSession {
|
|||||||
libraryItemId: this.libraryItemId,
|
libraryItemId: this.libraryItemId,
|
||||||
episodeId: this.episodeId,
|
episodeId: this.episodeId,
|
||||||
mediaType: this.mediaType,
|
mediaType: this.mediaType,
|
||||||
mediaMetadata: this.mediaMetadata ? this.mediaMetadata.toJSON() : null,
|
mediaMetadata: this.mediaMetadata?.toJSON() || null,
|
||||||
chapters: (this.chapters || []).map(c => ({ ...c })),
|
chapters: (this.chapters || []).map(c => ({ ...c })),
|
||||||
displayTitle: this.displayTitle,
|
displayTitle: this.displayTitle,
|
||||||
displayAuthor: this.displayAuthor,
|
displayAuthor: this.displayAuthor,
|
||||||
@ -63,7 +63,7 @@ class PlaybackSession {
|
|||||||
duration: this.duration,
|
duration: this.duration,
|
||||||
playMethod: this.playMethod,
|
playMethod: this.playMethod,
|
||||||
mediaPlayer: this.mediaPlayer,
|
mediaPlayer: this.mediaPlayer,
|
||||||
deviceInfo: this.deviceInfo ? this.deviceInfo.toJSON() : null,
|
deviceInfo: this.deviceInfo?.toJSON() || null,
|
||||||
date: this.date,
|
date: this.date,
|
||||||
dayOfWeek: this.dayOfWeek,
|
dayOfWeek: this.dayOfWeek,
|
||||||
timeListening: this.timeListening,
|
timeListening: this.timeListening,
|
||||||
@ -82,7 +82,7 @@ class PlaybackSession {
|
|||||||
libraryItemId: this.libraryItemId,
|
libraryItemId: this.libraryItemId,
|
||||||
episodeId: this.episodeId,
|
episodeId: this.episodeId,
|
||||||
mediaType: this.mediaType,
|
mediaType: this.mediaType,
|
||||||
mediaMetadata: this.mediaMetadata ? this.mediaMetadata.toJSON() : null,
|
mediaMetadata: this.mediaMetadata?.toJSON() || null,
|
||||||
chapters: (this.chapters || []).map(c => ({ ...c })),
|
chapters: (this.chapters || []).map(c => ({ ...c })),
|
||||||
displayTitle: this.displayTitle,
|
displayTitle: this.displayTitle,
|
||||||
displayAuthor: this.displayAuthor,
|
displayAuthor: this.displayAuthor,
|
||||||
@ -90,7 +90,7 @@ class PlaybackSession {
|
|||||||
duration: this.duration,
|
duration: this.duration,
|
||||||
playMethod: this.playMethod,
|
playMethod: this.playMethod,
|
||||||
mediaPlayer: this.mediaPlayer,
|
mediaPlayer: this.mediaPlayer,
|
||||||
deviceInfo: this.deviceInfo ? this.deviceInfo.toJSON() : null,
|
deviceInfo: this.deviceInfo?.toJSON() || null,
|
||||||
date: this.date,
|
date: this.date,
|
||||||
dayOfWeek: this.dayOfWeek,
|
dayOfWeek: this.dayOfWeek,
|
||||||
timeListening: this.timeListening,
|
timeListening: this.timeListening,
|
||||||
@ -151,6 +151,10 @@ class PlaybackSession {
|
|||||||
return Math.max(0, Math.min(this.currentTime / this.duration, 1))
|
return Math.max(0, Math.min(this.currentTime / this.duration, 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get deviceId() {
|
||||||
|
return this.deviceInfo?.deviceId
|
||||||
|
}
|
||||||
|
|
||||||
get deviceDescription() {
|
get deviceDescription() {
|
||||||
if (!this.deviceInfo) return 'No Device Info'
|
if (!this.deviceInfo) return 'No Device Info'
|
||||||
return this.deviceInfo.deviceDescription
|
return this.deviceInfo.deviceDescription
|
||||||
|
@ -214,6 +214,7 @@ 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.get('/sessions/open', SessionController.getOpenSessions.bind(this))
|
||||||
this.router.post('/session/local', SessionController.syncLocal.bind(this))
|
this.router.post('/session/local', SessionController.syncLocal.bind(this))
|
||||||
this.router.post('/session/local-all', SessionController.syncLocalSessions.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
|
||||||
|
Loading…
Reference in New Issue
Block a user