mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-07-07 20:44:46 +02:00
This patch implements a generic way of re-opening the player for the book you last listened to. This is especially handy when first opening the app and you can restart playback with a single click. The player is opened only if no other book is already open in the player. It will not overwrite or replace playbacks or player already in progress. The player is opened paused. No automatic playback since that could be really annoying. This closes #494
309 lines
No EOL
12 KiB
Vue
309 lines
No EOL
12 KiB
Vue
<template>
|
|
<div>
|
|
<app-audio-player ref="audioPlayer" :bookmarks="bookmarks" :sleep-timer-running="isSleepTimerRunning" :sleep-time-remaining="sleepTimeRemaining" :is-server-item="!!serverLibraryItemId" @selectPlaybackSpeed="showPlaybackSpeedModal = true" @updateTime="(t) => (currentTime = t)" @showSleepTimer="showSleepTimer" @showBookmarks="showBookmarks" />
|
|
|
|
<modals-playback-speed-modal v-model="showPlaybackSpeedModal" :playback-rate.sync="playbackSpeed" @update:playbackRate="updatePlaybackSpeed" @change="changePlaybackSpeed" />
|
|
<modals-sleep-timer-modal v-model="showSleepTimerModal" :current-time="sleepTimeRemaining" :sleep-timer-running="isSleepTimerRunning" :current-end-of-chapter-time="currentEndOfChapterTime" :is-auto="isAutoSleepTimer" @change="selectSleepTimeout" @cancel="cancelSleepTimer" @increase="increaseSleepTimer" @decrease="decreaseSleepTimer" />
|
|
<modals-bookmarks-modal v-model="showBookmarksModal" :bookmarks="bookmarks" :current-time="currentTime" :library-item-id="serverLibraryItemId" @select="selectBookmark" />
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { AbsAudioPlayer } from '@/plugins/capacitor'
|
|
import { Dialog } from '@capacitor/dialog'
|
|
|
|
export default {
|
|
data() {
|
|
return {
|
|
isReady: false,
|
|
settingsLoaded: false,
|
|
audioPlayerReady: false,
|
|
stream: null,
|
|
download: null,
|
|
showPlaybackSpeedModal: false,
|
|
showBookmarksModal: false,
|
|
showSleepTimerModal: false,
|
|
playbackSpeed: 1,
|
|
currentTime: 0,
|
|
isSleepTimerRunning: false,
|
|
sleepTimerEndTime: 0,
|
|
sleepTimeRemaining: 0,
|
|
isAutoSleepTimer: false,
|
|
onLocalMediaProgressUpdateListener: null,
|
|
onSleepTimerEndedListener: null,
|
|
onSleepTimerSetListener: null,
|
|
onMediaPlayerChangedListener: null,
|
|
sleepInterval: null,
|
|
currentEndOfChapterTime: 0,
|
|
serverLibraryItemId: null,
|
|
serverEpisodeId: null
|
|
}
|
|
},
|
|
computed: {
|
|
bookmarks() {
|
|
if (!this.serverLibraryItemId) return []
|
|
return this.$store.getters['user/getUserBookmarksForItem'](this.serverLibraryItemId)
|
|
}
|
|
},
|
|
methods: {
|
|
showBookmarks() {
|
|
this.showBookmarksModal = true
|
|
},
|
|
selectBookmark(bookmark) {
|
|
this.showBookmarksModal = false
|
|
if (!bookmark || isNaN(bookmark.time)) return
|
|
const bookmarkTime = Number(bookmark.time)
|
|
if (this.$refs.audioPlayer) {
|
|
this.$refs.audioPlayer.seek(bookmarkTime)
|
|
}
|
|
},
|
|
onSleepTimerEnded({ value: currentPosition }) {
|
|
this.isSleepTimerRunning = false
|
|
if (currentPosition) {
|
|
console.log('Sleep Timer Ended Current Position: ' + currentPosition)
|
|
}
|
|
},
|
|
onSleepTimerSet(payload) {
|
|
const { value: sleepTimeRemaining, isAuto } = payload
|
|
console.log('SLEEP TIMER SET', JSON.stringify(payload))
|
|
if (sleepTimeRemaining === 0) {
|
|
console.log('Sleep timer canceled')
|
|
this.isSleepTimerRunning = false
|
|
} else {
|
|
this.isSleepTimerRunning = true
|
|
}
|
|
|
|
this.isAutoSleepTimer = !!isAuto
|
|
this.sleepTimeRemaining = sleepTimeRemaining
|
|
},
|
|
showSleepTimer() {
|
|
if (this.$refs.audioPlayer && this.$refs.audioPlayer.currentChapter) {
|
|
this.currentEndOfChapterTime = Math.floor(this.$refs.audioPlayer.currentChapter.end)
|
|
} else {
|
|
this.currentEndOfChapterTime = 0
|
|
}
|
|
this.showSleepTimerModal = true
|
|
},
|
|
async selectSleepTimeout({ time, isChapterTime }) {
|
|
console.log('Setting sleep timer', time, isChapterTime)
|
|
var res = await AbsAudioPlayer.setSleepTimer({ time: String(time), isChapterTime })
|
|
if (!res.success) {
|
|
return this.$toast.error('Sleep timer did not set, invalid time')
|
|
}
|
|
},
|
|
increaseSleepTimer() {
|
|
// Default time to increase = 5 min
|
|
AbsAudioPlayer.increaseSleepTime({ time: '300000' })
|
|
},
|
|
decreaseSleepTimer() {
|
|
AbsAudioPlayer.decreaseSleepTime({ time: '300000' })
|
|
},
|
|
async cancelSleepTimer() {
|
|
console.log('Canceling sleep timer')
|
|
await AbsAudioPlayer.cancelSleepTimer()
|
|
},
|
|
streamClosed() {
|
|
console.log('Stream Closed')
|
|
},
|
|
streamProgress(data) {
|
|
if (!data.numSegments) return
|
|
var chunks = data.chunks
|
|
if (this.$refs.audioPlayer) {
|
|
this.$refs.audioPlayer.setChunksReady(chunks, data.numSegments)
|
|
}
|
|
},
|
|
streamReady() {
|
|
console.log('[StreamContainer] Stream Ready')
|
|
if (this.$refs.audioPlayer) {
|
|
this.$refs.audioPlayer.setStreamReady()
|
|
}
|
|
},
|
|
streamReset({ streamId, startTime }) {
|
|
console.log('received stream reset', streamId, startTime)
|
|
if (this.$refs.audioPlayer) {
|
|
if (this.stream && this.stream.id === streamId) {
|
|
this.$refs.audioPlayer.resetStream(startTime)
|
|
}
|
|
}
|
|
},
|
|
updatePlaybackSpeed(speed) {
|
|
if (this.$refs.audioPlayer) {
|
|
console.log(`[AudioPlayerContainer] Update Playback Speed: ${speed}`)
|
|
this.$refs.audioPlayer.setPlaybackSpeed(speed)
|
|
}
|
|
},
|
|
changePlaybackSpeed(speed) {
|
|
console.log(`[AudioPlayerContainer] Change Playback Speed: ${speed}`)
|
|
this.$store.dispatch('user/updateUserSettings', { playbackRate: speed })
|
|
},
|
|
settingsUpdated(settings) {
|
|
console.log(`[AudioPlayerContainer] Settings Update | PlaybackRate: ${settings.playbackRate}`)
|
|
this.playbackSpeed = settings.playbackRate
|
|
if (this.$refs.audioPlayer && this.$refs.audioPlayer.currentPlaybackRate !== settings.playbackRate) {
|
|
console.log(`[AudioPlayerContainer] PlaybackRate Updated: ${this.playbackSpeed}`)
|
|
this.$refs.audioPlayer.setPlaybackSpeed(this.playbackSpeed)
|
|
}
|
|
|
|
// Settings have been loaded (at least once, so it's safe to kickoff onReady)
|
|
if (!this.settingsLoaded) {
|
|
this.settingsLoaded = true
|
|
this.notifyOnReady()
|
|
}
|
|
},
|
|
closeStreamOnly() {
|
|
// If user logs out or disconnects from server and not playing local
|
|
if (this.$refs.audioPlayer && !this.$refs.audioPlayer.isLocalPlayMethod) {
|
|
this.$refs.audioPlayer.closePlayback()
|
|
}
|
|
},
|
|
castLocalItem() {
|
|
if (!this.serverLibraryItemId) {
|
|
this.$toast.error(`Cannot cast locally downloaded media`)
|
|
} else {
|
|
// Change to server library item
|
|
this.playServerLibraryItemAndCast(this.serverLibraryItemId, this.serverEpisodeId)
|
|
}
|
|
},
|
|
playServerLibraryItemAndCast(libraryItemId, episodeId) {
|
|
var playbackRate = 1
|
|
if (this.$refs.audioPlayer) {
|
|
playbackRate = this.$refs.audioPlayer.currentPlaybackRate || 1
|
|
}
|
|
AbsAudioPlayer.prepareLibraryItem({ libraryItemId, episodeId, playWhenReady: false, playbackRate })
|
|
.then((data) => {
|
|
if (data.error) {
|
|
const errorMsg = data.error || 'Failed to play'
|
|
this.$toast.error(errorMsg)
|
|
} else {
|
|
console.log('Library item play response', JSON.stringify(data))
|
|
AbsAudioPlayer.requestSession()
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.error('Failed', error)
|
|
this.$toast.error('Failed to play')
|
|
})
|
|
},
|
|
async playLibraryItem(payload) {
|
|
const libraryItemId = payload.libraryItemId
|
|
const episodeId = payload.episodeId
|
|
const startTime = payload.startTime
|
|
const startWhenReady = !payload.paused
|
|
|
|
// When playing local library item and can also play this item from the server
|
|
// then store the server library item id so it can be used if a cast is made
|
|
const serverLibraryItemId = payload.serverLibraryItemId || null
|
|
const serverEpisodeId = payload.serverEpisodeId || null
|
|
|
|
if (libraryItemId.startsWith('local') && this.$store.state.isCasting) {
|
|
const { value } = await Dialog.confirm({
|
|
title: 'Warning',
|
|
message: `Cannot cast downloaded media items. Confirm to close cast and play on your device.`
|
|
})
|
|
if (!value) {
|
|
return
|
|
}
|
|
}
|
|
|
|
// if already playing this item then jump to start time
|
|
if (this.$store.getters['getIsMediaStreaming'](libraryItemId, episodeId)) {
|
|
console.log('Already streaming item', startTime)
|
|
if (startTime !== undefined && startTime !== null) {
|
|
// seek to start time
|
|
AbsAudioPlayer.seek({ value: Math.floor(startTime) })
|
|
}
|
|
return
|
|
}
|
|
|
|
this.serverLibraryItemId = null
|
|
this.serverEpisodeId = null
|
|
|
|
let playbackRate = 1
|
|
if (this.$refs.audioPlayer) {
|
|
playbackRate = this.$refs.audioPlayer.currentPlaybackRate || 1
|
|
}
|
|
|
|
console.log('Called playLibraryItem', libraryItemId)
|
|
const preparePayload = { libraryItemId, episodeId, playWhenReady: startWhenReady, playbackRate }
|
|
if (startTime !== undefined && startTime !== null) preparePayload.startTime = startTime
|
|
AbsAudioPlayer.prepareLibraryItem(preparePayload)
|
|
.then((data) => {
|
|
if (data.error) {
|
|
const errorMsg = data.error || 'Failed to play'
|
|
this.$toast.error(errorMsg)
|
|
} else {
|
|
console.log('Library item play response', JSON.stringify(data))
|
|
if (!libraryItemId.startsWith('local')) {
|
|
this.serverLibraryItemId = libraryItemId
|
|
} else {
|
|
this.serverLibraryItemId = serverLibraryItemId
|
|
}
|
|
if (episodeId && !episodeId.startsWith('local')) {
|
|
this.serverEpisodeId = episodeId
|
|
} else {
|
|
this.serverEpisodeId = serverEpisodeId
|
|
}
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.error('Failed', error)
|
|
this.$toast.error('Failed to play')
|
|
})
|
|
},
|
|
pauseItem() {
|
|
if (this.$refs.audioPlayer && !this.$refs.audioPlayer.isPaused) {
|
|
this.$refs.audioPlayer.pause()
|
|
}
|
|
},
|
|
onLocalMediaProgressUpdate(localMediaProgress) {
|
|
console.log('Got local media progress update', localMediaProgress.progress, JSON.stringify(localMediaProgress))
|
|
this.$store.commit('globals/updateLocalMediaProgress', localMediaProgress)
|
|
},
|
|
onMediaPlayerChanged(data) {
|
|
var mediaPlayer = data.value
|
|
this.$store.commit('setMediaPlayer', mediaPlayer)
|
|
},
|
|
onReady() {
|
|
// The UI is reporting elsewhere we are ready
|
|
this.isReady = true
|
|
this.notifyOnReady()
|
|
},
|
|
notifyOnReady() {
|
|
// If settings aren't loaded yet, native player will receive incorrect settings
|
|
console.log('Notify on ready... settingsLoaded:', this.settingsLoaded, 'isReady:', this.isReady)
|
|
if (this.settingsLoaded && this.isReady) {
|
|
AbsAudioPlayer.onReady()
|
|
}
|
|
}
|
|
},
|
|
mounted() {
|
|
this.onLocalMediaProgressUpdateListener = AbsAudioPlayer.addListener('onLocalMediaProgressUpdate', this.onLocalMediaProgressUpdate)
|
|
this.onSleepTimerEndedListener = AbsAudioPlayer.addListener('onSleepTimerEnded', this.onSleepTimerEnded)
|
|
this.onSleepTimerSetListener = AbsAudioPlayer.addListener('onSleepTimerSet', this.onSleepTimerSet)
|
|
this.onMediaPlayerChangedListener = AbsAudioPlayer.addListener('onMediaPlayerChanged', this.onMediaPlayerChanged)
|
|
|
|
this.playbackSpeed = this.$store.getters['user/getUserSetting']('playbackRate')
|
|
console.log(`[AudioPlayerContainer] Init Playback Speed: ${this.playbackSpeed}`)
|
|
|
|
this.$eventBus.$on('abs-ui-ready', this.onReady)
|
|
this.$eventBus.$on('play-item', this.playLibraryItem)
|
|
this.$eventBus.$on('pause-item', this.pauseItem)
|
|
this.$eventBus.$on('close-stream', this.closeStreamOnly)
|
|
this.$eventBus.$on('cast-local-item', this.castLocalItem)
|
|
this.$eventBus.$on('user-settings', this.settingsUpdated)
|
|
},
|
|
beforeDestroy() {
|
|
if (this.onLocalMediaProgressUpdateListener) this.onLocalMediaProgressUpdateListener.remove()
|
|
if (this.onSleepTimerEndedListener) this.onSleepTimerEndedListener.remove()
|
|
if (this.onSleepTimerSetListener) this.onSleepTimerSetListener.remove()
|
|
if (this.onMediaPlayerChangedListener) this.onMediaPlayerChangedListener.remove()
|
|
|
|
this.$eventBus.$off('abs-ui-ready', this.onReady)
|
|
this.$eventBus.$off('play-item', this.playLibraryItem)
|
|
this.$eventBus.$off('pause-item', this.pauseItem)
|
|
this.$eventBus.$off('close-stream', this.closeStreamOnly)
|
|
this.$eventBus.$off('cast-local-item', this.castLocalItem)
|
|
this.$eventBus.$off('user-settings', this.settingsUpdated)
|
|
}
|
|
}
|
|
</script> |