mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Add:Bookmarks icon btn on library item page and ability to open player at specified time #796
This commit is contained in:
		
							parent
							
								
									45cd39ac0c
								
							
						
					
					
						commit
						a8c7905f6d
					
				| @ -381,7 +381,7 @@ export default { | |||||||
|         if (this.$refs.audioPlayer) this.$refs.audioPlayer.checkUpdateChapterTrack() |         if (this.$refs.audioPlayer) this.$refs.audioPlayer.checkUpdateChapterTrack() | ||||||
|       }) |       }) | ||||||
| 
 | 
 | ||||||
|       this.playerHandler.load(libraryItem, episodeId, true, this.initialPlaybackRate) |       this.playerHandler.load(libraryItem, episodeId, true, this.initialPlaybackRate, payload.startTime) | ||||||
|     }, |     }, | ||||||
|     pauseItem() { |     pauseItem() { | ||||||
|       this.playerHandler.pause() |       this.playerHandler.pause() | ||||||
| @ -393,11 +393,13 @@ export default { | |||||||
|   }, |   }, | ||||||
|   mounted() { |   mounted() { | ||||||
|     this.$eventBus.$on('cast-session-active', this.castSessionActive) |     this.$eventBus.$on('cast-session-active', this.castSessionActive) | ||||||
|  |     this.$eventBus.$on('playback-seek', this.seek) | ||||||
|     this.$eventBus.$on('play-item', this.playLibraryItem) |     this.$eventBus.$on('play-item', this.playLibraryItem) | ||||||
|     this.$eventBus.$on('pause-item', this.pauseItem) |     this.$eventBus.$on('pause-item', this.pauseItem) | ||||||
|   }, |   }, | ||||||
|   beforeDestroy() { |   beforeDestroy() { | ||||||
|     this.$eventBus.$off('cast-session-active', this.castSessionActive) |     this.$eventBus.$off('cast-session-active', this.castSessionActive) | ||||||
|  |     this.$eventBus.$off('playback-seek', this.seek) | ||||||
|     this.$eventBus.$off('play-item', this.playLibraryItem) |     this.$eventBus.$off('play-item', this.playLibraryItem) | ||||||
|     this.$eventBus.$off('pause-item', this.pauseItem) |     this.$eventBus.$off('pause-item', this.pauseItem) | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -1,6 +1,11 @@ | |||||||
| <template> | <template> | ||||||
|   <modals-modal v-model="show" name="bookmarks" :width="500" :height="'unset'"> |   <modals-modal v-model="show" name="bookmarks" :width="500" :height="'unset'"> | ||||||
|     <div ref="container" class="w-full rounded-lg bg-primary box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 80vh"> |     <template #outer> | ||||||
|  |       <div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden"> | ||||||
|  |         <p class="font-book text-3xl text-white truncate">Your Bookmarks</p> | ||||||
|  |       </div> | ||||||
|  |     </template> | ||||||
|  |     <div ref="container" class="w-full rounded-lg bg-bg box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 80vh"> | ||||||
|       <div v-if="show" class="w-full h-full"> |       <div v-if="show" class="w-full h-full"> | ||||||
|         <template v-for="bookmark in bookmarks"> |         <template v-for="bookmark in bookmarks"> | ||||||
|           <modals-bookmarks-bookmark-item :key="bookmark.id" :highlight="currentTime === bookmark.time" :bookmark="bookmark" @click="clickBookmark" @update="submitUpdateBookmark" @delete="deleteBookmark" /> |           <modals-bookmarks-bookmark-item :key="bookmark.id" :highlight="currentTime === bookmark.time" :bookmark="bookmark" @click="clickBookmark" @update="submitUpdateBookmark" @delete="deleteBookmark" /> | ||||||
| @ -8,8 +13,8 @@ | |||||||
|         <div v-if="!bookmarks.length" class="flex h-32 items-center justify-center"> |         <div v-if="!bookmarks.length" class="flex h-32 items-center justify-center"> | ||||||
|           <p class="text-xl">No Bookmarks</p> |           <p class="text-xl">No Bookmarks</p> | ||||||
|         </div> |         </div> | ||||||
|         <div class="w-full h-px bg-white bg-opacity-10" /> |         <div v-if="!hideCreate" class="w-full h-px bg-white bg-opacity-10" /> | ||||||
|         <form @submit.prevent="submitCreateBookmark"> |         <form v-if="!hideCreate" @submit.prevent="submitCreateBookmark"> | ||||||
|           <div v-show="canCreateBookmark" class="flex px-4 py-2 items-center text-center border-b border-white border-opacity-10 text-white text-opacity-80"> |           <div v-show="canCreateBookmark" class="flex px-4 py-2 items-center text-center border-b border-white border-opacity-10 text-white text-opacity-80"> | ||||||
|             <div class="w-16 max-w-16 text-center"> |             <div class="w-16 max-w-16 text-center"> | ||||||
|               <p class="text-sm font-mono text-gray-400"> |               <p class="text-sm font-mono text-gray-400"> | ||||||
| @ -39,7 +44,8 @@ export default { | |||||||
|       type: Number, |       type: Number, | ||||||
|       default: 0 |       default: 0 | ||||||
|     }, |     }, | ||||||
|     libraryItemId: String |     libraryItemId: String, | ||||||
|  |     hideCreate: Boolean | ||||||
|   }, |   }, | ||||||
|   data() { |   data() { | ||||||
|     return { |     return { | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| <template> | <template> | ||||||
|   <div class="flex items-center px-4 py-4 justify-start relative hover:bg-bg" :class="wrapperClass" @click="click" @mouseover="mouseover" @mouseleave="mouseleave"> |   <div class="flex items-center px-4 py-4 justify-start relative bg-primary hover:bg-opacity-25" :class="wrapperClass" @click="click" @mouseover="mouseover" @mouseleave="mouseleave"> | ||||||
|     <!-- <span class="material-icons" :class="highlight ? 'text-success' : 'text-white text-opacity-80'">{{ highlight ? 'bookmark' : 'bookmark_border' }}</span> --> |  | ||||||
|     <div class="w-16 max-w-16 text-center"> |     <div class="w-16 max-w-16 text-center"> | ||||||
|       <p class="text-sm font-mono text-gray-400"> |       <p class="text-sm font-mono text-gray-400"> | ||||||
|         {{ this.$secondsToTimestamp(bookmark.time) }} |         {{ this.$secondsToTimestamp(bookmark.time) }} | ||||||
|  | |||||||
| @ -11,7 +11,7 @@ | |||||||
| 
 | 
 | ||||||
|             <!-- Item Cover Overlay --> |             <!-- Item Cover Overlay --> | ||||||
|             <div class="absolute top-0 left-0 w-full h-full z-10 bg-black bg-opacity-30 opacity-0 hover:opacity-100 transition-opacity" @mousedown.prevent @mouseup.prevent> |             <div class="absolute top-0 left-0 w-full h-full z-10 bg-black bg-opacity-30 opacity-0 hover:opacity-100 transition-opacity" @mousedown.prevent @mouseup.prevent> | ||||||
|               <div v-show="showPlayButton && !streaming" class="h-full flex items-center justify-center pointer-events-none"> |               <div v-show="showPlayButton && !isStreaming" class="h-full flex items-center justify-center pointer-events-none"> | ||||||
|                 <div class="hover:text-white text-gray-200 hover:scale-110 transform duration-200 pointer-events-auto cursor-pointer" @click.stop.prevent="startStream"> |                 <div class="hover:text-white text-gray-200 hover:scale-110 transform duration-200 pointer-events-auto cursor-pointer" @click.stop.prevent="startStream"> | ||||||
|                   <span class="material-icons text-4xl">play_circle_filled</span> |                   <span class="material-icons text-4xl">play_circle_filled</span> | ||||||
|                 </div> |                 </div> | ||||||
| @ -129,12 +129,12 @@ | |||||||
| 
 | 
 | ||||||
|           <!-- Icon buttons --> |           <!-- Icon buttons --> | ||||||
|           <div class="flex items-center justify-center md:justify-start pt-4"> |           <div class="flex items-center justify-center md:justify-start pt-4"> | ||||||
|             <ui-btn v-if="showPlayButton" :disabled="streaming" color="success" :padding-x="4" small class="flex items-center h-9 mr-2" @click="startStream"> |             <ui-btn v-if="showPlayButton" :disabled="isStreaming" color="success" :padding-x="4" small class="flex items-center h-9 mr-2" @click="startStream"> | ||||||
|               <span v-show="!streaming" class="material-icons -ml-2 pr-1 text-white">play_arrow</span> |               <span v-show="!isStreaming" class="material-icons -ml-2 pr-1 text-white">play_arrow</span> | ||||||
|               {{ streaming ? 'Playing' : 'Play' }} |               {{ isStreaming ? 'Playing' : 'Play' }} | ||||||
|             </ui-btn> |             </ui-btn> | ||||||
|             <ui-btn v-else-if="isMissing || isInvalid" color="error" :padding-x="4" small class="flex items-center h-9 mr-2"> |             <ui-btn v-else-if="isMissing || isInvalid" color="error" :padding-x="4" small class="flex items-center h-9 mr-2"> | ||||||
|               <span v-show="!streaming" class="material-icons -ml-2 pr-1 text-white">error</span> |               <span v-show="!isStreaming" class="material-icons -ml-2 pr-1 text-white">error</span> | ||||||
|               {{ isMissing ? 'Missing' : 'Incomplete' }} |               {{ isMissing ? 'Missing' : 'Incomplete' }} | ||||||
|             </ui-btn> |             </ui-btn> | ||||||
| 
 | 
 | ||||||
| @ -160,7 +160,11 @@ | |||||||
|               <ui-icon-btn icon="search" class="mx-0.5" :loading="fetchingRSSFeed" outlined @click="findEpisodesClick" /> |               <ui-icon-btn icon="search" class="mx-0.5" :loading="fetchingRSSFeed" outlined @click="findEpisodesClick" /> | ||||||
|             </ui-tooltip> |             </ui-tooltip> | ||||||
| 
 | 
 | ||||||
|             <!-- Experimental RSS feed open --> |             <ui-tooltip v-if="bookmarks.length" text="Your Bookmarks" direction="top"> | ||||||
|  |               <ui-icon-btn :icon="bookmarks.length ? 'bookmarks' : 'bookmark_border'" class="mx-0.5" @click="clickBookmarksBtn" /> | ||||||
|  |             </ui-tooltip> | ||||||
|  | 
 | ||||||
|  |             <!-- RSS feed --> | ||||||
|             <ui-tooltip v-if="showRssFeedBtn" text="Open RSS Feed" direction="top"> |             <ui-tooltip v-if="showRssFeedBtn" text="Open RSS Feed" direction="top"> | ||||||
|               <ui-icon-btn icon="rss_feed" class="mx-0.5" :bg-color="rssFeedUrl ? 'success' : 'primary'" outlined @click="clickRSSFeed" /> |               <ui-icon-btn icon="rss_feed" class="mx-0.5" :bg-color="rssFeedUrl ? 'success' : 'primary'" outlined @click="clickRSSFeed" /> | ||||||
|             </ui-tooltip> |             </ui-tooltip> | ||||||
| @ -189,6 +193,7 @@ | |||||||
| 
 | 
 | ||||||
|     <modals-podcast-episode-feed v-model="showPodcastEpisodeFeed" :library-item="libraryItem" :episodes="podcastFeedEpisodes" /> |     <modals-podcast-episode-feed v-model="showPodcastEpisodeFeed" :library-item="libraryItem" :episodes="podcastFeedEpisodes" /> | ||||||
|     <modals-rssfeed-view-modal v-model="showRssFeedModal" :library-item="libraryItem" :feed-url="rssFeedUrl" /> |     <modals-rssfeed-view-modal v-model="showRssFeedModal" :library-item="libraryItem" :feed-url="rssFeedUrl" /> | ||||||
|  |     <modals-bookmarks-modal v-model="showBookmarksModal" :bookmarks="bookmarks" :library-item-id="libraryItemId" hide-create @select="selectBookmark" /> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| @ -222,7 +227,8 @@ export default { | |||||||
|       podcastFeedEpisodes: [], |       podcastFeedEpisodes: [], | ||||||
|       episodesDownloading: [], |       episodesDownloading: [], | ||||||
|       episodeDownloadsQueued: [], |       episodeDownloadsQueued: [], | ||||||
|       showRssFeedModal: false |       showRssFeedModal: false, | ||||||
|  |       showBookmarksModal: false | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
| @ -296,6 +302,10 @@ export default { | |||||||
|     chapters() { |     chapters() { | ||||||
|       return this.media.chapters || [] |       return this.media.chapters || [] | ||||||
|     }, |     }, | ||||||
|  |     bookmarks() { | ||||||
|  |       if (this.isPodcast) return [] | ||||||
|  |       return this.$store.getters['user/getUserBookmarksForItem'](this.libraryItemId) | ||||||
|  |     }, | ||||||
|     tracks() { |     tracks() { | ||||||
|       return this.media.tracks || [] |       return this.media.tracks || [] | ||||||
|     }, |     }, | ||||||
| @ -389,7 +399,7 @@ export default { | |||||||
|     streamLibraryItem() { |     streamLibraryItem() { | ||||||
|       return this.$store.state.streamLibraryItem |       return this.$store.state.streamLibraryItem | ||||||
|     }, |     }, | ||||||
|     streaming() { |     isStreaming() { | ||||||
|       return this.streamLibraryItem && this.streamLibraryItem.id === this.libraryItemId |       return this.streamLibraryItem && this.streamLibraryItem.id === this.libraryItemId | ||||||
|     }, |     }, | ||||||
|     userCanUpdate() { |     userCanUpdate() { | ||||||
| @ -409,6 +419,23 @@ export default { | |||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|  |     clickBookmarksBtn() { | ||||||
|  |       this.showBookmarksModal = true | ||||||
|  |     }, | ||||||
|  |     selectBookmark(bookmark) { | ||||||
|  |       if (!bookmark) return | ||||||
|  |       console.log('Select bookmark', bookmark) | ||||||
|  |       if (this.isStreaming) { | ||||||
|  |         this.$eventBus.$emit('playback-seek', bookmark.time) | ||||||
|  |       } else if (this.streamLibraryItem) { | ||||||
|  |         if (confirm(`Are you sure you want to play ${this.title} @ ${this.$secondsToTimestamp(bookmark.time)}?`)) { | ||||||
|  |           this.startStream(bookmark.time) | ||||||
|  |         } | ||||||
|  |       } else { | ||||||
|  |         this.startStream(bookmark.time) | ||||||
|  |       } | ||||||
|  |       this.showBookmarksModal = false | ||||||
|  |     }, | ||||||
|     clearDownloadQueue() { |     clearDownloadQueue() { | ||||||
|       if (confirm('Are you sure you want to clear episode download queue?')) { |       if (confirm('Are you sure you want to clear episode download queue?')) { | ||||||
|         this.$axios |         this.$axios | ||||||
| @ -470,7 +497,7 @@ export default { | |||||||
|           this.$toast.error(`Failed to mark as ${updatePayload.isFinished ? 'Finished' : 'Not Finished'}`) |           this.$toast.error(`Failed to mark as ${updatePayload.isFinished ? 'Finished' : 'Not Finished'}`) | ||||||
|         }) |         }) | ||||||
|     }, |     }, | ||||||
|     startStream() { |     startStream(startTime = null) { | ||||||
|       var episodeId = null |       var episodeId = null | ||||||
|       if (this.isPodcast) { |       if (this.isPodcast) { | ||||||
|         var episode = this.podcastEpisodes.find((ep) => { |         var episode = this.podcastEpisodes.find((ep) => { | ||||||
| @ -483,7 +510,8 @@ export default { | |||||||
| 
 | 
 | ||||||
|       this.$eventBus.$emit('play-item', { |       this.$eventBus.$emit('play-item', { | ||||||
|         libraryItemId: this.libraryItem.id, |         libraryItemId: this.libraryItem.id, | ||||||
|         episodeId |         episodeId, | ||||||
|  |         startTime | ||||||
|       }) |       }) | ||||||
|     }, |     }, | ||||||
|     editClick() { |     editClick() { | ||||||
|  | |||||||
| @ -18,6 +18,7 @@ export default class PlayerHandler { | |||||||
|     this.isHlsTranscode = false |     this.isHlsTranscode = false | ||||||
|     this.isVideo = false |     this.isVideo = false | ||||||
|     this.currentSessionId = null |     this.currentSessionId = null | ||||||
|  |     this.startTimeOverride = undefined // Used for starting playback at a specific time (i.e. clicking bookmark from library item page)
 | ||||||
|     this.startTime = 0 |     this.startTime = 0 | ||||||
| 
 | 
 | ||||||
|     this.failedProgressSyncs = 0 |     this.failedProgressSyncs = 0 | ||||||
| @ -51,12 +52,13 @@ export default class PlayerHandler { | |||||||
|     return this.libraryItem.media.episodes.find(ep => ep.id === this.episodeId) |     return this.libraryItem.media.episodes.find(ep => ep.id === this.episodeId) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   load(libraryItem, episodeId, playWhenReady, playbackRate) { |   load(libraryItem, episodeId, playWhenReady, playbackRate, startTimeOverride = undefined) { | ||||||
|     this.libraryItem = libraryItem |     this.libraryItem = libraryItem | ||||||
|     this.episodeId = episodeId |     this.episodeId = episodeId | ||||||
|     this.playWhenReady = playWhenReady |     this.playWhenReady = playWhenReady | ||||||
|     this.initialPlaybackRate = playbackRate |     this.initialPlaybackRate = playbackRate | ||||||
|     this.isVideo = libraryItem.mediaType === 'video' |     this.isVideo = libraryItem.mediaType === 'video' | ||||||
|  |     this.startTimeOverride = (startTimeOverride == null || isNaN(startTimeOverride)) ? undefined : Number(startTimeOverride) | ||||||
| 
 | 
 | ||||||
|     if (!this.player) this.switchPlayer(playWhenReady) |     if (!this.player) this.switchPlayer(playWhenReady) | ||||||
|     else this.prepare() |     else this.prepare() | ||||||
| @ -142,12 +144,14 @@ export default class PlayerHandler { | |||||||
|     } else { |     } else { | ||||||
|       this.stopPlayInterval() |       this.stopPlayInterval() | ||||||
|     } |     } | ||||||
|  |     if (this.player) { | ||||||
|       if (this.playerState === 'LOADED' || this.playerState === 'PLAYING') { |       if (this.playerState === 'LOADED' || this.playerState === 'PLAYING') { | ||||||
|         this.ctx.setDuration(this.getDuration()) |         this.ctx.setDuration(this.getDuration()) | ||||||
|       } |       } | ||||||
|       if (this.playerState !== 'LOADING') { |       if (this.playerState !== 'LOADING') { | ||||||
|         this.ctx.setCurrentTime(this.player.getCurrentTime()) |         this.ctx.setCurrentTime(this.player.getCurrentTime()) | ||||||
|       } |       } | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     this.ctx.setPlaying(this.playerState === 'PLAYING') |     this.ctx.setPlaying(this.playerState === 'PLAYING') | ||||||
|     this.ctx.playerLoading = this.playerState === 'LOADING' |     this.ctx.playerLoading = this.playerState === 'LOADING' | ||||||
| @ -183,13 +187,14 @@ export default class PlayerHandler { | |||||||
|     this.isVideo = session.libraryItem.mediaType === 'video' |     this.isVideo = session.libraryItem.mediaType === 'video' | ||||||
|     this.playWhenReady = false |     this.playWhenReady = false | ||||||
|     this.initialPlaybackRate = playbackRate |     this.initialPlaybackRate = playbackRate | ||||||
|  |     this.startTimeOverride = undefined | ||||||
| 
 | 
 | ||||||
|     this.prepareSession(session) |     this.prepareSession(session) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   prepareSession(session) { |   prepareSession(session) { | ||||||
|     this.failedProgressSyncs = 0 |     this.failedProgressSyncs = 0 | ||||||
|     this.startTime = session.currentTime |     this.startTime = this.startTimeOverride !== undefined ? this.startTimeOverride : session.currentTime | ||||||
|     this.currentSessionId = session.id |     this.currentSessionId = session.id | ||||||
|     this.displayTitle = session.displayTitle |     this.displayTitle = session.displayTitle | ||||||
|     this.displayAuthor = session.displayAuthor |     this.displayAuthor = session.displayAuthor | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user