mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-08-04 10:04:39 +02:00
Offline support, downloading, syncing progress
This commit is contained in:
parent
e97218f2e8
commit
a412c9d359
37 changed files with 2836 additions and 201 deletions
|
@ -1,24 +1,28 @@
|
|||
<template>
|
||||
<div class="w-full min-h-screen h-full bg-bg text-white">
|
||||
<app-appbar />
|
||||
<div id="content" class="overflow-hidden" :class="streaming ? 'streaming' : ''">
|
||||
<div id="content" class="overflow-hidden" :class="playerIsOpen ? 'playerOpen' : ''">
|
||||
<Nuxt />
|
||||
</div>
|
||||
<app-stream-container ref="streamContainer" />
|
||||
<modals-downloads-modal ref="downloadsModal" @selectDownload="selectDownload" @deleteDownload="deleteDownload" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Path from 'path'
|
||||
import { Capacitor } from '@capacitor/core'
|
||||
import { Network } from '@capacitor/network'
|
||||
import { AppUpdate } from '@robingenz/capacitor-app-update'
|
||||
import AudioDownloader from '@/plugins/audio-downloader'
|
||||
|
||||
export default {
|
||||
middleware: 'authenticated',
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
streaming() {
|
||||
return this.$store.state.streamAudiobook
|
||||
playerIsOpen() {
|
||||
return this.$store.getters['playerIsOpen']
|
||||
},
|
||||
routeName() {
|
||||
return this.$route.name
|
||||
|
@ -30,11 +34,50 @@ export default {
|
|||
if (isConnected) {
|
||||
this.$router.push('/')
|
||||
}
|
||||
} else {
|
||||
if (!isConnected) {
|
||||
this.$router.push('/connect')
|
||||
}
|
||||
this.syncUserProgress()
|
||||
},
|
||||
updateAudiobookProgressOnServer(audiobookProgress) {
|
||||
if (this.$server.socket) {
|
||||
this.$server.socket.emit('progress_update', audiobookProgress)
|
||||
}
|
||||
},
|
||||
syncUserProgress() {
|
||||
if (!this.$store.state.user.user) return
|
||||
|
||||
var userAudiobooks = this.$store.state.user.user.audiobooks
|
||||
var localAudiobooks = this.$store.state.user.localUserAudiobooks
|
||||
var localHasUpdates = false
|
||||
|
||||
var newestLocal = { ...localAudiobooks }
|
||||
for (const audiobookId in userAudiobooks) {
|
||||
if (localAudiobooks[audiobookId]) {
|
||||
if (localAudiobooks[audiobookId].lastUpdate > userAudiobooks[audiobookId].lastUpdate) {
|
||||
// Local progress is more recent than user progress
|
||||
this.updateAudiobookProgressOnServer(localAudiobooks[audiobookId])
|
||||
} else {
|
||||
// Server is more recent than local
|
||||
newestLocal[audiobookId] = userAudiobooks[audiobookId]
|
||||
localHasUpdates = true
|
||||
}
|
||||
} else {
|
||||
// Not on local yet - store on local
|
||||
newestLocal[audiobookId] = userAudiobooks[audiobookId]
|
||||
localHasUpdates = true
|
||||
}
|
||||
}
|
||||
|
||||
for (const audiobookId in localAudiobooks) {
|
||||
if (!userAudiobooks[audiobookId]) {
|
||||
// Local progress is not on server
|
||||
this.updateAudiobookProgressOnServer(localAudiobooks[audiobookId])
|
||||
}
|
||||
}
|
||||
|
||||
if (localHasUpdates) {
|
||||
console.log('Local audiobook progress has updates from server')
|
||||
this.$localStore.setAllAudiobookProgress(newestLocal)
|
||||
}
|
||||
},
|
||||
initialStream(stream) {
|
||||
if (this.$refs.streamContainer && this.$refs.streamContainer.audioPlayerReady) {
|
||||
|
@ -49,16 +92,16 @@ export default {
|
|||
await AppUpdate.openAppStore()
|
||||
}
|
||||
},
|
||||
showUpdateToast(availableVersion, immediateUpdateAllowed) {
|
||||
var toastText = immediateUpdateAllowed ? `Click here to update` : `Click here to open app store`
|
||||
this.$toast.info(`Update is available for v${availableVersion}! ${toastText}`, {
|
||||
draggable: false,
|
||||
hideProgressBar: false,
|
||||
timeout: 10000,
|
||||
closeButton: false,
|
||||
onClick: this.clickUpdateToast()
|
||||
})
|
||||
},
|
||||
// showUpdateToast(availableVersion, immediateUpdateAllowed) {
|
||||
// var toastText = immediateUpdateAllowed ? `Click here to update` : `Click here to open app store`
|
||||
// this.$toast.info(`Update is available for v${availableVersion}! ${toastText}`, {
|
||||
// draggable: false,
|
||||
// hideProgressBar: false,
|
||||
// timeout: 10000,
|
||||
// closeButton: false,
|
||||
// onClick: this.clickUpdateToast()
|
||||
// })
|
||||
// },
|
||||
async checkForUpdate() {
|
||||
const result = await AppUpdate.getAppUpdateInfo()
|
||||
if (!result) {
|
||||
|
@ -66,13 +109,165 @@ export default {
|
|||
return
|
||||
}
|
||||
this.$store.commit('setAppUpdateInfo', result)
|
||||
if (result.updateAvailability === 2) {
|
||||
setTimeout(() => {
|
||||
this.$toast.info(`Update is available!`, {
|
||||
draggable: false,
|
||||
hideProgressBar: false,
|
||||
timeout: 4000,
|
||||
closeButton: false,
|
||||
onClick: this.clickUpdateToast()
|
||||
})
|
||||
// this.showUpdateToast(result.availableVersion, !!result.immediateUpdateAllowed)
|
||||
}, 5000)
|
||||
}
|
||||
},
|
||||
onDownloadProgress(data) {
|
||||
// var downloadId = data.downloadId
|
||||
var progress = data.progress
|
||||
var filename = data.filename
|
||||
var audiobookId = filename ? Path.basename(filename, Path.extname(filename)) : ''
|
||||
|
||||
// if (result.updateAvailability === 2) {
|
||||
// setTimeout(() => {
|
||||
// this.showUpdateToast(result.availableVersion, !!result.immediateUpdateAllowed)
|
||||
// }, 5000)
|
||||
// }
|
||||
}
|
||||
var downloadObj = this.$store.getters['downloads/getDownload'](audiobookId)
|
||||
if (downloadObj) {
|
||||
if (this.$refs.downloadsModal) {
|
||||
this.$refs.downloadsModal.updateDownloadProgress({ audiobookId, progress })
|
||||
}
|
||||
this.$toast.update(downloadObj.toastId, { content: `${progress}% Downloading ${downloadObj.audiobook.book.title}` })
|
||||
}
|
||||
},
|
||||
onDownloadComplete(data) {
|
||||
var downloadId = data.downloadId
|
||||
var contentUrl = data.contentUrl
|
||||
var filename = data.filename
|
||||
var audiobookId = filename ? Path.basename(filename, Path.extname(filename)) : ''
|
||||
|
||||
if (audiobookId) {
|
||||
// Notify server to remove prepared download
|
||||
if (this.$server.socket) {
|
||||
this.$server.socket.emit('remove_download', downloadId)
|
||||
}
|
||||
|
||||
console.log('Download complete', filename, downloadId, contentUrl, 'AudiobookId:', audiobookId)
|
||||
var downloadObj = this.$store.getters['downloads/getDownload'](audiobookId)
|
||||
if (!downloadObj) {
|
||||
console.error('Could not find download...')
|
||||
} else {
|
||||
this.$toast.update(downloadObj.toastId, { content: `Success! ${downloadObj.audiobook.book.title} downloaded.`, options: { timeout: 5000, type: 'success' } }, true)
|
||||
|
||||
console.log('Found download, update with content url')
|
||||
delete downloadObj.isDownloading
|
||||
delete downloadObj.isPreparing
|
||||
downloadObj.contentUrl = contentUrl
|
||||
this.$store.commit('downloads/addUpdateDownload', downloadObj)
|
||||
}
|
||||
}
|
||||
},
|
||||
async checkLoadCurrent() {
|
||||
var currentObj = await this.$localStore.getCurrent()
|
||||
if (!currentObj) return
|
||||
|
||||
console.log('Has Current playing', currentObj.audiobookId)
|
||||
var download = this.$store.getters['downloads/getDownload'](currentObj.audiobookId)
|
||||
if (download) {
|
||||
this.$store.commit('setPlayingDownload', download)
|
||||
} else {
|
||||
console.warn('Download not available for previous current playing', currentObj.audiobookId)
|
||||
this.$localStore.setCurrent(null)
|
||||
}
|
||||
},
|
||||
onMediaLoaded(items) {
|
||||
var jsitems = JSON.parse(items)
|
||||
jsitems = jsitems.map((item) => {
|
||||
return {
|
||||
id: item.id,
|
||||
size: item.size,
|
||||
duration: item.duration,
|
||||
filename: item.name,
|
||||
audiobookId: item.name ? Path.basename(item.name, Path.extname(item.name)) : '',
|
||||
contentUrl: item.uri.replace(/\\\//g, '/'),
|
||||
coverUrl: item.coverUrl || null
|
||||
}
|
||||
})
|
||||
jsitems.forEach((item) => {
|
||||
var download = this.$store.getters['downloads/getDownload'](item.audiobookId)
|
||||
if (!download) {
|
||||
console.error(`Unknown media item found for filename ${item.filename}`)
|
||||
|
||||
var orphanDownload = {
|
||||
id: `orphan-${item.id}`,
|
||||
contentUrl: item.contentUrl,
|
||||
coverUrl: item.coverUrl,
|
||||
cover: item.coverUrl ? Capacitor.convertFileSrc(item.coverUrl) : null,
|
||||
mediaId: item.id,
|
||||
filename: item.filename,
|
||||
size: item.size,
|
||||
duration: item.duration,
|
||||
isOrphan: true
|
||||
}
|
||||
this.$store.commit('downloads/addUpdateDownload', orphanDownload)
|
||||
} else {
|
||||
console.log(`Found media item for audiobook ${download.audiobook.book.title} (${item.audiobookId})`)
|
||||
download.contentUrl = item.contentUrl
|
||||
download.coverUrl = item.coverUrl
|
||||
download.cover = item.coverUrl ? Capacitor.convertFileSrc(item.coverUrl) : null
|
||||
download.size = item.size
|
||||
download.duration = item.duration
|
||||
this.$store.commit('downloads/addUpdateDownload', download)
|
||||
|
||||
download.audiobook.isDownloaded = true
|
||||
this.$store.commit('audiobooks/addUpdate', download.audiobook)
|
||||
}
|
||||
})
|
||||
|
||||
var downloads = this.$store.state.downloads.downloads
|
||||
|
||||
downloads.forEach((download) => {
|
||||
var matchingItem = jsitems.find((item) => item.audiobookId === download.id)
|
||||
if (!matchingItem) {
|
||||
console.error(`Saved download not in media store ${download.audiobook.book.title} (${download.id})`)
|
||||
this.$store.commit('downloads/removeDownload', download)
|
||||
}
|
||||
})
|
||||
|
||||
this.checkLoadCurrent()
|
||||
},
|
||||
selectDownload(download) {
|
||||
this.$store.commit('setPlayOnLoad', true)
|
||||
this.$store.commit('setPlayingDownload', download)
|
||||
},
|
||||
async deleteDownload(download) {
|
||||
console.log('Delete download', download.filename)
|
||||
|
||||
if (this.$store.state.playingDownload && this.$store.state.playingDownload.id === download.id) {
|
||||
console.warn('Deleting download when currently playing download - terminate play')
|
||||
if (this.$refs.streamContainer) {
|
||||
this.$refs.streamContainer.cancelStream()
|
||||
}
|
||||
}
|
||||
|
||||
if (download.contentUrl) {
|
||||
await AudioDownloader.delete({ filename: download.filename, url: download.contentUrl, coverUrl: download.coverUrl })
|
||||
}
|
||||
this.$store.commit('downloads/removeDownload', download)
|
||||
},
|
||||
async initMediaStore() {
|
||||
// Load local database of downloads
|
||||
await this.$store.dispatch('downloads/loadFromStorage')
|
||||
await this.$localStore.loadUserAudiobooks()
|
||||
|
||||
// Request and setup listeners for media files on native
|
||||
AudioDownloader.addListener('onDownloadComplete', (data) => {
|
||||
this.onDownloadComplete(data)
|
||||
})
|
||||
AudioDownloader.addListener('onMediaLoaded', (data) => {
|
||||
this.onMediaLoaded(data.items)
|
||||
})
|
||||
AudioDownloader.addListener('onDownloadProgress', (data) => {
|
||||
this.onDownloadProgress(data)
|
||||
})
|
||||
AudioDownloader.load()
|
||||
},
|
||||
// parseSemver(ver) {
|
||||
// if (!ver) return null
|
||||
// var groups = ver.match(/^v((([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)$/)
|
||||
|
@ -130,6 +325,16 @@ export default {
|
|||
// this.$store.commit('setHasUpdate', true)
|
||||
// }
|
||||
// }
|
||||
async setupNetworkListener() {
|
||||
var status = await Network.getStatus()
|
||||
console.log('Network status', status.connected, status.connectionType)
|
||||
this.$store.commit('setNetworkStatus', status)
|
||||
|
||||
Network.addListener('networkStatusChange', (status) => {
|
||||
console.log('Network status changed', status.connected, status.connectionType)
|
||||
this.$store.commit('setNetworkStatus', status)
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (!this.$server) return console.error('No Server')
|
||||
|
@ -137,16 +342,9 @@ export default {
|
|||
this.$server.on('connected', this.connected)
|
||||
this.$server.on('initialStream', this.initialStream)
|
||||
|
||||
if (!this.$server.connected) {
|
||||
this.$router.push('/connect')
|
||||
}
|
||||
|
||||
this.setupNetworkListener()
|
||||
this.checkForUpdate()
|
||||
|
||||
// var checkForUpdateFlag = localStorage.getItem('checkForUpdate')
|
||||
// if (!checkForUpdateFlag || checkForUpdateFlag !== '1') {
|
||||
// this.checkForUpdate()
|
||||
// }
|
||||
this.initMediaStore()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -155,7 +353,8 @@ export default {
|
|||
#content {
|
||||
height: calc(100vh - 64px);
|
||||
}
|
||||
#content.streaming {
|
||||
height: calc(100vh - 204px);
|
||||
#content.playerOpen {
|
||||
/* height: calc(100vh - 204px); */
|
||||
height: calc(100vh - 240px);
|
||||
}
|
||||
</style>
|
Loading…
Add table
Add a link
Reference in a new issue