mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Adding files tables, fixing loading when switching streams
This commit is contained in:
		
							parent
							
								
									6e8fe32bf5
								
							
						
					
					
						commit
						7e482352b1
					
				| @ -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)) | ||||
|  | ||||
| @ -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)), | ||||
|  | ||||
| @ -11,12 +11,14 @@ You can directly use them in pages and other components without the need to impo | ||||
| - `<AppBookShelf>` | `<app-book-shelf>` (components/app/BookShelf.vue) | ||||
| - `<AppBookShelfToolbar>` | `<app-book-shelf-toolbar>` (components/app/BookShelfToolbar.vue) | ||||
| - `<AppStreamContainer>` | `<app-stream-container>` (components/app/StreamContainer.vue) | ||||
| - `<AppTracksTable>` | `<app-tracks-table>` (components/app/TracksTable.vue) | ||||
| - `<CardsBookCard>` | `<cards-book-card>` (components/cards/BookCard.vue) | ||||
| - `<CardsBookCover>` | `<cards-book-cover>` (components/cards/BookCover.vue) | ||||
| - `<ControlsVolumeControl>` | `<controls-volume-control>` (components/controls/VolumeControl.vue) | ||||
| - `<ModalsEditModal>` | `<modals-edit-modal>` (components/modals/EditModal.vue) | ||||
| - `<ModalsModal>` | `<modals-modal>` (components/modals/Modal.vue) | ||||
| - `<TablesAudioFilesTable>` | `<tables-audio-files-table>` (components/tables/AudioFilesTable.vue) | ||||
| - `<TablesOtherFilesTable>` | `<tables-other-files-table>` (components/tables/OtherFilesTable.vue) | ||||
| - `<TablesTracksTable>` | `<tables-tracks-table>` (components/tables/TracksTable.vue) | ||||
| - `<UiBtn>` | `<ui-btn>` (components/ui/Btn.vue) | ||||
| - `<UiLoadingIndicator>` | `<ui-loading-indicator>` (components/ui/LoadingIndicator.vue) | ||||
| - `<UiMenu>` | `<ui-menu>` (components/ui/Menu.vue) | ||||
|  | ||||
| @ -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" | ||||
|   }, | ||||
|  | ||||
| @ -42,11 +42,12 @@ | ||||
|     </div> | ||||
|     <div class="relative"> | ||||
|       <!-- Track --> | ||||
|       <div ref="track" class="w-full h-2 bg-gray-700 relative cursor-pointer transform duration-100 hover:scale-y-125" :class="loading ? 'animate-pulse' : ''" @mousemove="mousemoveTrack" @mouseleave="mouseleaveTrack" @click.stop="clickTrack"> | ||||
|         <div ref="readyTrack" class="h-full bg-gray-600 absolute top-0 left-0 pointer-events-none" /> | ||||
|       <div ref="track" class="w-full h-2 bg-gray-700 relative cursor-pointer transform duration-100 hover:scale-y-125 overflow-hidden" @mousemove="mousemoveTrack" @mouseleave="mouseleaveTrack" @click.stop="clickTrack"> | ||||
|         <div ref="readyTrack" class="h-full bg-gray-500 absolute top-0 left-0 pointer-events-none" /> | ||||
|         <div ref="bufferTrack" class="h-full bg-gray-400 absolute top-0 left-0 pointer-events-none" /> | ||||
|         <div ref="playedTrack" class="h-full bg-gray-200 absolute top-0 left-0 pointer-events-none" /> | ||||
|         <div ref="trackCursor" class="h-full w-0.5 bg-gray-50 absolute top-0 left-0 opacity-0 pointer-events-none" /> | ||||
|         <div v-if="loading" class="h-full w-1/4 absolute left-0 top-0 loadingTrack pointer-events-none bg-white bg-opacity-25" /> | ||||
|       </div> | ||||
| 
 | ||||
