mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Add:Player queue for audiobooks #1077
This commit is contained in:
		
							parent
							
								
									3357ccfaf3
								
							
						
					
					
						commit
						78559520ab
					
				| @ -477,6 +477,21 @@ export default { | |||||||
|           text: this.$strings.ButtonRemoveFromContinueListening |           text: this.$strings.ButtonRemoveFromContinueListening | ||||||
|         }) |         }) | ||||||
|       } |       } | ||||||
|  |       if (!this.isPodcast) { | ||||||
|  |         if (this.libraryItemIdStreaming && !this.isStreamingFromDifferentLibrary) { | ||||||
|  |           if (!this.isQueued) { | ||||||
|  |             items.push({ | ||||||
|  |               func: 'addToQueue', | ||||||
|  |               text: this.$strings.ButtonQueueAddItem | ||||||
|  |             }) | ||||||
|  |           } else if (!this.isStreaming) { | ||||||
|  |             items.push({ | ||||||
|  |               func: 'removeFromQueue', | ||||||
|  |               text: this.$strings.ButtonQueueRemoveItem | ||||||
|  |             }) | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|       return items |       return items | ||||||
|     }, |     }, | ||||||
|     _socket() { |     _socket() { | ||||||
| @ -690,8 +705,9 @@ export default { | |||||||
|         }) |         }) | ||||||
|     }, |     }, | ||||||
|     addToQueue() { |     addToQueue() { | ||||||
|  |       var queueItem = {} | ||||||
|       if (this.recentEpisode) { |       if (this.recentEpisode) { | ||||||
|         const queueItem = { |         queueItem = { | ||||||
|           libraryItemId: this.libraryItemId, |           libraryItemId: this.libraryItemId, | ||||||
|           libraryId: this.libraryId, |           libraryId: this.libraryId, | ||||||
|           episodeId: this.recentEpisode.id, |           episodeId: this.recentEpisode.id, | ||||||
| @ -701,8 +717,19 @@ export default { | |||||||
|           duration: this.recentEpisode.audioFile.duration || null, |           duration: this.recentEpisode.audioFile.duration || null, | ||||||
|           coverPath: this.media.coverPath || null |           coverPath: this.media.coverPath || null | ||||||
|         } |         } | ||||||
|         this.store.commit('addItemToQueue', queueItem) |       } else { | ||||||
|  |         queueItem = { | ||||||
|  |           libraryItemId: this.libraryItemId, | ||||||
|  |           libraryId: this.libraryId, | ||||||
|  |           episodeId: null, | ||||||
|  |           title: this.title, | ||||||
|  |           subtitle: this.author, | ||||||
|  |           caption: '', | ||||||
|  |           duration: this.media.duration || null, | ||||||
|  |           coverPath: this.media.coverPath || null | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|  |       this.store.commit('addItemToQueue', queueItem) | ||||||
|     }, |     }, | ||||||
|     removeFromQueue() { |     removeFromQueue() { | ||||||
|       const episodeId = this.recentEpisode ? this.recentEpisode.id : null |       const episodeId = this.recentEpisode ? this.recentEpisode.id : null | ||||||
| @ -815,6 +842,18 @@ export default { | |||||||
|             } |             } | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|  |       } else { | ||||||
|  |         const queueItem = { | ||||||
|  |           libraryItemId: this.libraryItemId, | ||||||
|  |           libraryId: this.libraryId, | ||||||
|  |           episodeId: null, | ||||||
|  |           title: this.title, | ||||||
|  |           subtitle: this.author, | ||||||
|  |           caption: '', | ||||||
|  |           duration: this.media.duration || null, | ||||||
|  |           coverPath: this.media.coverPath || null | ||||||
|  |         } | ||||||
|  |         queueItems.push(queueItem) | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       eventBus.$emit('play-item', { |       eventBus.$emit('play-item', { | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ | |||||||
|       <div v-show="showCoverBg" class="absolute top-0 left-0 w-full h-full overflow-hidden rounded-sm bg-primary"> |       <div v-show="showCoverBg" class="absolute top-0 left-0 w-full h-full overflow-hidden rounded-sm bg-primary"> | ||||||
|         <div class="absolute cover-bg" ref="coverBg" /> |         <div class="absolute cover-bg" ref="coverBg" /> | ||||||
|       </div> |       </div> | ||||||
|       <img ref="cover" :src="cover" @error="imageError" @load="imageLoaded" class="w-full h-full absolute top-0 left-0" :class="showCoverBg ? 'object-contain' : 'object-cover'" /> |       <img ref="cover" :src="cover" @error="imageError" @load="imageLoaded" class="w-full h-full absolute top-0 left-0" :class="showCoverBg ? 'object-contain' : 'object-fill'" /> | ||||||
| 
 | 
 | ||||||
|       <a v-if="!imageFailed && showOpenNewTab && isHovering" :href="cover" @click.stop target="_blank" class="absolute bg-primary flex items-center justify-center shadow-sm rounded-full hover:scale-110 transform duration-100" :style="{ top: sizeMultiplier * 0.5 + 'rem', right: sizeMultiplier * 0.5 + 'rem', width: 2.5 * sizeMultiplier + 'rem', height: 2.5 * sizeMultiplier + 'rem' }"> |       <a v-if="!imageFailed && showOpenNewTab && isHovering" :href="cover" @click.stop target="_blank" class="absolute bg-primary flex items-center justify-center shadow-sm rounded-full hover:scale-110 transform duration-100" :style="{ top: sizeMultiplier * 0.5 + 'rem', right: sizeMultiplier * 0.5 + 'rem', width: 2.5 * sizeMultiplier + 'rem', height: 2.5 * sizeMultiplier + 'rem' }"> | ||||||
|         <span class="material-icons" :style="{ fontSize: sizeMultiplier * 1.75 + 'rem' }">open_in_new</span> |         <span class="material-icons" :style="{ fontSize: sizeMultiplier * 1.75 + 'rem' }">open_in_new</span> | ||||||
| @ -63,6 +63,9 @@ export default { | |||||||
|     }, |     }, | ||||||
|     resolution() { |     resolution() { | ||||||
|       return `${this.naturalWidth}x${this.naturalHeight}px` |       return `${this.naturalWidth}x${this.naturalHeight}px` | ||||||
|  |     }, | ||||||
|  |     placeholderUrl() { | ||||||
|  |       return `${this.$config.routerBasePath}/book_placeholder.jpg` | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
| @ -72,7 +75,7 @@ export default { | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     imageLoaded() { |     imageLoaded() { | ||||||
|       if (this.$refs.cover) { |       if (this.$refs.cover && this.src !== this.placeholderUrl) { | ||||||
|         var { naturalWidth, naturalHeight } = this.$refs.cover |         var { naturalWidth, naturalHeight } = this.$refs.cover | ||||||
|         this.naturalHeight = naturalHeight |         this.naturalHeight = naturalHeight | ||||||
|         this.naturalWidth = naturalWidth |         this.naturalWidth = naturalWidth | ||||||
|  | |||||||
| @ -7,7 +7,7 @@ | |||||||
|       <p v-if="caption" class="text-gray-400 text-xs">{{ caption }}</p> |       <p v-if="caption" class="text-gray-400 text-xs">{{ caption }}</p> | ||||||
|     </div> |     </div> | ||||||
|     <div class="w-28"> |     <div class="w-28"> | ||||||
|       <p v-if="isOpenInPlayer" class="text-sm text-right text-gray-400">Streaming</p> |       <p v-if="isOpenInPlayer" class="text-sm text-right text-gray-400">{{ $strings.ButtonPlaying }}</p> | ||||||
|       <div v-else-if="isHovering" class="flex items-center justify-end -mx-1"> |       <div v-else-if="isHovering" class="flex items-center justify-end -mx-1"> | ||||||
|         <button class="outline-none mx-1 flex items-center" @click.stop="playClick"> |         <button class="outline-none mx-1 flex items-center" @click.stop="playClick"> | ||||||
|           <span class="material-icons text-success">play_arrow</span> |           <span class="material-icons text-success">play_arrow</span> | ||||||
|  | |||||||
| @ -2,13 +2,13 @@ | |||||||
|   <modals-modal v-model="show" name="queue-items" :width="800" :height="'unset'"> |   <modals-modal v-model="show" name="queue-items" :width="800" :height="'unset'"> | ||||||
|     <template #outer> |     <template #outer> | ||||||
|       <div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden"> |       <div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden"> | ||||||
|         <p class="font-book text-3xl text-white truncate">Player Queue</p> |         <p class="font-book text-3xl text-white truncate">{{ $strings.HeaderPlayerQueue }}</p> | ||||||
|       </div> |       </div> | ||||||
|     </template> |     </template> | ||||||
|     <div ref="container" class="w-full rounded-lg bg-bg box-shadow-md overflow-y-auto overflow-x-hidden py-4" style="max-height: 80vh"> |     <div ref="container" class="w-full rounded-lg bg-bg box-shadow-md overflow-y-auto overflow-x-hidden py-4" style="max-height: 80vh"> | ||||||
|       <div v-if="show" class="w-full h-full"> |       <div v-if="show" class="w-full h-full"> | ||||||
|         <div class="pb-4 px-4 flex items-center"> |         <div class="pb-4 px-4 flex items-center"> | ||||||
|           <p class="text-base text-gray-200">Player Queue</p> |           <p class="text-base text-gray-200">{{ $strings.HeaderPlayerQueue }}</p> | ||||||
|           <p class="text-base text-gray-400 px-4">{{ playerQueueItems.length }} Items</p> |           <p class="text-base text-gray-400 px-4">{{ playerQueueItems.length }} Items</p> | ||||||
|           <div class="flex-grow" /> |           <div class="flex-grow" /> | ||||||
|           <ui-checkbox v-model="playerQueueAutoPlay" label="Auto Play" medium checkbox-bg="primary" border-color="gray-600" label-class="pl-2 mb-px" /> |           <ui-checkbox v-model="playerQueueAutoPlay" label="Auto Play" medium checkbox-bg="primary" border-color="gray-600" label-class="pl-2 mb-px" /> | ||||||
|  | |||||||
| @ -22,15 +22,15 @@ | |||||||
|           <span class="material-icons text-2xl sm:text-3xl">format_list_bulleted</span> |           <span class="material-icons text-2xl sm:text-3xl">format_list_bulleted</span> | ||||||
|         </div> |         </div> | ||||||
| 
 | 
 | ||||||
|  |         <button v-if="playerQueueItems.length" class="outline-none text-gray-300 mx-1 lg:mx-2 hover:text-white" @mousedown.prevent @mouseup.prevent @click.stop="$emit('showPlayerQueueItems')"> | ||||||
|  |           <span class="material-icons text-2xl sm:text-3xl">queue_music</span> | ||||||
|  |         </button> | ||||||
|  | 
 | ||||||
|         <ui-tooltip v-if="chapters.length" direction="top" :text="useChapterTrack ? $strings.LabelUseFullTrack : $strings.LabelUseChapterTrack"> |         <ui-tooltip v-if="chapters.length" direction="top" :text="useChapterTrack ? $strings.LabelUseFullTrack : $strings.LabelUseChapterTrack"> | ||||||
|           <div class="cursor-pointer text-gray-300 mx-1 lg:mx-2 hover:text-white" @mousedown.prevent @mouseup.prevent @click.stop="setUseChapterTrack"> |           <div class="cursor-pointer text-gray-300 mx-1 lg:mx-2 hover:text-white" @mousedown.prevent @mouseup.prevent @click.stop="setUseChapterTrack"> | ||||||
|             <span class="material-icons text-2xl sm:text-3xl transform transition-transform" :class="useChapterTrack ? 'rotate-180' : ''">timelapse</span> |             <span class="material-icons text-2xl sm:text-3xl transform transition-transform" :class="useChapterTrack ? 'rotate-180' : ''">timelapse</span> | ||||||
|           </div> |           </div> | ||||||
|         </ui-tooltip> |         </ui-tooltip> | ||||||
| 
 |  | ||||||
|         <button v-if="playerQueueItems.length" class="outline-none text-gray-300 mx-1 lg:mx-2 hover:text-white" @mousedown.prevent @mouseup.prevent @click.stop="$emit('showPlayerQueueItems')"> |  | ||||||
|           <span class="material-icons text-2xl sm:text-3xl">queue_music</span> |  | ||||||
|         </button> |  | ||||||
|       </div> |       </div> | ||||||
| 
 | 
 | ||||||
|       <player-playback-controls :loading="loading" :seek-loading="seekLoading" :playback-rate.sync="playbackRate" :paused="paused" :has-next-chapter="hasNextChapter" @prevChapter="prevChapter" @nextChapter="nextChapter" @jumpForward="jumpForward" @jumpBackward="jumpBackward" @setPlaybackRate="setPlaybackRate" @playPause="playPause" /> |       <player-playback-controls :loading="loading" :seek-loading="seekLoading" :playback-rate.sync="playbackRate" :paused="paused" :has-next-chapter="hasNextChapter" @prevChapter="prevChapter" @nextChapter="nextChapter" @jumpForward="jumpForward" @jumpBackward="jumpBackward" @setPlaybackRate="setPlaybackRate" @playPause="playPause" /> | ||||||
|  | |||||||
| @ -72,11 +72,23 @@ export default { | |||||||
|       this.expanded = !this.expanded |       this.expanded = !this.expanded | ||||||
|     }, |     }, | ||||||
|     goToTimestamp(time) { |     goToTimestamp(time) { | ||||||
|  |       const queueItem = { | ||||||
|  |         libraryItemId: this.libraryItemId, | ||||||
|  |         libraryId: this.libraryItem.libraryId, | ||||||
|  |         episodeId: null, | ||||||
|  |         title: this.metadata.title, | ||||||
|  |         subtitle: this.metadata.authors.map((au) => au.name).join(', '), | ||||||
|  |         caption: '', | ||||||
|  |         duration: this.media.duration || null, | ||||||
|  |         coverPath: this.media.coverPath || null | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|       if (this.$store.getters['getIsMediaStreaming'](this.libraryItemId)) { |       if (this.$store.getters['getIsMediaStreaming'](this.libraryItemId)) { | ||||||
|         this.$eventBus.$emit('play-item', { |         this.$eventBus.$emit('play-item', { | ||||||
|           libraryItemId: this.libraryItemId, |           libraryItemId: this.libraryItemId, | ||||||
|           episodeId: null, |           episodeId: null, | ||||||
|           startTime: time |           startTime: time, | ||||||
|  |           queueItems: [queueItem] | ||||||
|         }) |         }) | ||||||
|       } else { |       } else { | ||||||
|         const payload = { |         const payload = { | ||||||
| @ -86,7 +98,8 @@ export default { | |||||||
|               this.$eventBus.$emit('play-item', { |               this.$eventBus.$emit('play-item', { | ||||||
|                 libraryItemId: this.libraryItemId, |                 libraryItemId: this.libraryItemId, | ||||||
|                 episodeId: null, |                 episodeId: null, | ||||||
|                 startTime: time |                 startTime: time, | ||||||
|  |                 queueItems: [queueItem] | ||||||
|               }) |               }) | ||||||
|             } |             } | ||||||
|           }, |           }, | ||||||
|  | |||||||
| @ -137,8 +137,22 @@ export default { | |||||||
|       this.isHovering = false |       this.isHovering = false | ||||||
|     }, |     }, | ||||||
|     playClick() { |     playClick() { | ||||||
|  |       const queueItems = [ | ||||||
|  |         { | ||||||
|  |           libraryItemId: this.book.id, | ||||||
|  |           libraryId: this.book.libraryId, | ||||||
|  |           episodeId: null, | ||||||
|  |           title: this.bookTitle, | ||||||
|  |           subtitle: this.bookAuthors.map((au) => au.name).join(', '), | ||||||
|  |           caption: '', | ||||||
|  |           duration: this.media.duration || null, | ||||||
|  |           coverPath: this.media.coverPath || null | ||||||
|  |         } | ||||||
|  |       ] | ||||||
|  | 
 | ||||||
|       this.$eventBus.$emit('play-item', { |       this.$eventBus.$emit('play-item', { | ||||||
|         libraryItemId: this.book.id |         libraryItemId: this.book.id, | ||||||
|  |         queueItems | ||||||
|       }) |       }) | ||||||
|     }, |     }, | ||||||
|     clickEdit() { |     clickEdit() { | ||||||
|  | |||||||
| @ -122,13 +122,42 @@ export default { | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     clickPlay() { |     clickPlay() { | ||||||
|       var nextBookNotRead = this.playableBooks.find((pb) => { |       const queueItems = [] | ||||||
|         var prog = this.$store.getters['user/getUserMediaProgress'](pb.id) | 
 | ||||||
|         return !prog || !prog.isFinished |       // Collection queue will start at the first unfinished book | ||||||
|  |       //   if all books are finished then entire collection is queued | ||||||
|  |       const itemsWithProgress = this.playableBooks.map((item) => { | ||||||
|  |         return { | ||||||
|  |           ...item, | ||||||
|  |           progress: this.$store.getters['user/getUserMediaProgress'](item.id) | ||||||
|  |         } | ||||||
|       }) |       }) | ||||||
|       if (nextBookNotRead) { | 
 | ||||||
|  |       const hasUnfinishedItems = itemsWithProgress.some((i) => !i.progress || !i.progress.isFinished) | ||||||
|  |       if (!hasUnfinishedItems) { | ||||||
|  |         console.warn('All items in collection are finished - starting at first item') | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       for (let i = 0; i < itemsWithProgress.length; i++) { | ||||||
|  |         const libraryItem = itemsWithProgress[i] | ||||||
|  |         if (!hasUnfinishedItems || !libraryItem.progress || !libraryItem.progress.isFinished) { | ||||||
|  |           queueItems.push({ | ||||||
|  |             libraryItemId: libraryItem.id, | ||||||
|  |             libraryId: libraryItem.libraryId, | ||||||
|  |             episodeId: null, | ||||||
|  |             title: libraryItem.media.metadata.title, | ||||||
|  |             subtitle: libraryItem.media.metadata.authors.map((au) => au.name).join(', '), | ||||||
|  |             caption: '', | ||||||
|  |             duration: libraryItem.media.duration || null, | ||||||
|  |             coverPath: libraryItem.media.coverPath || null | ||||||
|  |           }) | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (queueItems.length >= 0) { | ||||||
|         this.$eventBus.$emit('play-item', { |         this.$eventBus.$emit('play-item', { | ||||||
|           libraryItemId: nextBookNotRead.id |           libraryItemId: queueItems[0].libraryItemId, | ||||||
|  |           queueItems | ||||||
|         }) |         }) | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -127,12 +127,38 @@ export default { | |||||||
|         this.processingGoToTimestamp = false |         this.processingGoToTimestamp = false | ||||||
|         return |         return | ||||||
|       } |       } | ||||||
|       if (session.episodeId && !libraryItem.media.episodes.find((ep) => ep.id === session.episodeId)) { |       if (session.episodeId && !libraryItem.media.episodes.some((ep) => ep.id === session.episodeId)) { | ||||||
|         this.$toast.error('Failed to get podcast episode') |         this.$toast.error('Failed to get podcast episode') | ||||||
|         this.processingGoToTimestamp = false |         this.processingGoToTimestamp = false | ||||||
|         return |         return | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|  |       var queueItem = {} | ||||||
|  |       if (session.episodeId) { | ||||||
|  |         var episode = libraryItem.media.episodes.find((ep) => ep.id === session.episodeId) | ||||||
|  |         queueItem = { | ||||||
|  |           libraryItemId: libraryItem.id, | ||||||
|  |           libraryId: libraryItem.libraryId, | ||||||
|  |           episodeId: episode.id, | ||||||
|  |           title: episode.title, | ||||||
|  |           subtitle: libraryItem.media.metadata.title, | ||||||
|  |           caption: episode.publishedAt ? `Published ${this.$formatDate(episode.publishedAt, 'MMM do, yyyy')}` : 'Unknown publish date', | ||||||
|  |           duration: episode.audioFile.duration || null, | ||||||
|  |           coverPath: libraryItem.media.coverPath || null | ||||||
|  |         } | ||||||
|  |       } else { | ||||||
|  |         queueItem = { | ||||||
|  |           libraryItemId: libraryItem.id, | ||||||
|  |           libraryId: libraryItem.libraryId, | ||||||
|  |           episodeId: null, | ||||||
|  |           title: libraryItem.media.metadata.title, | ||||||
|  |           subtitle: libraryItem.media.metadata.authors.map((au) => au.name).join(', '), | ||||||
|  |           caption: '', | ||||||
|  |           duration: libraryItem.media.duration || null, | ||||||
|  |           coverPath: libraryItem.media.coverPath || null | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|       const payload = { |       const payload = { | ||||||
|         message: this.$getString('MessageStartPlaybackAtTime', [session.displayTitle, this.$secondsToTimestamp(session.currentTime)]), |         message: this.$getString('MessageStartPlaybackAtTime', [session.displayTitle, this.$secondsToTimestamp(session.currentTime)]), | ||||||
|         callback: (confirmed) => { |         callback: (confirmed) => { | ||||||
| @ -140,7 +166,8 @@ export default { | |||||||
|             this.$eventBus.$emit('play-item', { |             this.$eventBus.$emit('play-item', { | ||||||
|               libraryItemId: libraryItem.id, |               libraryItemId: libraryItem.id, | ||||||
|               episodeId: session.episodeId || null, |               episodeId: session.episodeId || null, | ||||||
|               startTime: session.currentTime |               startTime: session.currentTime, | ||||||
|  |               queueItems: [queueItem] | ||||||
|             }) |             }) | ||||||
|           } |           } | ||||||
|           this.processingGoToTimestamp = false |           this.processingGoToTimestamp = false | ||||||
|  | |||||||
| @ -114,12 +114,38 @@ export default { | |||||||
|         this.processingGoToTimestamp = false |         this.processingGoToTimestamp = false | ||||||
|         return |         return | ||||||
|       } |       } | ||||||
|       if (session.episodeId && !libraryItem.media.episodes.find((ep) => ep.id === session.episodeId)) { |       if (session.episodeId && !libraryItem.media.episodes.some((ep) => ep.id === session.episodeId)) { | ||||||
|         this.$toast.error('Failed to get podcast episode') |         this.$toast.error('Failed to get podcast episode') | ||||||
|         this.processingGoToTimestamp = false |         this.processingGoToTimestamp = false | ||||||
|         return |         return | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|  |       var queueItem = {} | ||||||
|  |       if (session.episodeId) { | ||||||
|  |         var episode = libraryItem.media.episodes.find((ep) => ep.id === session.episodeId) | ||||||
|  |         queueItem = { | ||||||
|  |           libraryItemId: libraryItem.id, | ||||||
|  |           libraryId: libraryItem.libraryId, | ||||||
|  |           episodeId: episode.id, | ||||||
|  |           title: episode.title, | ||||||
|  |           subtitle: libraryItem.media.metadata.title, | ||||||
|  |           caption: episode.publishedAt ? `Published ${this.$formatDate(episode.publishedAt, 'MMM do, yyyy')}` : 'Unknown publish date', | ||||||
|  |           duration: episode.audioFile.duration || null, | ||||||
|  |           coverPath: libraryItem.media.coverPath || null | ||||||
|  |         } | ||||||
|  |       } else { | ||||||
|  |         queueItem = { | ||||||
|  |           libraryItemId: libraryItem.id, | ||||||
|  |           libraryId: libraryItem.libraryId, | ||||||
|  |           episodeId: null, | ||||||
|  |           title: libraryItem.media.metadata.title, | ||||||
|  |           subtitle: libraryItem.media.metadata.authors.map((au) => au.name).join(', '), | ||||||
|  |           caption: '', | ||||||
|  |           duration: libraryItem.media.duration || null, | ||||||
|  |           coverPath: libraryItem.media.coverPath || null | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|       const payload = { |       const payload = { | ||||||
|         message: this.$getString('MessageStartPlaybackAtTime', [session.displayTitle, this.$secondsToTimestamp(session.currentTime)]), |         message: this.$getString('MessageStartPlaybackAtTime', [session.displayTitle, this.$secondsToTimestamp(session.currentTime)]), | ||||||
|         callback: (confirmed) => { |         callback: (confirmed) => { | ||||||
| @ -127,7 +153,8 @@ export default { | |||||||
|             this.$eventBus.$emit('play-item', { |             this.$eventBus.$emit('play-item', { | ||||||
|               libraryItemId: libraryItem.id, |               libraryItemId: libraryItem.id, | ||||||
|               episodeId: session.episodeId || null, |               episodeId: session.episodeId || null, | ||||||
|               startTime: session.currentTime |               startTime: session.currentTime, | ||||||
|  |               queueItems: [queueItem] | ||||||
|             }) |             }) | ||||||
|           } |           } | ||||||
|           this.processingGoToTimestamp = false |           this.processingGoToTimestamp = false | ||||||
|  | |||||||
| @ -137,12 +137,16 @@ | |||||||
|               {{ isMissing ? $strings.LabelMissing : $strings.LabelIncomplete }} |               {{ isMissing ? $strings.LabelMissing : $strings.LabelIncomplete }} | ||||||
|             </ui-btn> |             </ui-btn> | ||||||
| 
 | 
 | ||||||
|  |             <ui-tooltip v-if="showQueueBtn" :text="isQueued ? $strings.ButtonQueueRemoveItem : $strings.ButtonQueueAddItem" direction="top"> | ||||||
|  |               <ui-icon-btn :icon="isQueued ? 'playlist_add_check' : 'playlist_add'" class="mx-0.5" :class="isQueued ? 'text-success' : ''" @click="queueBtnClick" /> | ||||||
|  |             </ui-tooltip> | ||||||
|  | 
 | ||||||
|             <ui-btn v-if="showReadButton" color="info" :padding-x="4" small class="flex items-center h-9 mr-2" @click="openEbook"> |             <ui-btn v-if="showReadButton" color="info" :padding-x="4" small class="flex items-center h-9 mr-2" @click="openEbook"> | ||||||
|               <span class="material-icons -ml-2 pr-2 text-white">auto_stories</span> |               <span class="material-icons -ml-2 pr-2 text-white">auto_stories</span> | ||||||
|               {{ $strings.ButtonRead }} |               {{ $strings.ButtonRead }} | ||||||
|             </ui-btn> |             </ui-btn> | ||||||
| 
 | 
 | ||||||
|             <ui-tooltip v-if="userCanUpdate" text="Edit" direction="top"> |             <ui-tooltip v-if="userCanUpdate" :text="$strings.LabelEdit" direction="top"> | ||||||
|               <ui-icon-btn icon="edit" class="mx-0.5" @click="editClick" /> |               <ui-icon-btn icon="edit" class="mx-0.5" @click="editClick" /> | ||||||
|             </ui-tooltip> |             </ui-tooltip> | ||||||
| 
 | 
 | ||||||
| @ -398,6 +402,9 @@ export default { | |||||||
|     isStreaming() { |     isStreaming() { | ||||||
|       return this.streamLibraryItem && this.streamLibraryItem.id === this.libraryItemId |       return this.streamLibraryItem && this.streamLibraryItem.id === this.libraryItemId | ||||||
|     }, |     }, | ||||||
|  |     isQueued() { | ||||||
|  |       return this.$store.getters['getIsMediaQueued'](this.libraryItemId) | ||||||
|  |     }, | ||||||
|     userCanUpdate() { |     userCanUpdate() { | ||||||
|       return this.$store.getters['user/getUserCanUpdate'] |       return this.$store.getters['user/getUserCanUpdate'] | ||||||
|     }, |     }, | ||||||
| @ -412,6 +419,10 @@ export default { | |||||||
| 
 | 
 | ||||||
|       // If rss feed is open then show feed url to users otherwise just show to admins |       // If rss feed is open then show feed url to users otherwise just show to admins | ||||||
|       return this.userIsAdminOrUp || this.rssFeedUrl |       return this.userIsAdminOrUp || this.rssFeedUrl | ||||||
|  |     }, | ||||||
|  |     showQueueBtn() { | ||||||
|  |       if (this.isPodcast || this.isVideo) return false | ||||||
|  |       return !this.$store.getters['getIsStreamingFromDifferentLibrary'] && this.streamLibraryItem | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
| @ -536,6 +547,7 @@ export default { | |||||||
|           if (!podcastProgress || !podcastProgress.isFinished) { |           if (!podcastProgress || !podcastProgress.isFinished) { | ||||||
|             queueItems.push({ |             queueItems.push({ | ||||||
|               libraryItemId: this.libraryItemId, |               libraryItemId: this.libraryItemId, | ||||||
|  |               libraryId: this.libraryId, | ||||||
|               episodeId: episode.id, |               episodeId: episode.id, | ||||||
|               title: episode.title, |               title: episode.title, | ||||||
|               subtitle: this.title, |               subtitle: this.title, | ||||||
| @ -545,6 +557,18 @@ export default { | |||||||
|             }) |             }) | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|  |       } else { | ||||||
|  |         const queueItem = { | ||||||
|  |           libraryItemId: this.libraryItemId, | ||||||
|  |           libraryId: this.libraryId, | ||||||
|  |           episodeId: null, | ||||||
|  |           title: this.title, | ||||||
|  |           subtitle: this.authors.map((au) => au.name).join(', '), | ||||||
|  |           caption: '', | ||||||
|  |           duration: this.duration || null, | ||||||
|  |           coverPath: this.media.coverPath || null | ||||||
|  |         } | ||||||
|  |         queueItems.push(queueItem) | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       this.$eventBus.$emit('play-item', { |       this.$eventBus.$emit('play-item', { | ||||||
| @ -615,6 +639,26 @@ export default { | |||||||
|         console.log('RSS Feed Closed', data) |         console.log('RSS Feed Closed', data) | ||||||
|         this.rssFeedUrl = null |         this.rssFeedUrl = null | ||||||
|       } |       } | ||||||
|  |     }, | ||||||
|  |     queueBtnClick() { | ||||||
|  |       if (this.isQueued) { | ||||||
|  |         // Remove from queue | ||||||
|  |         this.$store.commit('removeItemFromQueue', { libraryItemId: this.libraryItemId }) | ||||||
|  |       } else { | ||||||
|  |         // Add to queue | ||||||
|  | 
 | ||||||
|  |         const queueItem = { | ||||||
|  |           libraryItemId: this.libraryItemId, | ||||||
|  |           libraryId: this.libraryId, | ||||||
|  |           episodeId: null, | ||||||
|  |           title: this.title, | ||||||
|  |           subtitle: this.authors.map((au) => au.name).join(', '), | ||||||
|  |           caption: '', | ||||||
|  |           duration: this.duration || null, | ||||||
|  |           coverPath: this.media.coverPath || null | ||||||
|  |         } | ||||||
|  |         this.$store.commit('addItemToQueue', queueItem) | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   mounted() { |   mounted() { | ||||||
|  | |||||||
| @ -107,6 +107,7 @@ | |||||||
|   "HeaderOtherFiles": "Other Files", |   "HeaderOtherFiles": "Other Files", | ||||||
|   "HeaderOpenRSSFeed": "Open RSS Feed", |   "HeaderOpenRSSFeed": "Open RSS Feed", | ||||||
|   "HeaderPermissions": "Permissions", |   "HeaderPermissions": "Permissions", | ||||||
|  |   "HeaderPlayerQueue": "Player Queue", | ||||||
|   "HeaderPodcastsToAdd": "Podcasts to Add", |   "HeaderPodcastsToAdd": "Podcasts to Add", | ||||||
|   "HeaderPreviewCover": "Preview Cover", |   "HeaderPreviewCover": "Preview Cover", | ||||||
|   "HeaderRemoveEpisode": "Remove Episode", |   "HeaderRemoveEpisode": "Remove Episode", | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user