diff --git a/client/components/app/Appbar.vue b/client/components/app/Appbar.vue index 594ccb6b..20ff627f 100644 --- a/client/components/app/Appbar.vue +++ b/client/components/app/Appbar.vue @@ -58,13 +58,13 @@ play_arrow {{ $strings.ButtonPlay }} - + - + - + @@ -103,6 +103,9 @@ export default { isPodcastLibrary() { return this.libraryMediaType === 'podcast' }, + isBookLibrary() { + return this.libraryMediaType === 'book' + }, isHome() { return this.$route.name === 'library-library' }, @@ -181,12 +184,15 @@ export default { const queueItems = [] libraryItems.forEach((item) => { + let subtitle = '' + if (item.mediaType === 'book') subtitle = item.media.metadata.authors.map((au) => au.name).join(', ') + else if (item.mediaType === 'music') subtitle = item.media.metadata.artists.join(', ') queueItems.push({ libraryItemId: item.id, libraryId: item.libraryId, episodeId: null, title: item.media.metadata.title, - subtitle: item.media.metadata.authors.map((au) => au.name).join(', '), + subtitle, caption: '', duration: item.media.duration || null, coverPath: item.media.coverPath || null diff --git a/client/components/app/BookShelfCategorized.vue b/client/components/app/BookShelfCategorized.vue index bb617086..0f60feac 100644 --- a/client/components/app/BookShelfCategorized.vue +++ b/client/components/app/BookShelfCategorized.vue @@ -136,7 +136,7 @@ export default { const mediaItem = { id: thisEntity.id, mediaType: thisEntity.mediaType, - hasTracks: thisEntity.mediaType === 'podcast' || thisEntity.media.numTracks || (thisEntity.media.tracks && thisEntity.media.tracks.length) + hasTracks: thisEntity.mediaType === 'podcast' || thisEntity.media.audioFile || thisEntity.media.numTracks || (thisEntity.media.tracks && thisEntity.media.tracks.length) } this.$store.commit('globals/setMediaItemSelected', { item: mediaItem, selected: isSelecting }) } else { @@ -147,7 +147,7 @@ export default { const mediaItem = { id: entity.id, mediaType: entity.mediaType, - hasTracks: entity.mediaType === 'podcast' || entity.media.numTracks || (entity.media.tracks && entity.media.tracks.length) + hasTracks: entity.mediaType === 'podcast' || entity.media.audioFile || entity.media.numTracks || (entity.media.tracks && entity.media.tracks.length) } this.$store.commit('globals/toggleMediaItemSelected', mediaItem) } diff --git a/client/components/app/LazyBookshelf.vue b/client/components/app/LazyBookshelf.vue index 58578fae..bdd06541 100644 --- a/client/components/app/LazyBookshelf.vue +++ b/client/components/app/LazyBookshelf.vue @@ -6,7 +6,7 @@ - + {{ $getString('MessageXLibraryIsEmpty', [libraryName]) }} {{ $strings.ButtonConfigureScanner }} @@ -16,7 +16,7 @@ {{ emptyMessage }} - + {{ $strings.ButtonClearFilter }} @@ -81,8 +81,11 @@ export default { showExperimentalFeatures() { return this.$store.state.showExperimentalFeatures }, + libraryMediaType() { + return this.$store.getters['libraries/getCurrentLibraryMediaType'] + }, isPodcast() { - return this.$store.getters['libraries/getCurrentLibraryMediaType'] == 'podcast' + return this.libraryMediaType === 'podcast' }, emptyMessage() { if (this.page === 'series') return this.$strings.MessageBookshelfNoSeries @@ -96,7 +99,7 @@ export default { return this.$strings.MessageNoResults }, entityName() { - if (!this.page) return 'books' + if (!this.page) return 'items' return this.page }, seriesSortBy() { @@ -158,11 +161,8 @@ export default { libraryName() { return this.$store.getters['libraries/getCurrentLibraryName'] }, - isEntityBook() { - return this.entityName === 'series-books' || this.entityName === 'books' - }, bookWidth() { - var coverSize = this.$store.getters['user/getUserSetting']('bookshelfCoverSize') + const coverSize = this.$store.getters['user/getUserSetting']('bookshelfCoverSize') if (this.isCoverSquareAspectRatio || this.entityName === 'playlists') return coverSize * 1.6 return coverSize }, @@ -192,7 +192,8 @@ export default { }, shelfHeight() { if (this.isAlternativeBookshelfView) { - var extraTitleSpace = this.isEntityBook ? 80 : 40 + const isItemEntity = this.entityName === 'series-books' || this.entityName === 'items' + const extraTitleSpace = isItemEntity ? 80 : this.entityName === 'albums' ? 60 : 40 return this.entityHeight + extraTitleSpace * this.sizeMultiplier } return this.entityHeight + 40 @@ -214,7 +215,7 @@ export default { this.$store.dispatch('user/updateUserSettings', { filterBy: 'all' }) }, editEntity(entity) { - if (this.entityName === 'books' || this.entityName === 'series-books') { + if (this.entityName === 'items' || this.entityName === 'series-books') { const bookIds = this.entities.map((e) => e.id) this.$store.commit('setBookshelfBookIds', bookIds) this.$store.commit('showEditModal', entity) @@ -229,7 +230,7 @@ export default { this.isSelectionMode = false }, selectEntity(entity, shiftKey) { - if (this.entityName === 'books' || this.entityName === 'series-books') { + if (this.entityName === 'items' || this.entityName === 'series-books') { const indexOf = this.entities.findIndex((ent) => ent && ent.id === entity.id) const lastLastItemIndexSelected = this.lastItemIndexSelected if (!this.selectedMediaItems.some((i) => i.id === entity.id)) { @@ -273,9 +274,8 @@ export default { const mediaItem = { id: thisEntity.id, mediaType: thisEntity.mediaType, - hasTracks: thisEntity.mediaType === 'podcast' || thisEntity.media.numTracks || (thisEntity.media.tracks && thisEntity.media.tracks.length) + hasTracks: thisEntity.mediaType === 'podcast' || thisEntity.media.audioFile || thisEntity.media.numTracks || (thisEntity.media.tracks && thisEntity.media.tracks.length) } - console.log('Setting media item selected', mediaItem, 'Num Selected=', this.selectedMediaItems.length) this.$store.commit('globals/setMediaItemSelected', { item: mediaItem, selected: isSelecting }) } else { console.error('Invalid entity index', i) @@ -285,7 +285,7 @@ export default { const mediaItem = { id: entity.id, mediaType: entity.mediaType, - hasTracks: entity.mediaType === 'podcast' || entity.media.numTracks || (entity.media.tracks && entity.media.tracks.length) + hasTracks: entity.mediaType === 'podcast' || entity.media.audioFile || entity.media.numTracks || (entity.media.tracks && entity.media.tracks.length) } this.$store.commit('globals/toggleMediaItemSelected', mediaItem) } @@ -316,7 +316,7 @@ export default { this.currentSFQueryString = this.buildSearchParams() } - const entityPath = this.entityName === 'books' || this.entityName === 'series-books' ? 'items' : this.entityName + const entityPath = this.entityName === 'series-books' ? 'items' : this.entityName const sfQueryString = this.currentSFQueryString ? this.currentSFQueryString + '&' : '' const fullQueryString = `?${sfQueryString}limit=${this.booksPerFetch}&page=${page}&minified=1&include=rssfeed` @@ -517,7 +517,7 @@ export default { }, libraryItemUpdated(libraryItem) { console.log('Item updated', libraryItem) - if (this.entityName === 'books' || this.entityName === 'series-books') { + if (this.entityName === 'items' || this.entityName === 'series-books') { var indexOf = this.entities.findIndex((ent) => ent && ent.id === libraryItem.id) if (indexOf >= 0) { this.entities[indexOf] = libraryItem @@ -528,7 +528,7 @@ export default { } }, libraryItemRemoved(libraryItem) { - if (this.entityName === 'books' || this.entityName === 'series-books') { + if (this.entityName === 'items' || this.entityName === 'series-books') { var indexOf = this.entities.findIndex((ent) => ent && ent.id === libraryItem.id) if (indexOf >= 0) { this.entities = this.entities.filter((ent) => ent.id !== libraryItem.id) diff --git a/client/components/app/StreamContainer.vue b/client/components/app/StreamContainer.vue index abfbb68d..9b5ddff1 100644 --- a/client/components/app/StreamContainer.vue +++ b/client/components/app/StreamContainer.vue @@ -12,6 +12,7 @@ person {{ podcastAuthor }} + {{ musicArtists }} {{ author.name }}, @@ -148,6 +149,10 @@ export default { if (!this.isPodcast) return null return this.mediaMetadata.author || 'Unknown' }, + musicArtists() { + if (!this.isMusic) return null + return this.mediaMetadata.artists.join(', ') + }, playerQueueItems() { return this.$store.state.playerQueueItems || [] } diff --git a/client/components/cards/LazyAlbumCard.vue b/client/components/cards/LazyAlbumCard.vue index aba07840..6fdebc58 100644 --- a/client/components/cards/LazyAlbumCard.vue +++ b/client/components/cards/LazyAlbumCard.vue @@ -12,6 +12,7 @@ {{ title }} + {{ artist || ' ' }} @@ -51,12 +52,15 @@ export default { return 0.875 }, sizeMultiplier() { - if (this.bookCoverAspectRatio === 1) return this.width / (120 * 1.6 * 2) - return this.width / 240 + const baseSize = this.bookCoverAspectRatio === 1 ? 192 : 120 + return this.width / baseSize }, title() { return this.album ? this.album.title : '' }, + artist() { + return this.album ? this.album.artist : '' + }, store() { return this.$store || this.$nuxt.$store }, diff --git a/client/components/cards/LazyBookCard.vue b/client/components/cards/LazyBookCard.vue index 7e9a400f..dc7175bc 100644 --- a/client/components/cards/LazyBookCard.vue +++ b/client/components/cards/LazyBookCard.vue @@ -70,7 +70,7 @@ - + more_vert @@ -276,6 +276,10 @@ export default { authorLF() { return this.mediaMetadata.authorNameLF }, + artist() { + const artists = this.mediaMetadata.artists || [] + return artists.join(', ') + }, displayTitle() { if (this.recentEpisode) return this.recentEpisode.title const ignorePrefix = this.orderBy === 'media.metadata.title' && this.sortingIgnorePrefix @@ -285,6 +289,7 @@ export default { displayLineTwo() { if (this.recentEpisode) return this.title if (this.isPodcast) return this.author + if (this.isMusic) return this.artist if (this.collapsedSeries) return '' if (this.isAuthorBookshelfView) { return this.mediaMetadata.publishedYear || '' @@ -345,7 +350,7 @@ export default { return !this.isSelectionMode && !this.showPlayButton && this.hasEbook && (this.showExperimentalFeatures || this.enableEReader) }, showPlayButton() { - return !this.isSelectionMode && !this.isMissing && !this.isInvalid && !this.isStreaming && (this.numTracks || this.recentEpisode) + return !this.isSelectionMode && !this.isMissing && !this.isInvalid && !this.isStreaming && (this.numTracks || this.recentEpisode || this.isMusic) }, showSmallEBookIcon() { return !this.isSelectionMode && this.hasEbook && (this.showExperimentalFeatures || this.enableEReader) @@ -370,7 +375,7 @@ export default { if (this.isPodcast) return 'Podcast has no episodes' return 'Item has no audio tracks & ebook' } - var txt = '' + let txt = '' if (this.numMissingParts) { txt += `${this.numMissingParts} missing parts.` } @@ -381,7 +386,7 @@ export default { return txt || 'Unknown Error' }, overlayWrapperClasslist() { - var classes = [] + const classes = [] if (this.isSelectionMode) classes.push('bg-opacity-60') else classes.push('bg-opacity-40') if (this.selected) { @@ -405,6 +410,8 @@ export default { return this.store.getters['user/getIsAdminOrUp'] }, moreMenuItems() { + if (this.isMusic) return [] + if (this.recentEpisode) { const items = [ { @@ -442,7 +449,7 @@ export default { return items } - var items = [] + let items = [] if (!this.isPodcast) { items = [ { diff --git a/client/components/controls/LibraryFilterSelect.vue b/client/components/controls/LibraryFilterSelect.vue index b585681a..4cb34e56 100644 --- a/client/components/controls/LibraryFilterSelect.vue +++ b/client/components/controls/LibraryFilterSelect.vue @@ -87,8 +87,14 @@ export default { this.$emit('input', val) } }, + libraryMediaType() { + return this.$store.getters['libraries/getCurrentLibraryMediaType'] + }, isPodcast() { - return this.$store.getters['libraries/getCurrentLibraryMediaType'] == 'podcast' + return this.libraryMediaType === 'podcast' + }, + isMusic() { + return this.libraryMediaType === 'music' }, seriesItems() { return [ @@ -214,9 +220,33 @@ export default { } ] }, + musicItems() { + return [ + { + text: this.$strings.LabelAll, + value: 'all' + }, + { + text: this.$strings.LabelGenre, + value: 'genres', + sublist: true + }, + { + text: this.$strings.LabelTag, + value: 'tags', + sublist: true + }, + { + text: this.$strings.ButtonIssues, + value: 'issues', + sublist: false + } + ] + }, selectItems() { if (this.isSeries) return this.seriesItems if (this.isPodcast) return this.podcastItems + if (this.isMusic) return this.musicItems return this.bookItems }, selectedItemSublist() { diff --git a/client/components/controls/LibrarySortSelect.vue b/client/components/controls/LibrarySortSelect.vue index 58ed864a..3857f47b 100644 --- a/client/components/controls/LibrarySortSelect.vue +++ b/client/components/controls/LibrarySortSelect.vue @@ -50,8 +50,14 @@ export default { this.$emit('update:descending', val) } }, + libraryMediaType() { + return this.$store.getters['libraries/getCurrentLibraryMediaType'] + }, isPodcast() { - return this.$store.getters['libraries/getCurrentLibraryMediaType'] == 'podcast' + return this.libraryMediaType === 'podcast' + }, + isMusic() { + return this.libraryMediaType === 'music' }, podcastItems() { return [ @@ -134,10 +140,40 @@ export default { } ] }, + musicItems() { + return [ + { + text: this.$strings.LabelTitle, + value: 'media.metadata.title' + }, + { + text: this.$strings.LabelAddedAt, + value: 'addedAt' + }, + { + text: this.$strings.LabelSize, + value: 'size' + }, + { + text: this.$strings.LabelDuration, + value: 'media.duration' + }, + { + text: this.$strings.LabelFileBirthtime, + value: 'birthtimeMs' + }, + { + text: this.$strings.LabelFileModified, + value: 'mtimeMs' + } + ] + }, selectItems() { let items = null if (this.isPodcast) { items = this.podcastItems + } else if (this.isMusic) { + items = this.musicItems } else if (this.$store.getters['user/getUserSetting']('filterBy').startsWith('series.')) { items = this.seriesItems } else { diff --git a/client/mixins/bookshelfCardsHelpers.js b/client/mixins/bookshelfCardsHelpers.js index 2d968806..b950d8b9 100644 --- a/client/mixins/bookshelfCardsHelpers.js +++ b/client/mixins/bookshelfCardsHelpers.js @@ -60,7 +60,7 @@ export default { sortingIgnorePrefix: !!this.sortingIgnorePrefix } - if (this.entityName === 'books') { + if (this.entityName === 'items') { props.filterBy = this.filterBy props.orderBy = this.orderBy } else if (this.entityName === 'series') { diff --git a/client/players/PlayerHandler.js b/client/players/PlayerHandler.js index 146f4c35..93d884f6 100644 --- a/client/players/PlayerHandler.js +++ b/client/players/PlayerHandler.js @@ -145,13 +145,11 @@ export default class PlayerHandler { console.log('[PlayerHandler] Player state change', state) this.playerState = state - if (!this.isMusic) { - if (this.playerState === 'PLAYING') { - this.setPlaybackRate(this.initialPlaybackRate) - this.startPlayInterval() - } else { - this.stopPlayInterval() - } + if (this.playerState === 'PLAYING') { + this.setPlaybackRate(this.initialPlaybackRate) + this.startPlayInterval() + } else { + this.stopPlayInterval() } if (this.player) { @@ -260,14 +258,14 @@ export default class PlayerHandler { startPlayInterval() { clearInterval(this.playInterval) - var lastTick = Date.now() + let lastTick = Date.now() this.playInterval = setInterval(() => { // Update UI if (!this.player) return - var currentTime = this.player.getCurrentTime() + const currentTime = this.player.getCurrentTime() this.ctx.setCurrentTime(currentTime) - var exactTimeElapsed = ((Date.now() - lastTick) / 1000) + const exactTimeElapsed = ((Date.now() - lastTick) / 1000) lastTick = Date.now() this.listeningTimeSinceSync += exactTimeElapsed if (this.listeningTimeSinceSync >= 5) { @@ -277,9 +275,9 @@ export default class PlayerHandler { } sendCloseSession() { - var syncData = null + let syncData = null if (this.player) { - var listeningTimeToAdd = Math.max(0, Math.floor(this.listeningTimeSinceSync)) + const listeningTimeToAdd = Math.max(0, Math.floor(this.listeningTimeSinceSync)) syncData = { timeListened: listeningTimeToAdd, duration: this.getDuration(), @@ -293,12 +291,14 @@ export default class PlayerHandler { } sendProgressSync(currentTime) { - var diffSinceLastSync = Math.abs(this.lastSyncTime - currentTime) + if (this.isMusic) return + + const diffSinceLastSync = Math.abs(this.lastSyncTime - currentTime) if (diffSinceLastSync < 1) return this.lastSyncTime = currentTime - var listeningTimeToAdd = Math.max(0, Math.floor(this.listeningTimeSinceSync)) - var syncData = { + const listeningTimeToAdd = Math.max(0, Math.floor(this.listeningTimeSinceSync)) + const syncData = { timeListened: listeningTimeToAdd, duration: this.getDuration(), currentTime diff --git a/server/objects/mediaTypes/Book.js b/server/objects/mediaTypes/Book.js index 317fa341..bcad931d 100644 --- a/server/objects/mediaTypes/Book.js +++ b/server/objects/mediaTypes/Book.js @@ -127,7 +127,7 @@ class Book { }) } get duration() { - var total = 0 + let total = 0 this.tracks.forEach((track) => total += track.duration) return total } diff --git a/server/objects/mediaTypes/Music.js b/server/objects/mediaTypes/Music.js index c685f567..9d47f6c0 100644 --- a/server/objects/mediaTypes/Music.js +++ b/server/objects/mediaTypes/Music.js @@ -41,6 +41,7 @@ class Music { coverPath: this.coverPath, tags: [...this.tags], audioFile: this.audioFile.toJSON(), + duration: this.duration, size: this.size } } @@ -52,6 +53,7 @@ class Music { coverPath: this.coverPath, tags: [...this.tags], audioFile: this.audioFile.toJSON(), + duration: this.duration, size: this.size } }
{{ $getString('MessageXLibraryIsEmpty', [libraryName]) }}
{{ emptyMessage }}
{{ podcastAuthor }}
{{ musicArtists }}
{{ author.name }},
{{ title }}
{{ artist || ' ' }}