|       <!-- Hover timestamp --> | ||||
| @ -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() | ||||
|   } | ||||
| } | ||||
| </script> | ||||
| @ -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%; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
| @ -1,6 +1,6 @@ | ||||
| <template> | ||||
|   <div class="w-full h-16 bg-primary relative"> | ||||
|     <div id="appbar" class="absolute top-0 bottom-0 left-0 w-full h-full px-6 py-1 z-10"> | ||||
|     <div id="appbar" class="absolute top-0 bottom-0 left-0 w-full h-full px-6 py-1 z-20"> | ||||
|       <div class="flex h-full items-center"> | ||||
|         <img v-if="!showBack" src="/LogoTransparent.png" class="w-12 h-12 mr-4" /> | ||||
|         <a v-if="showBack" @click="back" class="rounded-full h-12 w-12 flex items-center justify-center hover:bg-white hover:bg-opacity-10 mr-4 cursor-pointer"> | ||||
|  | ||||
| @ -14,7 +14,7 @@ | ||||
|       <span v-if="stream" class="material-icons px-4 cursor-pointer" @click="cancelStream">close</span> | ||||
|     </div> | ||||
| 
 | ||||
|     <audio-player ref="audioPlayer" :loading="!stream" @updateTime="updateTime" @hook:mounted="audioPlayerMounted" /> | ||||
|     <audio-player ref="audioPlayer" :loading="isLoading" @updateTime="updateTime" @hook:mounted="audioPlayerMounted" /> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| @ -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() | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
							
								
								
									
										67
									
								
								client/components/tables/AudioFilesTable.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								client/components/tables/AudioFilesTable.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,67 @@ | ||||
| <template> | ||||
|   <div class="w-full my-2"> | ||||
|     <div class="w-full bg-primary px-6 py-2 flex items-center cursor-pointer" @click.stop="clickBar"> | ||||
|       <p class="pr-4">Other Audio Files</p> | ||||
|       <span class="bg-black-400 rounded-xl py-1 px-2 text-sm font-mono">{{ files.length }}</span> | ||||
|       <div class="flex-grow" /> | ||||
|       <nuxt-link :to="`/audiobook/${audiobookId}/edit`" class="mr-4"> | ||||
|         <ui-btn small color="primary">Manage Tracks</ui-btn> | ||||
|       </nuxt-link> | ||||
|       <div class="cursor-pointer h-10 w-10 rounded-full hover:bg-black-400 flex justify-center items-center duration-500" :class="showTracks ? 'transform rotate-180' : ''"> | ||||
|         <span class="material-icons text-4xl">expand_more</span> | ||||
|       </div> | ||||
|     </div> | ||||
|     <transition name="slide"> | ||||
|       <div class="w-full" v-show="showTracks"> | ||||
|         <table class="text-sm tracksTable"> | ||||
|           <tr class="font-book"> | ||||
|             <th class="text-left">Filename</th> | ||||
|             <th class="text-left">Size</th> | ||||
|             <th class="text-left">Duration</th> | ||||
|             <th class="text-left">Notes</th> | ||||
|           </tr> | ||||
|           <template v-for="track in files"> | ||||
|             <tr :key="track.path"> | ||||
|               <td class="font-book pl-2"> | ||||
|                 {{ track.filename }} | ||||
|               </td> | ||||
|               <td class="font-mono"> | ||||
|                 {{ $bytesPretty(track.size) }} | ||||
|               </td> | ||||
|               <td class="font-mono"> | ||||
|                 {{ $secondsToTimestamp(track.duration) }} | ||||
|               </td> | ||||
|               <td class="text-xs"> | ||||
|                 <p>{{ track.error || '' }}</p> | ||||
|               </td> | ||||
|             </tr> | ||||
|           </template> | ||||
|         </table> | ||||
|       </div> | ||||
|     </transition> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| export default { | ||||
|   props: { | ||||
|     files: { | ||||
|       type: Array, | ||||
|       default: () => [] | ||||
|     }, | ||||
|     audiobookId: String | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       showTracks: false | ||||
|     } | ||||
|   }, | ||||
|   computed: {}, | ||||
|   methods: { | ||||
|     clickBar() { | ||||
|       this.showTracks = !this.showTracks | ||||
|     } | ||||
|   }, | ||||
|   mounted() {} | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										59
									
								
								client/components/tables/OtherFilesTable.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								client/components/tables/OtherFilesTable.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | ||||
| <template> | ||||
|   <div class="w-full my-2"> | ||||
|     <div class="w-full bg-primary px-6 py-2 flex items-center cursor-pointer" @click.stop="clickBar"> | ||||
|       <p class="pr-4">Other Files</p> | ||||
|       <span class="bg-black-400 rounded-xl py-1 px-2 text-sm font-mono">{{ files.length }}</span> | ||||
|       <div class="flex-grow" /> | ||||
|       <!-- <nuxt-link :to="`/audiobook/${audiobookId}/edit`" class="mr-4"> | ||||
|         <ui-btn small color="primary">Manage Tracks</ui-btn> | ||||
|       </nuxt-link> --> | ||||
|       <div class="cursor-pointer h-10 w-10 rounded-full hover:bg-black-400 flex justify-center items-center duration-500" :class="showFiles ? 'transform rotate-180' : ''"> | ||||
|         <span class="material-icons text-4xl">expand_more</span> | ||||
|       </div> | ||||
|     </div> | ||||
|     <transition name="slide"> | ||||
|       <div class="w-full" v-show="showFiles"> | ||||
|         <table class="text-sm tracksTable"> | ||||
|           <tr class="font-book"> | ||||
|             <th class="text-left">Path</th> | ||||
|             <th class="text-left">Filetype</th> | ||||
|           </tr> | ||||
|           <template v-for="file in files"> | ||||
|             <tr :key="file.path"> | ||||
|               <td class="font-book pl-2"> | ||||
|                 {{ file.path }} | ||||
|               </td> | ||||
|               <td class="text-xs"> | ||||
|                 <p>{{ file.filetype }}</p> | ||||
|               </td> | ||||
|             </tr> | ||||
|           </template> | ||||
|         </table> | ||||
|       </div> | ||||
|     </transition> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| export default { | ||||
|   props: { | ||||
|     files: { | ||||
|       type: Array, | ||||
|       default: () => [] | ||||
|     }, | ||||
|     audiobookId: String | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       showFiles: false | ||||
|     } | ||||
|   }, | ||||
|   computed: {}, | ||||
|   methods: { | ||||
|     clickBar() { | ||||
|       this.showFiles = !this.showFiles | ||||
|     } | ||||
|   }, | ||||
|   mounted() {} | ||||
| } | ||||
| </script> | ||||
| @ -5,7 +5,7 @@ | ||||
|       <span class="bg-black-400 rounded-xl py-1 px-2 text-sm font-mono">{{ tracks.length }}</span> | ||||
|       <div class="flex-grow" /> | ||||
|       <nuxt-link :to="`/audiobook/${audiobookId}/edit`" class="mr-4"> | ||||
|         <ui-btn small color="primary">Edit Track Order</ui-btn> | ||||
|         <ui-btn small color="primary">Manage Tracks</ui-btn> | ||||
|       </nuxt-link> | ||||
|       <div class="cursor-pointer h-10 w-10 rounded-full hover:bg-black-400 flex justify-center items-center duration-500" :class="showTracks ? 'transform rotate-180' : ''"> | ||||
|         <span class="material-icons text-4xl">expand_more</span> | ||||
| @ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "audiobookshelf-client", | ||||
|   "version": "0.9.3", | ||||
|   "version": "0.9.4", | ||||
|   "description": "Audiobook manager and player", | ||||
|   "main": "index.js", | ||||
|   "scripts": { | ||||
|  | ||||
| @ -47,7 +47,11 @@ | ||||
|             <p class="text-sm font-mono">{{ invalidParts.join(', ') }}</p> | ||||
|           </div> | ||||
| 
 | ||||
|           <app-tracks-table :tracks="tracks" :audiobook-id="audiobook.id" class="mt-6" /> | ||||
|           <tables-tracks-table :tracks="tracks" :audiobook-id="audiobook.id" class="mt-6" /> | ||||
| 
 | ||||
|           <tables-audio-files-table v-if="otherAudioFiles.length" :audiobook-id="audiobook.id" :files="otherAudioFiles" class="mt-6" /> | ||||
| 
 | ||||
|           <tables-other-files-table v-if="otherFiles.length" :audiobook-id="audiobook.id" :files="otherFiles" class="mt-6" /> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
| @ -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' | ||||
|     }, | ||||
|  | ||||
| @ -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 | ||||
|   } | ||||
| } | ||||
| @ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "audiobookshelf", | ||||
|   "version": "0.9.3", | ||||
|   "version": "0.9.4", | ||||
|   "description": "", | ||||
|   "main": "index.js", | ||||
|   "scripts": { | ||||
|  | ||||
| @ -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) | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -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 | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -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) { | ||||
|  | ||||
| @ -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 | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user