diff --git a/client/.nuxt/components/index.js b/client/.nuxt/components/index.js index 8d1a1122..51e7786b 100644 --- a/client/.nuxt/components/index.js +++ b/client/.nuxt/components/index.js @@ -5,12 +5,14 @@ export { default as AppAppbar } from '../..\\components\\app\\Appbar.vue' export { default as AppBookShelf } from '../..\\components\\app\\BookShelf.vue' export { default as AppBookShelfToolbar } from '../..\\components\\app\\BookShelfToolbar.vue' export { default as AppStreamContainer } from '../..\\components\\app\\StreamContainer.vue' -export { default as AppTracksTable } from '../..\\components\\app\\TracksTable.vue' export { default as CardsBookCard } from '../..\\components\\cards\\BookCard.vue' export { default as CardsBookCover } from '../..\\components\\cards\\BookCover.vue' export { default as ControlsVolumeControl } from '../..\\components\\controls\\VolumeControl.vue' export { default as ModalsEditModal } from '../..\\components\\modals\\EditModal.vue' export { default as ModalsModal } from '../..\\components\\modals\\Modal.vue' +export { default as TablesAudioFilesTable } from '../..\\components\\tables\\AudioFilesTable.vue' +export { default as TablesOtherFilesTable } from '../..\\components\\tables\\OtherFilesTable.vue' +export { default as TablesTracksTable } from '../..\\components\\tables\\TracksTable.vue' export { default as UiBtn } from '../..\\components\\ui\\Btn.vue' export { default as UiLoadingIndicator } from '../..\\components\\ui\\LoadingIndicator.vue' export { default as UiMenu } from '../..\\components\\ui\\Menu.vue' @@ -30,12 +32,14 @@ export const LazyAppAppbar = import('../..\\components\\app\\Appbar.vue' /* webp export const LazyAppBookShelf = import('../..\\components\\app\\BookShelf.vue' /* webpackChunkName: "components/app-book-shelf" */).then(c => wrapFunctional(c.default || c)) export const LazyAppBookShelfToolbar = import('../..\\components\\app\\BookShelfToolbar.vue' /* webpackChunkName: "components/app-book-shelf-toolbar" */).then(c => wrapFunctional(c.default || c)) export const LazyAppStreamContainer = import('../..\\components\\app\\StreamContainer.vue' /* webpackChunkName: "components/app-stream-container" */).then(c => wrapFunctional(c.default || c)) -export const LazyAppTracksTable = import('../..\\components\\app\\TracksTable.vue' /* webpackChunkName: "components/app-tracks-table" */).then(c => wrapFunctional(c.default || c)) export const LazyCardsBookCard = import('../..\\components\\cards\\BookCard.vue' /* webpackChunkName: "components/cards-book-card" */).then(c => wrapFunctional(c.default || c)) export const LazyCardsBookCover = import('../..\\components\\cards\\BookCover.vue' /* webpackChunkName: "components/cards-book-cover" */).then(c => wrapFunctional(c.default || c)) export const LazyControlsVolumeControl = import('../..\\components\\controls\\VolumeControl.vue' /* webpackChunkName: "components/controls-volume-control" */).then(c => wrapFunctional(c.default || c)) export const LazyModalsEditModal = import('../..\\components\\modals\\EditModal.vue' /* webpackChunkName: "components/modals-edit-modal" */).then(c => wrapFunctional(c.default || c)) export const LazyModalsModal = import('../..\\components\\modals\\Modal.vue' /* webpackChunkName: "components/modals-modal" */).then(c => wrapFunctional(c.default || c)) +export const LazyTablesAudioFilesTable = import('../..\\components\\tables\\AudioFilesTable.vue' /* webpackChunkName: "components/tables-audio-files-table" */).then(c => wrapFunctional(c.default || c)) +export const LazyTablesOtherFilesTable = import('../..\\components\\tables\\OtherFilesTable.vue' /* webpackChunkName: "components/tables-other-files-table" */).then(c => wrapFunctional(c.default || c)) +export const LazyTablesTracksTable = import('../..\\components\\tables\\TracksTable.vue' /* webpackChunkName: "components/tables-tracks-table" */).then(c => wrapFunctional(c.default || c)) export const LazyUiBtn = import('../..\\components\\ui\\Btn.vue' /* webpackChunkName: "components/ui-btn" */).then(c => wrapFunctional(c.default || c)) export const LazyUiLoadingIndicator = import('../..\\components\\ui\\LoadingIndicator.vue' /* webpackChunkName: "components/ui-loading-indicator" */).then(c => wrapFunctional(c.default || c)) export const LazyUiMenu = import('../..\\components\\ui\\Menu.vue' /* webpackChunkName: "components/ui-menu" */).then(c => wrapFunctional(c.default || c)) diff --git a/client/.nuxt/components/plugin.js b/client/.nuxt/components/plugin.js index 53a12e7e..f9823b55 100644 --- a/client/.nuxt/components/plugin.js +++ b/client/.nuxt/components/plugin.js @@ -7,12 +7,14 @@ const components = { AppBookShelf: () => import('../..\\components\\app\\BookShelf.vue' /* webpackChunkName: "components/app-book-shelf" */).then(c => wrapFunctional(c.default || c)), AppBookShelfToolbar: () => import('../..\\components\\app\\BookShelfToolbar.vue' /* webpackChunkName: "components/app-book-shelf-toolbar" */).then(c => wrapFunctional(c.default || c)), AppStreamContainer: () => import('../..\\components\\app\\StreamContainer.vue' /* webpackChunkName: "components/app-stream-container" */).then(c => wrapFunctional(c.default || c)), - AppTracksTable: () => import('../..\\components\\app\\TracksTable.vue' /* webpackChunkName: "components/app-tracks-table" */).then(c => wrapFunctional(c.default || c)), CardsBookCard: () => import('../..\\components\\cards\\BookCard.vue' /* webpackChunkName: "components/cards-book-card" */).then(c => wrapFunctional(c.default || c)), CardsBookCover: () => import('../..\\components\\cards\\BookCover.vue' /* webpackChunkName: "components/cards-book-cover" */).then(c => wrapFunctional(c.default || c)), ControlsVolumeControl: () => import('../..\\components\\controls\\VolumeControl.vue' /* webpackChunkName: "components/controls-volume-control" */).then(c => wrapFunctional(c.default || c)), ModalsEditModal: () => import('../..\\components\\modals\\EditModal.vue' /* webpackChunkName: "components/modals-edit-modal" */).then(c => wrapFunctional(c.default || c)), ModalsModal: () => import('../..\\components\\modals\\Modal.vue' /* webpackChunkName: "components/modals-modal" */).then(c => wrapFunctional(c.default || c)), + TablesAudioFilesTable: () => import('../..\\components\\tables\\AudioFilesTable.vue' /* webpackChunkName: "components/tables-audio-files-table" */).then(c => wrapFunctional(c.default || c)), + TablesOtherFilesTable: () => import('../..\\components\\tables\\OtherFilesTable.vue' /* webpackChunkName: "components/tables-other-files-table" */).then(c => wrapFunctional(c.default || c)), + TablesTracksTable: () => import('../..\\components\\tables\\TracksTable.vue' /* webpackChunkName: "components/tables-tracks-table" */).then(c => wrapFunctional(c.default || c)), UiBtn: () => import('../..\\components\\ui\\Btn.vue' /* webpackChunkName: "components/ui-btn" */).then(c => wrapFunctional(c.default || c)), UiLoadingIndicator: () => import('../..\\components\\ui\\LoadingIndicator.vue' /* webpackChunkName: "components/ui-loading-indicator" */).then(c => wrapFunctional(c.default || c)), UiMenu: () => import('../..\\components\\ui\\Menu.vue' /* webpackChunkName: "components/ui-menu" */).then(c => wrapFunctional(c.default || c)), diff --git a/client/.nuxt/components/readme.md b/client/.nuxt/components/readme.md index 2c3c4000..7095248b 100644 --- a/client/.nuxt/components/readme.md +++ b/client/.nuxt/components/readme.md @@ -11,12 +11,14 @@ You can directly use them in pages and other components without the need to impo - `` | `` (components/app/BookShelf.vue) - `` | `` (components/app/BookShelfToolbar.vue) - `` | `` (components/app/StreamContainer.vue) -- `` | `` (components/app/TracksTable.vue) - `` | `` (components/cards/BookCard.vue) - `` | `` (components/cards/BookCover.vue) - `` | `` (components/controls/VolumeControl.vue) - `` | `` (components/modals/EditModal.vue) - `` | `` (components/modals/Modal.vue) +- `` | `` (components/tables/AudioFilesTable.vue) +- `` | `` (components/tables/OtherFilesTable.vue) +- `` | `` (components/tables/TracksTable.vue) - `` | `` (components/ui/Btn.vue) - `` | `` (components/ui/LoadingIndicator.vue) - `` | `` (components/ui/Menu.vue) diff --git a/client/.nuxt/vetur/tags.json b/client/.nuxt/vetur/tags.json index dfa2cd6f..37a9c618 100644 --- a/client/.nuxt/vetur/tags.json +++ b/client/.nuxt/vetur/tags.json @@ -14,9 +14,6 @@ "AppStreamContainer": { "description": "Auto imported from components/app/StreamContainer.vue" }, - "AppTracksTable": { - "description": "Auto imported from components/app/TracksTable.vue" - }, "CardsBookCard": { "description": "Auto imported from components/cards/BookCard.vue" }, @@ -32,6 +29,15 @@ "ModalsModal": { "description": "Auto imported from components/modals/Modal.vue" }, + "TablesAudioFilesTable": { + "description": "Auto imported from components/tables/AudioFilesTable.vue" + }, + "TablesOtherFilesTable": { + "description": "Auto imported from components/tables/OtherFilesTable.vue" + }, + "TablesTracksTable": { + "description": "Auto imported from components/tables/TracksTable.vue" + }, "UiBtn": { "description": "Auto imported from components/ui/Btn.vue" }, diff --git a/client/components/AudioPlayer.vue b/client/components/AudioPlayer.vue index 90868090..7076abdb 100644 --- a/client/components/AudioPlayer.vue +++ b/client/components/AudioPlayer.vue @@ -42,11 +42,12 @@
-
-
+
+
+
@@ -97,6 +98,9 @@ export default { }, methods: { seek(time) { + if (this.loading) { + return + } if (this.seekLoading) { console.error('Already seek loading', this.seekedTime) return @@ -169,6 +173,7 @@ export default { setStreamReady() { this.readyTrackWidth = this.trackWidth this.$refs.readyTrack.style.width = this.trackWidth + 'px' + console.warn('SET STREAM READY', this.readyTrackWidth) }, setChunksReady(chunks, numSegments) { var largestSeg = 0 @@ -307,7 +312,6 @@ export default { console.error('No audio on paused()') return } - console.log('Paused') this.isPaused = this.$refs.audio.paused }, playing() { @@ -321,7 +325,6 @@ export default { this.totalDuration = this.audioEl.duration }, set(url, currentTime, playOnLoad = false) { - console.log('[AudioPlayer] SET PlayOnLoad ', playOnLoad) if (this.hlsInstance) { this.terminateStream() } @@ -342,13 +345,13 @@ export default { xhr.setRequestHeader('Authorization', `Bearer ${this.token}`) } } - console.log('[AudioPlayer-Set] HLS Config', hlsOptions, this.$secondsToTimestamp(hlsOptions.startPosition)) + console.log('[AudioPlayer-Set] HLS Config', hlsOptions) this.hlsInstance = new Hls(hlsOptions) var audio = this.$refs.audio audio.volume = this.volume this.hlsInstance.attachMedia(audio) this.hlsInstance.on(Hls.Events.MEDIA_ATTACHED, () => { - console.log('[HLS] MEDIA ATTACHED') + // console.log('[HLS] MEDIA ATTACHED') this.hlsInstance.loadSource(url) this.hlsInstance.on(Hls.Events.MANIFEST_PARSED, function () { @@ -366,10 +369,7 @@ export default { }) this.hlsInstance.on(Hls.Events.FRAG_LOADED, (e, data) => { var frag = data.frag - console.log('[HLS] Frag Loaded', frag.sn, this.$secondsToTimestamp(frag.start), frag) - }) - this.hlsInstance.on(Hls.Events.STREAM_STATE_TRANSITION, (e, data) => { - console.log('[HLS] Stream State Transition', data) + // console.log('[HLS] Frag Loaded', frag.sn, this.$secondsToTimestamp(frag.start), frag) }) this.hlsInstance.on(Hls.Events.BUFFER_APPENDED, (e, data) => { // console.log('[HLS] BUFFER', data) @@ -419,7 +419,8 @@ export default { } }, mounted() { - this.$nextTick(this.init) + // this.$nextTick(this.init) + this.init() } } @@ -432,4 +433,17 @@ export default { border-right: 6px solid transparent; border-top: 6px solid white; } +.loadingTrack { + animation-name: loadingTrack; + animation-duration: 1s; + animation-iteration-count: infinite; +} +@keyframes loadingTrack { + 0% { + left: -25%; + } + 100% { + left: 100%; + } +} \ No newline at end of file diff --git a/client/components/app/Appbar.vue b/client/components/app/Appbar.vue index 0398d3fb..b81b8e11 100644 --- a/client/components/app/Appbar.vue +++ b/client/components/app/Appbar.vue @@ -1,6 +1,6 @@ @@ -34,6 +34,14 @@ export default { user() { return this.$store.state.user }, + isLoading() { + if (!this.streamAudiobook) return false + if (this.stream) { + // IF Stream exists, set loading if stream is diff from next stream + return this.stream.audiobook.id !== this.streamAudiobook.id + } + return true + }, streamAudiobook() { return this.$store.state.streamAudiobook }, @@ -56,7 +64,7 @@ export default { methods: { audioPlayerMounted() { if (this.stream) { - // this.$refs.audioPlayer.set(this.playlistUrl) + console.log('[STREAM-CONTAINER] audioPlayerMounted w/ Stream', this.stream) this.openStream() } }, @@ -77,6 +85,9 @@ export default { } var currentTime = this.stream.clientCurrentTime || 0 this.$refs.audioPlayer.set(this.playlistUrl, currentTime, playOnLoad) + if (this.stream.isTranscodeComplete) { + this.$refs.audioPlayer.setStreamReady() + } }, streamProgress(data) { if (!data.numSegments) return @@ -87,14 +98,16 @@ export default { }, streamOpen(stream) { this.stream = stream - this.$nextTick(() => { + if (this.$refs.audioPlayer) { + console.log('[STREAM-CONTAINER] streamOpen', stream) this.openStream() - }) + } }, streamClosed(streamId) { if (this.stream && this.stream.id === streamId) { this.terminateStream() this.$store.commit('clearStreamAudiobook', this.stream.audiobook.id) + this.stream = null } }, streamReady() { @@ -123,14 +136,6 @@ export default { this.$refs.audioPlayer.resetStream(startTime) } } - }, - mounted() { - if (this.stream) { - console.log('[STREAM_CONTAINER] Mounted with STREAM', this.stream) - this.$nextTick(() => { - this.openStream() - }) - } } } diff --git a/client/components/tables/AudioFilesTable.vue b/client/components/tables/AudioFilesTable.vue new file mode 100644 index 00000000..8472957d --- /dev/null +++ b/client/components/tables/AudioFilesTable.vue @@ -0,0 +1,67 @@ + + + \ No newline at end of file diff --git a/client/components/tables/OtherFilesTable.vue b/client/components/tables/OtherFilesTable.vue new file mode 100644 index 00000000..1dbdf520 --- /dev/null +++ b/client/components/tables/OtherFilesTable.vue @@ -0,0 +1,59 @@ + + + \ No newline at end of file diff --git a/client/components/app/TracksTable.vue b/client/components/tables/TracksTable.vue similarity index 96% rename from client/components/app/TracksTable.vue rename to client/components/tables/TracksTable.vue index e405b250..d603c146 100644 --- a/client/components/app/TracksTable.vue +++ b/client/components/tables/TracksTable.vue @@ -5,7 +5,7 @@ {{ tracks.length }}
- Edit Track Order + Manage Tracks
expand_more diff --git a/client/package.json b/client/package.json index 1e6ef38a..bfc19e1b 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "audiobookshelf-client", - "version": "0.9.3", + "version": "0.9.4", "description": "Audiobook manager and player", "main": "index.js", "scripts": { diff --git a/client/pages/audiobook/_id/index.vue b/client/pages/audiobook/_id/index.vue index 16337647..d0d16a5c 100644 --- a/client/pages/audiobook/_id/index.vue +++ b/client/pages/audiobook/_id/index.vue @@ -47,7 +47,11 @@

{{ invalidParts.join(', ') }}

- + + + + +
@@ -136,9 +140,20 @@ export default { book() { return this.audiobook.book || {} }, + otherFiles() { + return this.audiobook.otherFiles || [] + }, + otherAudioFiles() { + return this.audioFiles.filter((af) => { + return !this.tracks.find((t) => t.path === af.path) + }) + }, tracks() { return this.audiobook.tracks || [] }, + audioFiles() { + return this.audiobook.audioFiles || [] + }, description() { return this.book.description || 'No Description' }, diff --git a/client/store/index.js b/client/store/index.js index 755bd65a..650301a6 100644 --- a/client/store/index.js +++ b/client/store/index.js @@ -25,7 +25,6 @@ export const actions = { export const mutations = { setUser(state, user) { state.user = user - console.log('SETUSER', user) if (user.token) { localStorage.setItem('token', user.token) } @@ -58,6 +57,7 @@ export const mutations = { state.isScanning = isScanning }, setScanProgress(state, progress) { + if (progress > 0) state.isScanning = true state.scanProgress = progress } } \ No newline at end of file diff --git a/package.json b/package.json index ea083a13..d93181da 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "audiobookshelf", - "version": "0.9.3", + "version": "0.9.4", "description": "", "main": "index.js", "scripts": { diff --git a/server/Audiobook.js b/server/Audiobook.js index 895df533..7682b22f 100644 --- a/server/Audiobook.js +++ b/server/Audiobook.js @@ -14,7 +14,6 @@ class Audiobook { this.invalidParts = [] this.audioFiles = [] - this.ebookFiles = [] this.otherFiles = [] this.tags = [] @@ -38,7 +37,6 @@ class Audiobook { this.invalidParts = audiobook.invalidParts this.audioFiles = audiobook.audioFiles - this.ebookFiles = audiobook.ebookFiles this.otherFiles = audiobook.otherFiles this.tags = audiobook.tags @@ -103,7 +101,6 @@ class Audiobook { book: this.bookToJSON(), tracks: this.tracksToJSON(), audioFiles: this.audioFiles, - ebookFiles: this.ebookFiles, otherFiles: this.otherFiles } } @@ -141,7 +138,6 @@ class Audiobook { missingParts: this.missingParts, invalidParts: this.invalidParts, audioFiles: this.audioFiles, - ebookFiles: this.ebookFiles, otherFiles: this.otherFiles, tags: this.tags, book: this.bookToJSON(), @@ -156,7 +152,6 @@ class Audiobook { this.addedAt = Date.now() this.otherFiles = data.otherFiles || [] - this.ebookFiles = data.ebooks || [] this.setBook(data) } diff --git a/server/Stream.js b/server/Stream.js index 0be7b1d1..fcf75a77 100644 --- a/server/Stream.js +++ b/server/Stream.js @@ -83,7 +83,8 @@ class Stream extends EventEmitter { clientPlaylistUri: this.clientPlaylistUri, clientCurrentTime: this.clientCurrentTime, startTime: this.startTime, - segmentStartNumber: this.segmentStartNumber + segmentStartNumber: this.segmentStartNumber, + isTranscodeComplete: this.isTranscodeComplete } } diff --git a/server/utils/audioFileScanner.js b/server/utils/audioFileScanner.js index 092518af..06958117 100644 --- a/server/utils/audioFileScanner.js +++ b/server/utils/audioFileScanner.js @@ -92,17 +92,14 @@ async function scanParts(audiobook, parts) { continue; } - var audioFileObj = { - path: parts[i], - filename: Path.basename(parts[i]), - fullPath: fullPath - } - var trackNumFromMeta = getTrackNumberFromMeta(scanData) var trackNumFromFilename = getTrackNumberFromFilename(parts[i]) - audioFileObj = { - ...audioFileObj, + var audioFileObj = { + path: Path.join(audiobook.path, parts[i]), + ext: Path.extname(parts[i]), + filename: parts[i], + fullPath: fullPath, ...scanData, trackNumFromMeta, trackNumFromFilename @@ -129,15 +126,8 @@ async function scanParts(audiobook, parts) { continue; } - var track = { - index: trackNumber, - filename: parts[i], - ext: Path.extname(parts[i]), - path: Path.join(audiobook.path, parts[i]), - fullPath: Path.join(audiobook.fullPath, parts[i]), - ...scanData - } - tracks.push(track) + audioFileObj.index = trackNumber + tracks.push(audioFileObj) } if (!tracks.length) { diff --git a/server/utils/scandir.js b/server/utils/scandir.js index e6e71842..74bfbaa2 100644 --- a/server/utils/scandir.js +++ b/server/utils/scandir.js @@ -26,47 +26,44 @@ function getFileType(ext) { if (INFO_FORMATS.includes(ext_cleaned)) return 'info' if (IMAGE_FORMATS.includes(ext_cleaned)) return 'image' if (EBOOK_FORMATS.includes(ext_cleaned)) return 'ebook' - return null + return 'unknown' } async function getAllAudiobookFiles(path) { console.log('getAllAudiobooks', path) var paths = await getPaths(path) - var books = {} + var audiobooks = {} paths.files.forEach((filepath) => { var relpath = filepath.replace(path, '').slice(1) var pathformat = Path.parse(relpath) var authordir = Path.dirname(pathformat.dir) var bookdir = Path.basename(pathformat.dir) - if (!books[bookdir]) { - books[bookdir] = { + if (!audiobooks[bookdir]) { + audiobooks[bookdir] = { author: authordir, title: bookdir, path: pathformat.dir, fullPath: Path.join(path, pathformat.dir), parts: [], - infos: [], - images: [], - ebooks: [], otherFiles: [] } } var filetype = getFileType(pathformat.ext) if (filetype === 'abpart') { - books[bookdir].parts.push(`${pathformat.name}${pathformat.ext}`) - } else if (filetype === 'info') { - books[bookdir].infos.push(`${pathformat.name}${pathformat.ext}`) - } else if (filetype === 'image') { - books[bookdir].images.push(`${pathformat.name}${pathformat.ext}`) - } else if (filetype === 'ebook') { - books[bookdir].ebooks.push(`${pathformat.name}${pathformat.ext}`) + audiobooks[bookdir].parts.push(pathformat.base) } else { - Logger.warn('Invalid file type', pathformat.name, pathformat.ext) - books[bookdir].otherFiles.push(`${pathformat.name}${pathformat.ext}`) + var fileObj = { + filetype: filetype, + filename: pathformat.base, + path: relpath, + fullPath: filepath, + ext: pathformat.ext + } + audiobooks[bookdir].otherFiles.push(fileObj) } }) - return Object.values(books) + return Object.values(audiobooks) } module.exports.getAllAudiobookFiles = getAllAudiobookFiles \ No newline at end of file