mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-12-10 22:09:38 +00:00
Update listening sessions per device and show open sessions
This commit is contained in:
parent
8fca84e4bd
commit
25ca950dd0
11 changed files with 195 additions and 39 deletions
|
|
@ -460,6 +460,13 @@ export default {
|
|||
showFailedProgressSyncs() {
|
||||
if (!isNaN(this.syncFailedToast)) this.$toast.dismiss(this.syncFailedToast)
|
||||
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() {
|
||||
|
|
|
|||
|
|
@ -98,7 +98,8 @@
|
|||
</div>
|
||||
|
||||
<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>
|
||||
</modals-modal>
|
||||
|
|
@ -157,6 +158,9 @@ export default {
|
|||
},
|
||||
timeFormat() {
|
||||
return this.$store.state.serverSettings.timeFormat
|
||||
},
|
||||
isOpenSession() {
|
||||
return !!this._session.open
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -188,6 +192,24 @@ export default {
|
|||
var errMsg = error.response ? error.response.data || '' : ''
|
||||
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() {}
|
||||
|
|
|
|||
|
|
@ -299,8 +299,17 @@ export default {
|
|||
userStreamUpdate(user) {
|
||||
this.$store.commit('users/updateUserOnline', user)
|
||||
},
|
||||
userSessionClosed(sessionId) {
|
||||
if (this.$refs.streamContainer) this.$refs.streamContainer.sessionClosedEvent(sessionId)
|
||||
},
|
||||
userMediaProgressUpdate(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) {
|
||||
if (this.currentLibraryId !== collection.libraryId) return
|
||||
|
|
@ -405,6 +414,7 @@ export default {
|
|||
this.socket.on('user_online', this.userOnline)
|
||||
this.socket.on('user_offline', this.userOffline)
|
||||
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)
|
||||
|
||||
// Collection Listeners
|
||||
|
|
|
|||
|
|
@ -52,9 +52,53 @@
|
|||
</div>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</template>
|
||||
|
||||
|
|
@ -81,6 +125,7 @@ export default {
|
|||
showSessionModal: false,
|
||||
selectedSession: null,
|
||||
listeningSessions: [],
|
||||
openListeningSessions: [],
|
||||
numPages: 0,
|
||||
total: 0,
|
||||
currentPage: 0,
|
||||
|
|
@ -114,6 +159,9 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
closedSession() {
|
||||
this.loadOpenSessions()
|
||||
},
|
||||
removedSession() {
|
||||
// If on last page and this was the last session then load prev page
|
||||
if (this.currentPage == this.numPages - 1) {
|
||||
|
|
@ -222,7 +270,7 @@ export default {
|
|||
async loadSessions(page) {
|
||||
var userFilterQuery = this.selectedUser ? `&user=${this.selectedUser}` : ''
|
||||
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
|
||||
})
|
||||
if (!data) {
|
||||
|
|
@ -236,8 +284,24 @@ export default {
|
|||
this.listeningSessions = data.sessions
|
||||
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() {
|
||||
this.loadSessions(0)
|
||||
this.loadOpenSessions()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
|
|
|||
|
|
@ -173,16 +173,28 @@ export default class PlayerHandler {
|
|||
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) {
|
||||
var payload = {
|
||||
const payload = {
|
||||
deviceInfo: {
|
||||
deviceId: this.getDeviceId()
|
||||
},
|
||||
supportedMimeTypes: this.player.playableMimeTypes,
|
||||
mediaPlayer: this.isCasting ? 'chromecast' : 'html5',
|
||||
forceTranscode,
|
||||
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`
|
||||
var session = await this.ctx.$axios.$post(path, payload).catch((error) => {
|
||||
const path = this.episodeId ? `/api/items/${this.libraryItem.id}/play/${this.episodeId}` : `/api/items/${this.libraryItem.id}/play`
|
||||
const session = await this.ctx.$axios.$post(path, payload).catch((error) => {
|
||||
console.error('Failed to start stream', error)
|
||||
})
|
||||
this.prepareSession(session)
|
||||
|
|
@ -238,6 +250,10 @@ export default class PlayerHandler {
|
|||
closePlayer() {
|
||||
console.log('[PlayerHandler] Close Player')
|
||||
this.sendCloseSession()
|
||||
this.resetPlayer()
|
||||
}
|
||||
|
||||
resetPlayer() {
|
||||
if (this.player) {
|
||||
this.player.destroy()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
import Vue from 'vue'
|
||||
import cronParser from 'cron-parser'
|
||||
import { nanoid } from 'nanoid'
|
||||
|
||||
Vue.prototype.$randomId = () => nanoid()
|
||||
|
||||
Vue.prototype.$bytesPretty = (bytes, decimals = 2) => {
|
||||
if (isNaN(bytes) || bytes == 0) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue