This commit is contained in:
Mark Cooper
2021-09-01 20:07:11 -05:00
commit 0fef23e47b
106 changed files with 18798 additions and 0 deletions
+74
View File
@@ -0,0 +1,74 @@
<template>
<div class="w-full h-16 bg-primary relative">
<div id="appbar" class="absolute top-0 left-0 w-full h-full z-30 flex items-center px-2">
<nuxt-link v-show="!showBack" to="/" class="mr-4">
<img src="/Logo.png" class="h-12 w-12" />
</nuxt-link>
<a v-if="showBack" @click="back" class="rounded-full h-12 w-12 flex items-center justify-center hover:bg-white hover:bg-opacity-10 mr-2 cursor-pointer">
<span class="material-icons text-4xl text-white">arrow_back</span>
</a>
<p class="text-xl font-book">AudioBookshelf</p>
<div class="flex-grow" />
<ui-menu :label="username" :items="menuItems" @action="menuAction" class="ml-5" />
</div>
</div>
</template>
<script>
export default {
data() {
return {
menuItems: [
{
value: 'account',
text: 'Account',
to: '/account'
},
{
value: 'logout',
text: 'Logout'
}
]
}
},
computed: {
showBack() {
return this.$route.name !== 'index'
},
user() {
return this.$store.state.user.user
},
username() {
return this.user ? this.user.username : 'err'
}
},
methods: {
back() {
if (this.$route.name === 'audiobook-id-edit') {
this.$router.push(`/audiobook/${this.$route.params.id}`)
} else {
this.$router.push('/')
}
},
logout() {
this.$axios.$post('/logout').catch((error) => {
console.error(error)
})
this.$server.logout()
this.$router.push('/connect')
},
menuAction(action) {
if (action === 'logout') {
this.logout()
}
}
},
mounted() {}
}
</script>
<style>
#appbar {
box-shadow: 0px 5px 5px #11111155;
}
</style>
+116
View File
@@ -0,0 +1,116 @@
<template>
<div id="bookshelf" ref="wrapper" class="w-full overflow-y-auto">
<template v-for="(shelf, index) in groupedBooks">
<div :key="index" class="border-b border-opacity-10 w-full bookshelfRow py-4 flex justify-around relative">
<template v-for="audiobook in shelf">
<div :key="audiobook.id" class="relative px-4">
<nuxt-link :to="`/audiobook/${audiobook.id}`">
<cards-book-cover :audiobook="audiobook" :width="cardWidth" class="mx-auto -mb-px" />
</nuxt-link>
</div>
</template>
<div class="bookshelfDivider h-4 w-full absolute bottom-0 left-0 right-0 z-10" />
</div>
</template>
<div class="w-full py-16 text-center text-xl">
<div class="py-4">No Audiobooks</div>
<ui-btn v-if="hasFilters" @click="clearFilter">Clear Filter</ui-btn>
</div>
</div>
</template>
<script>
export default {
data() {
return {
currFilterOrderKey: null,
groupedBooks: [],
pageWidth: 0
}
},
computed: {
cardWidth() {
return 140
},
cardHeight() {
return this.cardWidth * 2
},
filterOrderKey() {
return this.$store.getters['user/getFilterOrderKey']
},
hasFilters() {
return this.$store.getters['user/getUserSetting']('filterBy') !== 'all'
}
},
methods: {
clearFilter() {
this.$store.dispatch('user/updateUserSettings', {
filterBy: 'all'
})
},
playAudiobook(audiobook) {
console.log('Play Audiobook', audiobook)
this.$store.commit('setStreamAudiobook', audiobook)
this.$server.socket.emit('open_stream', audiobook.id)
},
calcShelves() {
var booksPerShelf = Math.floor(this.pageWidth / (this.cardWidth + 32))
var groupedBooks = []
var audiobooksSorted = this.$store.getters['audiobooks/getFilteredAndSorted']()
this.currFilterOrderKey = this.filterOrderKey
var numGroups = Math.ceil(audiobooksSorted.length / booksPerShelf)
for (let i = 0; i < numGroups; i++) {
var group = audiobooksSorted.slice(i * booksPerShelf, i * booksPerShelf + 2)
groupedBooks.push(group)
}
this.groupedBooks = groupedBooks
},
audiobooksUpdated() {
this.calcShelves()
},
init() {
if (this.$refs.wrapper) {
this.pageWidth = this.$refs.wrapper.clientWidth
this.calcShelves()
}
},
resize() {
this.init()
},
settingsUpdated() {
if (this.currFilterOrderKey !== this.filterOrderKey) {
this.calcShelves()
}
}
},
mounted() {
this.$store.commit('audiobooks/addListener', { id: 'bookshelf', meth: this.audiobooksUpdated })
this.$store.commit('user/addSettingsListener', { id: 'bookshelf', meth: this.settingsUpdated })
window.addEventListener('resize', this.resize)
this.$store.dispatch('audiobooks/load')
this.init()
},
beforeDestroy() {
this.$store.commit('audiobooks/removeListener', 'bookshelf')
this.$store.commit('user/removeSettingsListener', 'bookshelf')
window.removeEventListener('resize', this.resize)
}
}
</script>
<style>
#bookshelf {
height: calc(100% - 48px);
}
.bookshelfRow {
background-image: url(/wood_panels.jpg);
}
.bookshelfDivider {
background: rgb(149, 119, 90);
background: linear-gradient(180deg, rgba(149, 119, 90, 1) 0%, rgba(103, 70, 37, 1) 17%, rgba(103, 70, 37, 1) 88%, rgba(71, 48, 25, 1) 100%);
box-shadow: 2px 14px 8px #111111aa;
}
</style>
+187
View File
@@ -0,0 +1,187 @@
<template>
<div class="w-full p-4 pointer-events-none fixed bottom-0 left-0 right-0 z-20">
<div v-if="streamAudiobook" class="w-full bg-primary absolute bottom-0 left-0 right-0 z-50 p-2 pointer-events-auto" @click.stop @mousedown.stop @mouseup.stop>
<div class="pl-16 pr-2 flex items-center pb-2">
<div>
<p class="px-2">{{ title }}</p>
<p class="px-2 text-xs text-gray-400">by {{ author }}</p>
</div>
<div class="flex-grow" />
<span class="material-icons" @click="cancelStream">close</span>
</div>
<div class="absolute left-2 -top-10">
<cards-book-cover :audiobook="streamAudiobook" :width="64" />
</div>
<audio-player-mini ref="audioPlayerMini" :loading="!stream || currStreamAudiobookId !== streamAudiobookId" @updateTime="updateTime" @hook:mounted="audioPlayerMounted" />
</div>
</div>
</template>
<script>
import { Dialog } from '@capacitor/dialog'
export default {
data() {
return {
audioPlayerReady: false,
stream: null,
lastServerUpdateSentSeconds: 0
}
},
computed: {
streamAudiobook() {
return this.$store.state.streamAudiobook
},
streamAudiobookId() {
return this.streamAudiobook ? this.streamAudiobook.id : null
},
currStreamAudiobookId() {
return this.stream ? this.stream.audiobook.id : null
},
book() {
return this.streamAudiobook ? this.streamAudiobook.book || {} : {}
},
title() {
return this.book ? this.book.title : ''
},
author() {
return this.book ? this.book.author : ''
},
cover() {
return this.book ? this.book.cover : ''
},
series() {
return this.book ? this.book.series : ''
},
volumeNumber() {
return this.book ? this.book.volumeNumber : ''
},
seriesTxt() {
if (!this.series) return ''
if (!this.volumeNumber) return this.series
return `${this.series} #${this.volumeNumber}`
},
duration() {
return this.streamAudiobook ? this.streamAudiobook.duration || 0 : 0
},
coverForNative() {
if (!this.cover) {
return `${this.$store.state.serverUrl}/Logo.png`
}
if (this.cover.startsWith('http')) return this.cover
var _clean = this.cover.replace(/\\/g, '/')
if (_clean.startsWith('/local')) {
var _cover = process.env.NODE_ENV !== 'production' && process.env.PROD !== '1' ? _clean.replace('/local', '') : _clean
return `${this.$store.state.serverUrl}${_cover}`
}
return _clean
}
},
methods: {
async cancelStream() {
const { value } = await Dialog.confirm({
title: 'Confirm',
message: 'Cancel this stream?'
})
if (value) {
this.$server.socket.emit('close_stream')
}
},
updateTime(currentTime) {
var diff = currentTime - this.lastServerUpdateSentSeconds
if (diff > 4 || diff < 0) {
this.lastServerUpdateSentSeconds = currentTime
var updatePayload = {
currentTime,
streamId: this.stream.id
}
this.$server.socket.emit('stream_update', updatePayload)
}
},
closeStream() {},
streamClosed(audiobookId) {
console.log('Stream Closed')
if (this.stream.audiobook.id === audiobookId || audiobookId === 'n/a') {
this.$store.commit('setStreamAudiobook', null)
}
},
streamProgress(data) {
if (!data.numSegments) return
var chunks = data.chunks
if (this.$refs.audioPlayerMini) {
this.$refs.audioPlayerMini.setChunksReady(chunks, data.numSegments)
}
},
streamReady() {
console.log('[StreamContainer] Stream Ready')
if (this.$refs.audioPlayerMini) {
this.$refs.audioPlayerMini.setStreamReady()
}
},
streamReset({ streamId, startTime }) {
if (this.$refs.audioPlayerMini) {
if (this.stream && this.stream.id === streamId) {
this.$refs.audioPlayerMini.terminateStream(startTime)
}
}
},
streamOpen(stream) {
console.log('[StreamContainer] Stream Open', stream)
if (!this.$refs.audioPlayerMini) {
console.error('No Audio Player Mini')
return
}
if (this.stream && this.stream.id !== stream.id) {
console.error('STREAM CHANGED', this.stream.id, stream.id)
}
this.stream = stream
var playlistUrl = stream.clientPlaylistUri
var currentTime = stream.clientCurrentTime || 0
var playOnLoad = this.$store.state.playOnLoad
if (playOnLoad) this.$store.commit('setPlayOnLoad', false)
var audiobookStreamData = {
title: this.title,
author: this.author,
playWhenReady: !!playOnLoad,
startTime: String(Math.floor(currentTime * 1000)),
cover: this.coverForNative,
duration: String(Math.floor(this.duration * 1000)),
series: this.seriesTxt,
playlistUrl: this.$server.url + playlistUrl,
token: this.$store.getters['user/getToken']
}
console.log('audiobook stream data', audiobookStreamData.token, JSON.stringify(audiobookStreamData))
this.$refs.audioPlayerMini.set(audiobookStreamData)
},
audioPlayerMounted() {
console.log('Audio Player Mounted', this.$server.stream)
this.audioPlayerReady = true
if (this.$server.stream) {
this.streamOpen(this.$server.stream)
}
},
setListeners() {
if (!this.$server.socket) {
console.error('Invalid server socket not set')
return
}
this.$server.socket.on('stream_open', this.streamOpen)
this.$server.socket.on('stream_closed', this.streamClosed)
this.$server.socket.on('stream_progress', this.streamProgress)
this.$server.socket.on('stream_ready', this.streamReady)
this.$server.socket.on('stream_reset', this.streamReset)
}
},
mounted() {
console.warn('Stream Container Mounted')
this.setListeners()
}
}
</script>