mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-31 02:17:01 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			572 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			572 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
| <template>
 | |
|   <div v-if="streamLibraryItem" id="mediaPlayerContainer" class="w-full fixed bottom-0 left-0 right-0 h-48 lg:h-40 z-50 bg-primary px-2 lg:px-4 pb-1 lg:pb-4 pt-2">
 | |
|     <div class="absolute left-2 top-2 lg:left-4 cursor-pointer">
 | |
|       <covers-book-cover expand-on-click :library-item="streamLibraryItem" :width="bookCoverWidth" :book-cover-aspect-ratio="coverAspectRatio" />
 | |
|     </div>
 | |
|     <div class="flex items-start mb-6 lg:mb-0" :class="isSquareCover ? 'pl-18 sm:pl-24' : 'pl-12 sm:pl-16'">
 | |
|       <div class="min-w-0 w-full">
 | |
|         <div class="flex items-center">
 | |
|           <nuxt-link :to="`/item/${streamLibraryItem.id}`" class="hover:underline cursor-pointer text-sm sm:text-lg block truncate">
 | |
|             {{ title }}
 | |
|           </nuxt-link>
 | |
|           <widgets-explicit-indicator v-if="isExplicit" />
 | |
|         </div>
 | |
|         <div class="text-gray-400 flex items-center w-1/2 sm:w-4/5 lg:w-2/5">
 | |
|           <span class="material-symbols text-sm">person</span>
 | |
|           <div v-if="podcastAuthor" class="pl-1 sm:pl-1.5 text-xs sm:text-base truncate">{{ podcastAuthor }}</div>
 | |
|           <div v-else-if="authors.length" class="pl-1 sm:pl-1.5 text-xs sm:text-base truncate">
 | |
|             <nuxt-link v-for="(author, index) in authors" :key="index" :to="`/author/${author.id}`" class="hover:underline">{{ author.name }}<span v-if="index < authors.length - 1">, </span></nuxt-link>
 | |
|           </div>
 | |
|           <div v-else class="text-xs sm:text-base cursor-pointer pl-1 sm:pl-1.5">{{ $strings.LabelUnknown }}</div>
 | |
|         </div>
 | |
| 
 | |
|         <div class="text-gray-400 flex items-center">
 | |
|           <span class="material-symbols text-xs">schedule</span>
 | |
|           <p class="font-mono text-xs sm:text-sm pl-1 sm:pl-1.5 pb-px">{{ totalDurationPretty }}</p>
 | |
|         </div>
 | |
|       </div>
 | |
|       <div class="grow" />
 | |
|       <ui-tooltip direction="top" :text="$strings.LabelClosePlayer">
 | |
|         <button :aria-label="$strings.LabelClosePlayer" class="material-symbols sm:px-2 py-1 lg:p-4 cursor-pointer text-xl sm:text-2xl" @click="closePlayer">close</button>
 | |
|       </ui-tooltip>
 | |
|     </div>
 | |
|     <player-ui
 | |
|       ref="audioPlayer"
 | |
|       :chapters="chapters"
 | |
|       :current-chapter="currentChapter"
 | |
|       :paused="!isPlaying"
 | |
|       :loading="playerLoading"
 | |
|       :bookmarks="bookmarks"
 | |
|       :sleep-timer-set="sleepTimerSet"
 | |
|       :sleep-timer-remaining="sleepTimerRemaining"
 | |
|       :sleep-timer-type="sleepTimerType"
 | |
|       :is-podcast="isPodcast"
 | |
|       :hasNextItemInQueue="hasNextItemInQueue"
 | |
|       @playPause="playPause"
 | |
|       @jumpForward="jumpForward"
 | |
|       @jumpBackward="jumpBackward"
 | |
|       @setVolume="setVolume"
 | |
|       @setPlaybackRate="setPlaybackRate"
 | |
|       @seek="seek"
 | |
|       @nextItemInQueue="playNextItemInQueue"
 | |
|       @close="closePlayer"
 | |
|       @showBookmarks="showBookmarks"
 | |
|       @showSleepTimer="showSleepTimerModal = true"
 | |
|       @showPlayerQueueItems="showPlayerQueueItemsModal = true"
 | |
