mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-02-19 11:40:54 +01:00
Merge branch 'advplyr:master' into show-subtitles
This commit is contained in:
commit
0042604e6d
@ -341,7 +341,7 @@ export default {
|
|||||||
if (this.recentEpisode) return this.recentEpisode.title
|
if (this.recentEpisode) return this.recentEpisode.title
|
||||||
const ignorePrefix = this.orderBy === 'media.metadata.title' && this.sortingIgnorePrefix
|
const ignorePrefix = this.orderBy === 'media.metadata.title' && this.sortingIgnorePrefix
|
||||||
if (this.collapsedSeries) return ignorePrefix ? this.collapsedSeries.nameIgnorePrefix : this.collapsedSeries.name
|
if (this.collapsedSeries) return ignorePrefix ? this.collapsedSeries.nameIgnorePrefix : this.collapsedSeries.name
|
||||||
return ignorePrefix ? this.mediaMetadata.titleIgnorePrefix : this.title || '\u00A0'
|
return ignorePrefix ? this.mediaMetadata.titleIgnorePrefix || '\u00A0' : this.title || '\u00A0'
|
||||||
},
|
},
|
||||||
displaySubtitle() {
|
displaySubtitle() {
|
||||||
if (!this.libraryItem) return '\u00A0'
|
if (!this.libraryItem) return '\u00A0'
|
||||||
@ -369,7 +369,10 @@ export default {
|
|||||||
if (this.orderBy === 'media.duration') return 'Duration: ' + this.$elapsedPrettyExtended(this.media.duration, false)
|
if (this.orderBy === 'media.duration') return 'Duration: ' + this.$elapsedPrettyExtended(this.media.duration, false)
|
||||||
if (this.orderBy === 'size') return 'Size: ' + this.$bytesPretty(this._libraryItem.size)
|
if (this.orderBy === 'size') return 'Size: ' + this.$bytesPretty(this._libraryItem.size)
|
||||||
if (this.orderBy === 'media.numTracks') return `${this.numEpisodes} Episodes`
|
if (this.orderBy === 'media.numTracks') return `${this.numEpisodes} Episodes`
|
||||||
if (this.orderBy === 'media.metadata.publishedYear' && this.mediaMetadata.publishedYear) return 'Published ' + this.mediaMetadata.publishedYear
|
if (this.orderBy === 'media.metadata.publishedYear') {
|
||||||
|
if (this.mediaMetadata.publishedYear) return 'Published ' + this.mediaMetadata.publishedYear
|
||||||
|
return '\u00A0'
|
||||||
|
}
|
||||||
return null
|
return null
|
||||||
},
|
},
|
||||||
episodeProgress() {
|
episodeProgress() {
|
||||||
|
@ -229,6 +229,11 @@ export default {
|
|||||||
text: this.$strings.LabelRSSFeedOpen,
|
text: this.$strings.LabelRSSFeedOpen,
|
||||||
value: 'feed-open',
|
value: 'feed-open',
|
||||||
sublist: false
|
sublist: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelShareOpen,
|
||||||
|
value: 'share-open',
|
||||||
|
sublist: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full h-screen max-h-screen overflow-hidden" :style="{ backgroundColor: coverRgb }">
|
<div class="w-full h-dvh max-h-dvh overflow-hidden" :style="{ backgroundColor: coverRgb }">
|
||||||
<div class="w-screen h-screen absolute inset-0 pointer-events-none" style="background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(38, 38, 38, 1) 80%)"></div>
|
<div class="w-screen h-screen absolute inset-0 pointer-events-none" style="background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(38, 38, 38, 1) 80%)"></div>
|
||||||
<div class="absolute inset-0 w-screen h-screen flex items-center justify-center z-10">
|
<div class="absolute inset-0 w-screen h-dvh flex items-center justify-center z-10">
|
||||||
<div class="w-full p-2 sm:p-4 md:p-8">
|
<div class="w-full p-2 sm:p-4 md:p-8">
|
||||||
<div v-if="!isMobileLandscape" :style="{ width: coverWidth + 'px', height: coverHeight + 'px' }" class="mx-auto overflow-hidden rounded-xl my-2">
|
<div v-if="!isMobileLandscape" :style="{ width: coverWidth + 'px', height: coverHeight + 'px' }" class="mx-auto overflow-hidden rounded-xl my-2">
|
||||||
<img ref="coverImg" :src="coverUrl" class="object-contain w-full h-full" @load="coverImageLoaded" />
|
<img ref="coverImg" :src="coverUrl" class="object-contain w-full h-full" @load="coverImageLoaded" />
|
||||||
@ -113,6 +113,8 @@ export default {
|
|||||||
.then((color) => {
|
.then((color) => {
|
||||||
this.coverRgb = color.rgba
|
this.coverRgb = color.rgba
|
||||||
this.coverBgIsLight = color.isLight
|
this.coverBgIsLight = color.isLight
|
||||||
|
|
||||||
|
document.body.style.backgroundColor = color.hex
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
|
@ -512,6 +512,7 @@
|
|||||||
"LabelSettingsStoreMetadataWithItemHelp": "By default metadata files are stored in /metadata/items, enabling this setting will store metadata files in your library item folders",
|
"LabelSettingsStoreMetadataWithItemHelp": "By default metadata files are stored in /metadata/items, enabling this setting will store metadata files in your library item folders",
|
||||||
"LabelSettingsTimeFormat": "Time Format",
|
"LabelSettingsTimeFormat": "Time Format",
|
||||||
"LabelShare": "Share",
|
"LabelShare": "Share",
|
||||||
|
"LabelShareOpen": "Share Open",
|
||||||
"LabelShareURL": "Share URL",
|
"LabelShareURL": "Share URL",
|
||||||
"LabelShowAll": "Show All",
|
"LabelShowAll": "Show All",
|
||||||
"LabelShowSeconds": "Show seconds",
|
"LabelShowSeconds": "Show seconds",
|
||||||
|
@ -74,7 +74,7 @@ class Server {
|
|||||||
this.podcastManager = new PodcastManager(this.watcher, this.notificationManager)
|
this.podcastManager = new PodcastManager(this.watcher, this.notificationManager)
|
||||||
this.audioMetadataManager = new AudioMetadataMangaer()
|
this.audioMetadataManager = new AudioMetadataMangaer()
|
||||||
this.rssFeedManager = new RssFeedManager()
|
this.rssFeedManager = new RssFeedManager()
|
||||||
this.cronManager = new CronManager(this.podcastManager)
|
this.cronManager = new CronManager(this.podcastManager, this.playbackSessionManager)
|
||||||
this.apiCacheManager = new ApiCacheManager()
|
this.apiCacheManager = new ApiCacheManager()
|
||||||
this.binaryManager = new BinaryManager()
|
this.binaryManager = new BinaryManager()
|
||||||
|
|
||||||
|
@ -42,19 +42,14 @@ class ShareController {
|
|||||||
const playbackSession = ShareManager.findPlaybackSessionBySessionId(req.cookies.share_session_id)
|
const playbackSession = ShareManager.findPlaybackSessionBySessionId(req.cookies.share_session_id)
|
||||||
|
|
||||||
if (playbackSession) {
|
if (playbackSession) {
|
||||||
const playbackSessionMediaItemShare = ShareManager.findByMediaItemId(playbackSession.mediaItemId)
|
if (mediaItemShare.id === playbackSession.mediaItemShareId) {
|
||||||
if (!playbackSessionMediaItemShare) {
|
|
||||||
Logger.error(`[ShareController] Share playback session ${req.cookies.share_session_id} media item share not found with id ${playbackSession.mediaItemId}`)
|
|
||||||
return res.sendStatus(500)
|
|
||||||
}
|
|
||||||
if (playbackSessionMediaItemShare.slug === slug) {
|
|
||||||
Logger.debug(`[ShareController] Found share playback session ${req.cookies.share_session_id}`)
|
Logger.debug(`[ShareController] Found share playback session ${req.cookies.share_session_id}`)
|
||||||
mediaItemShare.playbackSession = playbackSession.toJSONForClient()
|
mediaItemShare.playbackSession = playbackSession.toJSONForClient()
|
||||||
return res.json(mediaItemShare)
|
return res.json(mediaItemShare)
|
||||||
} else {
|
} else {
|
||||||
// TODO: Close old session and use same session id
|
// Changed media item share - close other session
|
||||||
Logger.info(`[ShareController] Share playback session found with id ${req.cookies.share_session_id} but media item share slug ${playbackSessionMediaItemShare.slug} does not match requested slug ${slug}`)
|
Logger.debug(`[ShareController] Other playback session is already open for share session. Closing session "${playbackSession.displayTitle}"`)
|
||||||
res.clearCookie('share_session_id')
|
ShareManager.closeSharePlaybackSession(playbackSession)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Logger.info(`[ShareController] Share playback session not found with id ${req.cookies.share_session_id}`)
|
Logger.info(`[ShareController] Share playback session not found with id ${req.cookies.share_session_id}`)
|
||||||
@ -238,6 +233,7 @@ class ShareController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
playbackSession.currentTime = Math.min(currentTime, playbackSession.duration)
|
playbackSession.currentTime = Math.min(currentTime, playbackSession.duration)
|
||||||
|
playbackSession.updatedAt = Date.now()
|
||||||
Logger.debug(`[ShareController] Update share playback session ${req.cookies.share_session_id} currentTime: ${playbackSession.currentTime}`)
|
Logger.debug(`[ShareController] Update share playback session ${req.cookies.share_session_id} currentTime: ${playbackSession.currentTime}`)
|
||||||
res.sendStatus(204)
|
res.sendStatus(204)
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,14 @@ const Logger = require('../Logger')
|
|||||||
const Database = require('../Database')
|
const Database = require('../Database')
|
||||||
const LibraryScanner = require('../scanner/LibraryScanner')
|
const LibraryScanner = require('../scanner/LibraryScanner')
|
||||||
|
|
||||||
|
const ShareManager = require('./ShareManager')
|
||||||
|
|
||||||
class CronManager {
|
class CronManager {
|
||||||
constructor(podcastManager) {
|
constructor(podcastManager, playbackSessionManager) {
|
||||||
|
/** @type {import('./PodcastManager')} */
|
||||||
this.podcastManager = podcastManager
|
this.podcastManager = podcastManager
|
||||||
|
/** @type {import('./PlaybackSessionManager')} */
|
||||||
|
this.playbackSessionManager = playbackSessionManager
|
||||||
|
|
||||||
this.libraryScanCrons = []
|
this.libraryScanCrons = []
|
||||||
this.podcastCrons = []
|
this.podcastCrons = []
|
||||||
@ -19,10 +24,26 @@ class CronManager {
|
|||||||
* @param {import('../objects/Library')[]} libraries
|
* @param {import('../objects/Library')[]} libraries
|
||||||
*/
|
*/
|
||||||
async init(libraries) {
|
async init(libraries) {
|
||||||
|
this.initOpenSessionCleanupCron()
|
||||||
this.initLibraryScanCrons(libraries)
|
this.initLibraryScanCrons(libraries)
|
||||||
await this.initPodcastCrons()
|
await this.initPodcastCrons()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize open session cleanup cron
|
||||||
|
* Runs every day at 00:30
|
||||||
|
* Closes open share sessions that have not been updated in 24 hours
|
||||||
|
* Closes open playback sessions that have not been updated in 36 hours
|
||||||
|
* TODO: Clients should re-open the session if it is closed so that stale sessions can be closed sooner
|
||||||
|
*/
|
||||||
|
initOpenSessionCleanupCron() {
|
||||||
|
cron.schedule('30 0 * * *', async () => {
|
||||||
|
Logger.debug('[CronManager] Open session cleanup cron executing')
|
||||||
|
ShareManager.closeStaleOpenShareSessions()
|
||||||
|
await this.playbackSessionManager.closeStaleOpenSessions()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize library scan crons
|
* Initialize library scan crons
|
||||||
* @param {import('../objects/Library')[]} libraries
|
* @param {import('../objects/Library')[]} libraries
|
||||||
|
@ -21,6 +21,8 @@ class PlaybackSessionManager {
|
|||||||
this.StreamsPath = Path.join(global.MetadataPath, 'streams')
|
this.StreamsPath = Path.join(global.MetadataPath, 'streams')
|
||||||
|
|
||||||
this.oldPlaybackSessionMap = {} // TODO: Remove after updated mobile versions
|
this.oldPlaybackSessionMap = {} // TODO: Remove after updated mobile versions
|
||||||
|
|
||||||
|
/** @type {PlaybackSession[]} */
|
||||||
this.sessions = []
|
this.sessions = []
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -346,6 +348,10 @@ class PlaybackSessionManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} sessionId
|
||||||
|
*/
|
||||||
async removeSession(sessionId) {
|
async removeSession(sessionId) {
|
||||||
const session = this.sessions.find((s) => s.id === sessionId)
|
const session = this.sessions.find((s) => s.id === sessionId)
|
||||||
if (!session) return
|
if (!session) return
|
||||||
@ -378,5 +384,18 @@ class PlaybackSessionManager {
|
|||||||
Logger.error(`[PlaybackSessionManager] cleanOrphanStreams failed`, error)
|
Logger.error(`[PlaybackSessionManager] cleanOrphanStreams failed`, error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close all open sessions that have not been updated in the last 36 hours
|
||||||
|
*/
|
||||||
|
async closeStaleOpenSessions() {
|
||||||
|
const updatedAtTimeCutoff = Date.now() - 1000 * 60 * 60 * 36
|
||||||
|
const staleSessions = this.sessions.filter((session) => session.updatedAt < updatedAtTimeCutoff)
|
||||||
|
for (const session of staleSessions) {
|
||||||
|
const sessionLastUpdate = new Date(session.updatedAt)
|
||||||
|
Logger.info(`[PlaybackSessionManager] Closing stale session "${session.displayTitle}" (${session.id}) last updated at ${sessionLastUpdate}`)
|
||||||
|
await this.removeSession(session.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
module.exports = PlaybackSessionManager
|
module.exports = PlaybackSessionManager
|
||||||
|
@ -25,10 +25,19 @@ class ShareManager {
|
|||||||
* @param {import('../objects/PlaybackSession')} playbackSession
|
* @param {import('../objects/PlaybackSession')} playbackSession
|
||||||
*/
|
*/
|
||||||
addOpenSharePlaybackSession(playbackSession) {
|
addOpenSharePlaybackSession(playbackSession) {
|
||||||
Logger.info(`[ShareManager] Adding new open share playback session ${playbackSession.shareSessionId}`)
|
Logger.info(`[ShareManager] Adding new open share playback session "${playbackSession.displayTitle}"`)
|
||||||
this.openSharePlaybackSessions.push(playbackSession)
|
this.openSharePlaybackSessions.push(playbackSession)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {import('../objects/PlaybackSession')} playbackSession
|
||||||
|
*/
|
||||||
|
closeSharePlaybackSession(playbackSession) {
|
||||||
|
Logger.info(`[ShareManager] Closing share playback session "${playbackSession.displayTitle}"`)
|
||||||
|
this.openSharePlaybackSessions = this.openSharePlaybackSessions.filter((s) => s.id !== playbackSession.id)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find an open media item share by media item ID
|
* Find an open media item share by media item ID
|
||||||
* @param {string} mediaItemId
|
* @param {string} mediaItemId
|
||||||
@ -153,5 +162,18 @@ class ShareManager {
|
|||||||
destroyMediaItemShare(mediaItemShareId) {
|
destroyMediaItemShare(mediaItemShareId) {
|
||||||
return Database.models.mediaItemShare.destroy({ where: { id: mediaItemShareId } })
|
return Database.models.mediaItemShare.destroy({ where: { id: mediaItemShareId } })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close open share sessions that have not been updated in the last 24 hours
|
||||||
|
*/
|
||||||
|
closeStaleOpenShareSessions() {
|
||||||
|
const updatedAtTimeCutoff = Date.now() - 1000 * 60 * 60 * 24
|
||||||
|
const staleSessions = this.openSharePlaybackSessions.filter((session) => session.updatedAt < updatedAtTimeCutoff)
|
||||||
|
for (const session of staleSessions) {
|
||||||
|
const sessionLastUpdate = new Date(session.updatedAt)
|
||||||
|
Logger.info(`[PlaybackSessionManager] Closing stale session "${session.displayTitle}" (${session.id}) last updated at ${sessionLastUpdate}`)
|
||||||
|
this.closeSharePlaybackSession(session)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
module.exports = new ShareManager()
|
module.exports = new ShareManager()
|
||||||
|
@ -568,7 +568,6 @@ class LibraryItem extends Model {
|
|||||||
oldLibraryItem.numEpisodesIncomplete = li.numEpisodesIncomplete
|
oldLibraryItem.numEpisodesIncomplete = li.numEpisodesIncomplete
|
||||||
}
|
}
|
||||||
if (li.mediaType === 'book' && options.include?.includes?.('share')) {
|
if (li.mediaType === 'book' && options.include?.includes?.('share')) {
|
||||||
console.log('Lookup share for media item id', li.mediaId)
|
|
||||||
oldLibraryItem.mediaItemShare = ShareManager.findByMediaItemId(li.mediaId)
|
oldLibraryItem.mediaItemShare = ShareManager.findByMediaItemId(li.mediaId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -412,6 +412,11 @@ module.exports = {
|
|||||||
model: Database.feedModel,
|
model: Database.feedModel,
|
||||||
required: true
|
required: true
|
||||||
})
|
})
|
||||||
|
} else if (filterGroup === 'share-open') {
|
||||||
|
bookIncludes.push({
|
||||||
|
model: Database.mediaItemShareModel,
|
||||||
|
required: true
|
||||||
|
})
|
||||||
} else if (filterGroup === 'ebooks' && filterValue === 'supplementary') {
|
} else if (filterGroup === 'ebooks' && filterValue === 'supplementary') {
|
||||||
// TODO: Temp workaround for filtering supplementary ebook
|
// TODO: Temp workaround for filtering supplementary ebook
|
||||||
libraryItemWhere['libraryFiles'] = {
|
libraryItemWhere['libraryFiles'] = {
|
||||||
|
Loading…
Reference in New Issue
Block a user