|     />
 | |
| 
 | |
|     <modals-bookmarks-modal v-model="showBookmarksModal" :bookmarks="bookmarks" :current-time="bookmarkCurrentTime" :playback-rate="currentPlaybackRate" :library-item-id="libraryItemId" @select="selectBookmark" />
 | |
| 
 | |
|     <modals-sleep-timer-modal v-model="showSleepTimerModal" :timer-set="sleepTimerSet" :timer-type="sleepTimerType" :remaining="sleepTimerRemaining" :has-chapters="!!chapters.length" @set="setSleepTimer" @cancel="cancelSleepTimer" @increment="incrementSleepTimer" @decrement="decrementSleepTimer" />
 | |
| 
 | |
|     <modals-player-queue-items-modal v-model="showPlayerQueueItemsModal" />
 | |
|   </div>
 | |
| </template>
 | |
| 
 | |
| <script>
 | |
| import PlayerHandler from '@/players/PlayerHandler'
 | |
| 
 | |
| export default {
 | |
|   data() {
 | |
|     return {
 | |
|       playerHandler: new PlayerHandler(this),
 | |
|       totalDuration: 0,
 | |
|       showBookmarksModal: false,
 | |
|       bookmarkCurrentTime: 0,
 | |
|       playerLoading: false,
 | |
|       isPlaying: false,
 | |
|       currentTime: 0,
 | |
|       showSleepTimerModal: false,
 | |
|       showPlayerQueueItemsModal: false,
 | |
|       sleepTimerSet: false,
 | |
|       sleepTimerRemaining: 0,
 | |
|       sleepTimerType: null,
 | |
|       sleepTimer: null,
 | |
|       displayTitle: null,
 | |
|       currentPlaybackRate: 1,
 | |
|       syncFailedToast: null,
 | |
|       coverAspectRatio: 1,
 | |
|       lastChapterId: null
 | |
|     }
 | |
|   },
 | |
|   computed: {
 | |
|     isSquareCover() {
 | |
|       return this.coverAspectRatio === 1
 | |
|     },
 | |
|     isMobile() {
 | |
|       return this.$store.state.globals.isMobile
 | |
|     },
 | |
|     bookCoverWidth() {
 | |
|       if (this.isMobile) return 64 / this.coverAspectRatio
 | |
|       return 77 / this.coverAspectRatio
 | |
|     },
 | |
|     cover() {
 | |
|       if (this.media.coverPath) return this.media.coverPath
 | |
|       return 'Logo.png'
 | |
|     },
 | |
|     user() {
 | |
|       return this.$store.state.user.user
 | |
|     },
 | |
|     userMediaProgress() {
 | |
|       if (!this.libraryItemId) return
 | |
|       return this.$store.getters['user/getUserMediaProgress'](this.libraryItemId)
 | |
|     },
 | |
|     userItemCurrentTime() {
 | |
|       return this.userMediaProgress ? this.userMediaProgress.currentTime || 0 : 0
 | |
|     },
 | |
|     bookmarks() {
 | |
|       if (!this.libraryItemId) return []
 | |
|       return this.$store.getters['user/getUserBookmarksForItem'](this.libraryItemId)
 | |
|     },
 | |
|     streamLibraryItem() {
 | |
|       return this.$store.state.streamLibraryItem
 | |
|     },
 | |
|     streamEpisode() {
 | |
|       if (!this.$store.state.streamEpisodeId) return null
 | |
|       const episodes = this.streamLibraryItem.media.episodes || []
 | |
|       return episodes.find((ep) => ep.id === this.$store.state.streamEpisodeId)
 | |
|     },
 | |
|     libraryItemId() {
 | |
|       return this.streamLibraryItem?.id || null
 | |
|     },
 | |
|     media() {
 | |
|       return this.streamLibraryItem?.media || {}
 | |
|     },
 | |
|     isPodcast() {
 | |
|       return this.streamLibraryItem?.mediaType === 'podcast'
 | |
|     },
 | |
|     isExplicit() {
 | |
|       return !!this.mediaMetadata.explicit
 | |
|     },
 | |
|     mediaMetadata() {
 | |
|       return this.media.metadata || {}
 | |
|     },
 | |
|     chapters() {
 | |
|       if (this.streamEpisode) return this.streamEpisode.chapters || []
 | |
|       return this.media.chapters || []
 | |
|     },
 | |
|     currentChapter() {
 | |
|       return this.chapters.find((chapter) => chapter.start <= this.currentTime && this.currentTime < chapter.end)
 | |
|     },
 | |
|     title() {
 | |
|       if (this.playerHandler.displayTitle) return this.playerHandler.displayTitle
 | |
|       return this.mediaMetadata.title || 'No Title'
 | |
|     },
 | |
|     authors() {
 | |
|       return this.mediaMetadata.authors || []
 | |
|     },
 | |
|     libraryId() {
 | |
|       return this.streamLibraryItem?.libraryId || null
 | |
|     },
 | |
|     totalDurationPretty() {
 | |
|       // Adjusted by playback rate
 | |
|       return this.$secondsToTimestamp(this.totalDuration / this.currentPlaybackRate)
 | |
|     },
 | |
|     podcastAuthor() {
 | |
|       if (!this.isPodcast) return null
 | |
|       return this.mediaMetadata.author || this.$strings.LabelUnknown
 | |
|     },
 | |
|     hasNextItemInQueue() {
 | |
|       return this.currentPlayerQueueIndex < this.playerQueueItems.length - 1
 | |
|     },
 | |
|     currentPlayerQueueIndex() {
 | |
|       if (!this.libraryItemId) return -1
 | |
|       return this.playerQueueItems.findIndex((i) => {
 | |
|         if (this.streamEpisode?.id) return i.episodeId === this.streamEpisode.id
 | |
|         return i.libraryItemId === this.libraryItemId
 | |
|       })
 | |
|     },
 | |
|     playerQueueItems() {
 | |
|       return this.$store.state.playerQueueItems || []
 | |
|     }
 | |
|   },
 | |
|   methods: {
 | |
|     mediaFinished(libraryItemId, episodeId) {
 | |
|       // Play next item in queue
 | |
|       if (!this.playerQueueItems.length || !this.$store.state.playerQueueAutoPlay) {
 | |
|         // TODO: Set media finished flag so play button will play next queue item
 | |
|         return
 | |
|       }
 | |
|       var currentQueueIndex = this.playerQueueItems.findIndex((i) => {
 | |
|         if (episodeId) return i.libraryItemId === libraryItemId && i.episodeId === episodeId
 | |
|         return i.libraryItemId === libraryItemId
 | |
|       })
 | |
|       if (currentQueueIndex < 0) {
 | |
|         console.error('Media finished not found in queue - using first in queue', this.playerQueueItems)
 | |
|         currentQueueIndex = -1
 | |
|       }
 | |
|       if (currentQueueIndex === this.playerQueueItems.length - 1) {
 | |
|         console.log('Finished last item in queue')
 | |
|         return
 | |
|       }
 | |
|       const nextItemInQueue = this.playerQueueItems[currentQueueIndex + 1]
 | |
|       if (nextItemInQueue) {
 | |
|         this.playLibraryItem({
 | |
|           libraryItemId: nextItemInQueue.libraryItemId,
 | |
|           episodeId: nextItemInQueue.episodeId || null,
 | |
|           queueItems: this.playerQueueItems
 | |
|         })
 | |
|       }
 | |
|     },
 | |
|     setPlaying(isPlaying) {
 | |
|       this.isPlaying = isPlaying
 | |
|       this.$store.commit('setIsPlaying', isPlaying)
 | |
|       this.updateMediaSessionPlaybackState()
 | |
|     },
 | |
|     setSleepTimer(time) {
 | |
|       this.sleepTimerSet = true
 | |
|       this.showSleepTimerModal = false
 | |
| 
 | |
|       this.sleepTimerType = time.timerType
 | |
|       if (this.sleepTimerType === this.$constants.SleepTimerTypes.COUNTDOWN) {
 | |
|         this.runSleepTimer(time)
 | |
|       }
 | |
|     },
 | |
|     runSleepTimer(time) {
 | |
|       this.sleepTimerRemaining = time.seconds
 | |
| 
 | |
|       var lastTick = Date.now()
 | |
|       clearInterval(this.sleepTimer)
 | |
|       this.sleepTimer = setInterval(() => {
 | |
|         var elapsed = Date.now() - lastTick
 | |
|         lastTick = Date.now()
 | |
|         this.sleepTimerRemaining -= elapsed / 1000
 | |
| 
 | |
|         if (this.sleepTimerRemaining <= 0) {
 | |
|           this.sleepTimerEnd()
 | |
|         }
 | |
|       }, 1000)
 | |
|     },
 | |
|     checkChapterEnd() {
 | |
|       if (!this.currentChapter) return
 | |
| 
 | |
|       // Track chapter transitions by comparing current chapter with last chapter
 | |
|       if (this.lastChapterId !== this.currentChapter.id) {
 | |
|         // Chapter changed - if we had a previous chapter, this means we crossed a boundary
 | |
|         if (this.lastChapterId) {
 | |
|           this.sleepTimerEnd()
 | |
|         }
 | |
|         this.lastChapterId = this.currentChapter.id
 | |
|       }
 | |
|     },
 | |
|     sleepTimerEnd() {
 | |
|       this.clearSleepTimer()
 | |
|       this.playerHandler.pause()
 | |
|       this.$toast.info(this.$strings.ToastSleepTimerDone)
 | |
|     },
 | |
|     cancelSleepTimer() {
 | |
|       this.showSleepTimerModal = false
 | |
|       this.clearSleepTimer()
 | |
|     },
 | |
|     clearSleepTimer() {
 | |
|       clearInterval(this.sleepTimer)
 | |
|       this.sleepTimerRemaining = 0
 | |
|       this.sleepTimer = null
 | |
|       this.sleepTimerSet = false
 | |
|       this.sleepTimerType = null
 | |
|     },
 | |
|     incrementSleepTimer(amount) {
 | |
|       if (!this.sleepTimerSet) return
 | |
|       this.sleepTimerRemaining += amount
 | |
|     },
 | |
|     decrementSleepTimer(amount) {
 | |
|       if (this.sleepTimerRemaining < amount) {
 | |
|         this.sleepTimerRemaining = 3
 | |
|         return
 | |
|       }
 | |
|       this.sleepTimerRemaining = Math.max(0, this.sleepTimerRemaining - amount)
 | |
|     },
 | |
|     playPause() {
 | |
|       this.playerHandler.playPause()
 | |
|     },
 | |
|     jumpForward() {
 | |
|       this.playerHandler.jumpForward()
 | |
|     },
 | |
|     jumpBackward() {
 | |
|       this.playerHandler.jumpBackward()
 | |
|     },
 | |
|     setVolume(volume) {
 | |
|       this.playerHandler.setVolume(volume)
 | |
|     },
 | |
|     setPlaybackRate(playbackRate) {
 | |
|       this.currentPlaybackRate = playbackRate
 | |
|       this.playerHandler.setPlaybackRate(playbackRate)
 | |
|     },
 | |
|     seek(time) {
 | |
|       this.playerHandler.seek(time)
 | |
|     },
 | |
|     playbackTimeUpdate(time) {
 | |
|       // When updating progress from another session
 | |
|       this.playerHandler.seek(time, false)
 | |
|     },
 | |
|     setCurrentTime(time) {
 | |
|       this.currentTime = time
 | |
|       if (this.$refs.audioPlayer) {
 | |
|         this.$refs.audioPlayer.setCurrentTime(time)
 | |
|       }
 | |
| 
 | |
|       if (this.sleepTimerType === this.$constants.SleepTimerTypes.CHAPTER && this.sleepTimerSet) {
 | |
|         this.checkChapterEnd()
 | |
|       }
 | |
|     },
 | |
|     setDuration(duration) {
 | |
|       this.totalDuration = duration
 | |
|       if (this.$refs.audioPlayer) {
 | |
|         this.$refs.audioPlayer.setDuration(duration)
 | |
|       }
 | |
|     },
 | |
|     setBufferTime(buffertime) {
 | |
|       if (this.$refs.audioPlayer) {
 | |
|         this.$refs.audioPlayer.setBufferTime(buffertime)
 | |
|       }
 | |
|     },
 | |
|     showBookmarks() {
 | |
|       this.bookmarkCurrentTime = this.currentTime
 | |
|       this.showBookmarksModal = true
 | |
|     },
 | |
|     selectBookmark(bookmark) {
 | |
|       this.seek(bookmark.time)
 | |
|       this.showBookmarksModal = false
 | |
|     },
 | |
|     closePlayer() {
 | |
|       this.playerHandler.closePlayer()
 | |
|       this.$store.commit('setMediaPlaying', null)
 | |
|     },
 | |
|     mediaSessionPlay() {
 | |
|       console.log('Media session play')
 | |
|       this.playerHandler.play()
 | |
|     },
 | |
|     mediaSessionPause() {
 | |
|       console.log('Media session pause')
 | |
|       this.playerHandler.pause()
 | |
|     },
 | |
|     mediaSessionStop() {
 | |
|       console.log('Media session stop')
 | |
|       this.playerHandler.pause()
 | |
|     },
 | |
|     mediaSessionSeekBackward() {
 | |
|       console.log('Media session seek backward')
 | |
|       this.playerHandler.jumpBackward()
 | |
|     },
 | |
|     mediaSessionSeekForward() {
 | |
|       console.log('Media session seek forward')
 | |
|       this.playerHandler.jumpForward()
 | |
|     },
 | |
|     mediaSessionSeekTo(e) {
 | |
|       console.log('Media session seek to', e)
 | |
|       if (e.seekTime !== null && !isNaN(e.seekTime)) {
 | |
|         this.playerHandler.seek(e.seekTime)
 | |
|       }
 | |
|     },
 | |
|     mediaSessionPreviousTrack() {
 | |
|       if (this.$refs.audioPlayer) {
 | |
|         this.$refs.audioPlayer.prevChapter()
 | |
|       }
 | |
|     },
 | |
|     mediaSessionNextTrack() {
 | |
|       if (this.$refs.audioPlayer) {
 | |
|         this.$refs.audioPlayer.nextChapter()
 | |
|       }
 | |
|     },
 | |
|     updateMediaSessionPlaybackState() {
 | |
|       if ('mediaSession' in navigator) {
 | |
|         navigator.mediaSession.playbackState = this.isPlaying ? 'playing' : 'paused'
 | |
|       }
 | |
|     },
 | |
|     setMediaSession() {
 | |
|       if (!this.streamLibraryItem) {
 | |
|         console.error('setMediaSession: No library item set')
 | |
|         return
 | |
|       }
 | |
| 
 | |
|       // https://developer.mozilla.org/en-US/docs/Web/API/Media_Session_API
 | |
|       if ('mediaSession' in navigator) {
 | |
|         const chapterInfo = []
 | |
|         if (this.chapters.length) {
 | |
|           this.chapters.forEach((chapter) => {
 | |
|             chapterInfo.push({
 | |
|               title: chapter.title,
 | |
|               startTime: chapter.start
 | |
|             })
 | |
|           })
 | |
|         }
 | |
| 
 | |
|         navigator.mediaSession.metadata = new MediaMetadata({
 | |
|           title: this.title,
 | |
|           artist: this.playerHandler.displayAuthor || this.mediaMetadata.authorName || 'Unknown',
 | |
|           album: this.mediaMetadata.seriesName || '',
 | |
|           artwork: [
 | |
|             {
 | |
|               src: this.$store.getters['globals/getLibraryItemCoverSrc'](this.streamLibraryItem, '/Logo.png', true)
 | |
|             }
 | |
|           ],
 | |
|           chapterInfo
 | |
|         })
 | |
|         console.log('Set media session metadata', navigator.mediaSession.metadata)
 | |
| 
 | |
|         navigator.mediaSession.setActionHandler('play', this.mediaSessionPlay)
 | |
|         navigator.mediaSession.setActionHandler('pause', this.mediaSessionPause)
 | |
|         navigator.mediaSession.setActionHandler('stop', this.mediaSessionStop)
 | |
|         navigator.mediaSession.setActionHandler('seekbackward', this.mediaSessionSeekBackward)
 | |
|         navigator.mediaSession.setActionHandler('seekforward', this.mediaSessionSeekForward)
 | |
|         navigator.mediaSession.setActionHandler('seekto', this.mediaSessionSeekTo)
 | |
|         navigator.mediaSession.setActionHandler('previoustrack', this.mediaSessionSeekBackward)
 | |
|         navigator.mediaSession.setActionHandler('nexttrack', this.mediaSessionSeekForward)
 | |
|       } else {
 | |
|         console.warn('Media session not available')
 | |
|       }
 | |
|     },
 | |
|     streamProgress(data) {
 | |
|       if (this.playerHandler.isPlayingLocalItem && this.playerHandler.currentStreamId === data.stream) {
 | |
|         if (!data.numSegments) return
 | |
|         var chunks = data.chunks
 | |
|         console.log(`[MediaPlayerContainer] Stream Progress ${data.percent}`)
 | |
|         if (this.$refs.audioPlayer) {
 | |
|           this.$refs.audioPlayer.setChunksReady(chunks, data.numSegments)
 | |
|         } else {
 | |
|           console.error('No Audio Ref')
 | |
|         }
 | |
|       }
 | |
|     },
 | |
|     sessionOpen(session) {
 | |
|       // For opening session on init (temporarily unused)
 | |
|       this.$store.commit('setMediaPlaying', {
 | |
|         libraryItem: session.libraryItem,
 | |
|         episodeId: session.episodeId
 | |
|       })
 | |
|       this.playerHandler.prepareOpenSession(session, this.currentPlaybackRate)
 | |
|     },
 | |
|     streamOpen(session) {
 | |
|       console.log(`[MediaPlayerContainer] Stream session open`, session)
 | |
|     },
 | |
|     streamClosed(streamId) {
 | |
|       // Stream was closed from the server
 | |
|       if (this.playerHandler.isPlayingLocalItem && this.playerHandler.currentStreamId === streamId) {
 | |
|         console.warn('[MediaPlayerContainer] Closing stream due to request from server')
 | |
|         this.playerHandler.closePlayer()
 | |
|       }
 | |
|     },
 | |
|     streamReady() {
 | |
|       console.log(`[MediaPlayerContainer] Stream Ready`)
 | |
|       if (this.$refs.audioPlayer) {
 | |
|         this.$refs.audioPlayer.setStreamReady()
 | |
|       } else {
 | |
|         console.error('No Audio Ref')
 | |
|       }
 | |
|     },
 | |
|     streamError(streamId) {
 | |
|       // Stream had critical error from the server
 | |
|       if (this.playerHandler.isPlayingLocalItem && this.playerHandler.currentStreamId === streamId) {
 | |
|         console.warn('[MediaPlayerContainer] Closing stream due to stream error from server')
 | |
|         this.playerHandler.closePlayer()
 | |
|       }
 | |
|     },
 | |
|     streamReset({ startTime, streamId }) {
 | |
|       this.playerHandler.resetStream(startTime, streamId)
 | |
|     },
 | |
|     castSessionActive(isActive) {
 | |
|       if (isActive && this.playerHandler.isPlayingLocalItem) {
 | |
|         // Cast session started switch to cast player
 | |
|         this.playerHandler.switchPlayer()
 | |
|       } else if (!isActive && this.playerHandler.isPlayingCastedItem) {
 | |
|         // Cast session ended switch to local player
 | |
|         this.playerHandler.switchPlayer()
 | |
|       }
 | |
|     },
 | |
|     playNextItemInQueue() {
 | |
|       if (this.hasNextItemInQueue) {
 | |
|         this.playQueueItem({ index: this.currentPlayerQueueIndex + 1 })
 | |
|       }
 | |
|     },
 | |
|     /**
 | |
|      * @param {{ index: number }} payload
 | |
|      */
 | |
|     playQueueItem(payload) {
 | |
|       if (payload?.index === undefined) {
 | |
|         console.error('playQueueItem: No index provided')
 | |
|         return
 | |
|       }
 | |
|       if (!this.playerQueueItems[payload.index]) {
 | |
|         console.error('playQueueItem: No item found at index', payload.index)
 | |
|         return
 | |
|       }
 | |
|       const item = this.playerQueueItems[payload.index]
 | |
|       this.playLibraryItem({
 | |
|         libraryItemId: item.libraryItemId,
 | |
|         episodeId: item.episodeId || null,
 | |
|         queueItems: this.playerQueueItems
 | |
|       })
 | |
|     },
 | |
|     async playLibraryItem(payload) {
 | |
|       const libraryItemId = payload.libraryItemId
 | |
|       const episodeId = payload.episodeId || null
 | |
| 
 | |
|       if (this.playerHandler.libraryItemId == libraryItemId && this.playerHandler.episodeId == episodeId) {
 | |
|         if (payload.startTime !== null && !isNaN(payload.startTime)) {
 | |
|           this.seek(payload.startTime)
 | |
|         } else {
 | |
|           this.playerHandler.play()
 | |
|         }
 | |
|         return
 | |
|       }
 | |
| 
 | |
|       const libraryItem = await this.$axios.$get(`/api/items/${libraryItemId}?expanded=1`).catch((error) => {
 | |
|         console.error('Failed to fetch full item', error)
 | |
|         return null
 | |
|       })
 | |
|       if (!libraryItem) return
 | |
| 
 | |
|       this.$store.commit('setMediaPlaying', {
 | |
|         libraryItem,
 | |
|         episodeId,
 | |
|         queueItems: payload.queueItems || []
 | |
|       })
 | |
|       // Set cover aspect ratio for this item's library since the library may change
 | |
|       this.coverAspectRatio = this.$store.getters['libraries/getBookCoverAspectRatio']
 | |
| 
 | |
|       this.$nextTick(() => {
 | |
|         if (this.$refs.audioPlayer) this.$refs.audioPlayer.checkUpdateChapterTrack()
 | |
|       })
 | |
| 
 | |
|       this.playerHandler.load(libraryItem, episodeId, true, this.currentPlaybackRate, payload.startTime)
 | |
|     },
 | |
|     pauseItem() {
 | |
|       this.playerHandler.pause()
 | |
|     },
 | |
|     showFailedProgressSyncs() {
 | |
|       if (!isNaN(this.syncFailedToast)) this.$toast.dismiss(this.syncFailedToast)
 | |
|       this.syncFailedToast = this.$toast(this.$strings.ToastProgressIsNotBeingSynced, { timeout: false, type: 'error' })
 | |
|     },
 | |
|     sessionClosedEvent(sessionId) {
 | |
|       if (this.playerHandler.currentSessionId === sessionId) {
 | |
|         console.log('sessionClosedEvent closing current session', sessionId)
 | |
|         this.playerHandler.resetPlayer() // Closes player without reporting to server
 | |
|         this.$store.commit('setMediaPlaying', null)
 | |
|       }
 | |
|     }
 | |
|   },
 | |
|   mounted() {
 | |
|     this.$eventBus.$on('cast-session-active', this.castSessionActive)
 | |
|     this.$eventBus.$on('playback-seek', this.seek)
 | |
|     this.$eventBus.$on('playback-time-update', this.playbackTimeUpdate)
 | |
|     this.$eventBus.$on('play-queue-item', this.playQueueItem)
 | |
|     this.$eventBus.$on('play-item', this.playLibraryItem)
 | |
|     this.$eventBus.$on('pause-item', this.pauseItem)
 | |
|   },
 | |
|   beforeDestroy() {
 | |
|     this.$eventBus.$off('cast-session-active', this.castSessionActive)
 | |
|     this.$eventBus.$off('playback-seek', this.seek)
 | |
|     this.$eventBus.$off('playback-time-update', this.playbackTimeUpdate)
 | |
|     this.$eventBus.$off('play-queue-item', this.playQueueItem)
 | |
|     this.$eventBus.$off('play-item', this.playLibraryItem)
 | |
|     this.$eventBus.$off('pause-item', this.pauseItem)
 | |
|   }
 | |
| }
 | |
| </script>
 | |
| 
 | |
| <style>
 | |
| #mediaPlayerContainer {
 | |
|   box-shadow: 0px -6px 8px #1111113f;
 | |
| }
 | |
| </style>
 |