Merge branch 'master' of github.com:advplyr/audiobookshelf

# Conflicts:
#	client/components/app/BookShelfCategorized.vue
#	client/components/cards/LazySeriesCard.vue
This commit is contained in:
Toni Barth 2024-07-09 15:06:08 +02:00
commit 2896e32f4e
147 changed files with 8627 additions and 5171 deletions

View file

@ -11,10 +11,9 @@
</div>
</div>
<div class="flex justify-center">
<div class="flex justify-center mb-2">
<div class="w-full max-w-2xl">
<p class="text-xl mb-1">{{ $strings.HeaderMetadataToEmbed }}</p>
<p class="mb-2 text-base text-gray-300">audiobookshelf uses <a href="https://github.com/sandreas/tone" target="_blank" class="hover:underline text-blue-400 hover:text-blue-300">tone</a> to write metadata.</p>
<p class="text-xl">{{ $strings.HeaderMetadataToEmbed }}</p>
</div>
<div class="w-full max-w-2xl"></div>
</div>
@ -26,7 +25,7 @@
<div class="w-2/3 text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelValue }}</div>
</div>
<div class="w-full max-h-72 overflow-auto">
<template v-for="(value, key, index) in toneObject">
<template v-for="(value, key, index) in metadataObject">
<div :key="key" class="flex py-1 px-4 text-sm" :class="index % 2 === 0 ? 'bg-primary bg-opacity-25' : ''">
<div class="w-1/3 font-semibold">{{ key }}</div>
<div class="w-2/3">
@ -208,7 +207,7 @@ export default {
processing: false,
audiofilesEncoding: {},
audiofilesFinished: {},
toneObject: null,
metadataObject: null,
selectedTool: 'embed',
isCancelingEncode: false,
showEncodeOptions: false,
@ -387,7 +386,7 @@ export default {
window.history.replaceState({ path: newurl }, '', newurl)
},
init() {
this.fetchToneObject()
this.fetchMetadataEmbedObject()
if (this.$route.query.tool === 'm4b') {
if (this.availableTools.some((t) => t.value === 'm4b')) {
this.selectedTool = 'm4b'
@ -401,15 +400,14 @@ export default {
const shouldBackupAudioFiles = localStorage.getItem('embedMetadataShouldBackup')
this.shouldBackupAudioFiles = shouldBackupAudioFiles != 0
},
fetchToneObject() {
fetchMetadataEmbedObject() {
this.$axios
.$get(`/api/items/${this.libraryItemId}/tone-object`)
.then((toneObject) => {
delete toneObject.CoverFile
this.toneObject = toneObject
.$get(`/api/items/${this.libraryItemId}/metadata-object`)
.then((metadataObject) => {
this.metadataObject = metadataObject
})
.catch((error) => {
console.error('Failed to fetch tone object', error)
console.error('Failed to fetch metadata object', error)
})
},
taskUpdated(task) {
@ -426,4 +424,4 @@ export default {
this.$root.socket.off('audiofile_metadata_finished', this.audiofileMetadataFinished)
}
}
</script>
</script>

View file

@ -1,10 +1,27 @@
<template>
<div>
<app-settings-content :header-text="$strings.HeaderBackups" :description="$strings.MessageBackupsDescription">
<div v-if="backupLocation" class="flex items-center mb-4">
<span class="material-icons-outlined text-2xl text-black-50 mr-2">folder</span>
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelBackupLocation }}:</span>
<div class="text-gray-100 pl-4">{{ backupLocation }}</div>
<div v-if="backupLocation" class="mb-4 max-w-full overflow-hidden">
<div class="flex items-center mb-0.5">
<span class="material-icons-outlined text-2xl text-black-50 mr-2">folder</span>
<span class="text-white text-opacity-60 uppercase text-sm whitespace-nowrap">{{ $strings.LabelBackupLocation }}:</span>
</div>
<div v-if="!showEditBackupPath" class="inline-flex items-center w-full overflow-hidden">
<p class="text-gray-100 max-w-[calc(100%-40px)] text-sm sm:text-base break-words">{{ backupLocation }}</p>
<div class="w-10 min-w-10 flex items-center justify-center">
<button class="text-black-50 hover:text-yellow-500 inline-flex" type="button" @click="showEditBackupPath = !showEditBackupPath">
<span class="material-icons text-lg">edit</span>
</button>
</div>
</div>
<div v-else>
<form class="flex items-center w-full space-x-1" @submit.prevent="saveBackupPath">
<ui-text-input v-model="newBackupLocation" :disabled="savingBackupPath || !canEditBackup" class="w-full max-w-[calc(100%-50px)] text-sm h-8" />
<ui-btn v-if="canEditBackup" small :loading="savingBackupPath" color="success" type="submit" class="h-8">{{ $strings.ButtonSave }}</ui-btn>
<ui-btn small :disabled="savingBackupPath" type="button" class="h-8" @click="cancelEditBackupPath">{{ $strings.ButtonCancel }}</ui-btn>
</form>
<p class="text-sm text-warning/80 pt-1">{{ canEditBackup ? $strings.MessageBackupsLocationEditNote : $strings.MessageBackupsLocationNoEditNote }}</p>
</div>
</div>
<div class="flex items-center py-2">
@ -15,21 +32,23 @@
</div>
<div v-if="enableBackups" class="mb-6">
<div class="flex items-center pl-6 mb-2">
<span class="material-icons-outlined text-2xl text-black-50 mr-2">schedule</span>
<div class="w-40">
<div class="flex items-center pl-0 sm:pl-6 mb-2">
<span class="material-icons-outlined text-xl sm:text-2xl text-black-50 mr-2">schedule</span>
<div class="w-32 min-w-32 sm:w-40 sm:min-w-40">
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.HeaderSchedule }}:</span>
</div>
<div class="text-gray-100">{{ scheduleDescription }}</div>
<span class="material-icons text-lg text-black-50 hover:text-yellow-500 cursor-pointer ml-2" @click="showCronBuilder = !showCronBuilder">edit</span>
<div class="text-gray-100 text-sm sm:text-base">{{ scheduleDescription }}</div>
<button class="ml-2 text-black-50 hover:text-yellow-500 inline-flex" type="button" @click="showCronBuilder = !showCronBuilder">
<span class="material-icons text-lg">edit</span>
</button>
</div>
<div v-if="nextBackupDate" class="flex items-center pl-6 py-0.5 px-2">
<span class="material-icons-outlined text-2xl text-black-50 mr-2">event</span>
<div class="w-40">
<div v-if="nextBackupDate" class="flex items-center pl-0 sm:pl-6 py-0.5">
<span class="material-icons-outlined text-xl sm:text-2xl text-black-50 mr-2">event</span>
<div class="w-32 min-w-32 sm:w-40 sm:min-w-40">
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelNextBackupDate }}:</span>
</div>
<div class="text-gray-100">{{ nextBackupDate }}</div>
<div class="text-gray-100 text-sm sm:text-base">{{ nextBackupDate }}</div>
</div>
</div>
@ -49,7 +68,7 @@
</ui-tooltip>
</div>
<tables-backups-table @loaded="backupsLoaded" />
<tables-backups-table ref="backupsTable" @loaded="backupsLoaded" />
<modals-backup-schedule-modal v-model="showCronBuilder" :cron-expression.sync="cronExpression" />
</app-settings-content>
@ -72,7 +91,11 @@ export default {
cronExpression: '',
newServerSettings: {},
showCronBuilder: false,
backupLocation: ''
showEditBackupPath: false,
backupPathEnvSet: false,
backupLocation: '',
newBackupLocation: '',
savingBackupPath: false
}
},
watch: {
@ -93,6 +116,10 @@ export default {
timeFormat() {
return this.serverSettings.timeFormat
},
canEditBackup() {
// Prevent editing of backup path if an environment variable is set
return !this.backupPathEnvSet
},
scheduleDescription() {
if (!this.cronExpression) return ''
const parsed = this.$parseCronExpression(this.cronExpression)
@ -105,8 +132,42 @@ export default {
}
},
methods: {
backupsLoaded(backupLocation) {
this.backupLocation = backupLocation
backupsLoaded(data) {
this.backupLocation = data.backupLocation
this.newBackupLocation = data.backupLocation
this.backupPathEnvSet = data.backupPathEnvSet
},
cancelEditBackupPath() {
this.newBackupLocation = this.backupLocation
this.showEditBackupPath = false
},
saveBackupPath() {
if (!this.newBackupLocation?.trim()) {
this.$toast.error(this.$strings.MessageBackupsLocationPathEmpty)
return
}
this.newBackupLocation = this.newBackupLocation.trim()
if (this.newBackupLocation === this.backupLocation) {
this.showEditBackupPath = false
return
}
this.savingBackupPath = true
this.$axios
.patch('/api/backups/path', { path: this.newBackupLocation })
.then(() => {
this.backupLocation = this.newBackupLocation
this.showEditBackupPath = false
this.$refs.backupsTable.loadBackups()
})
.catch((error) => {
console.error('Failed to save backup path', error)
const errorMsg = error.response?.data || 'Failed to save backup path'
this.$toast.error(errorMsg)
})
.finally(() => {
this.savingBackupPath = false
})
},
updateBackupsSettings() {
if (isNaN(this.maxBackupSize) || this.maxBackupSize <= 0) {

View file

@ -100,7 +100,7 @@
</div>
<p v-else class="text-white text-opacity-50">{{ $strings.MessageNoListeningSessions }}</p>
<div class="w-full my-8 h-px bg-white/10" />
<div v-if="openListeningSessions.length" class="w-full my-8 h-px bg-white/10" />
<!-- open listening sessions table -->
<p v-if="openListeningSessions.length" class="text-lg my-4">Open Listening Sessions</p>
@ -144,6 +144,45 @@
</tr>
</table>
</div>
<div v-if="openShareListeningSessions.length" class="w-full my-8 h-px bg-white/10" />
<!-- open share listening sessions table -->
<p v-if="openShareListeningSessions.length" class="text-lg my-4">Open Share Listening Sessions</p>
<div v-if="openShareListeningSessions.length" class="block max-w-full">
<table class="userSessionsTable">
<tr class="bg-primary bg-opacity-40">
<th class="w-48 min-w-48 text-left">{{ $strings.LabelItem }}</th>
<th class="w-20 min-w-20 text-left hidden md:table-cell">{{ $strings.LabelUser }}</th>
<th class="w-32 min-w-32 text-left hidden md:table-cell">{{ $strings.LabelPlayMethod }}</th>
<th class="w-32 min-w-32 text-left hidden sm:table-cell">{{ $strings.LabelDeviceInfo }}</th>
<th class="w-16 min-w-16">{{ $strings.LabelLastTime }}</th>
<th class="flex-grow hidden sm:table-cell">{{ $strings.LabelLastUpdate }}</th>
</tr>
<tr v-for="session in openShareListeningSessions" :key="`open-${session.id}`" class="cursor-pointer" @click="showSession(session)">
<td class="py-1 max-w-48">
<p class="text-xs text-gray-200 truncate">{{ session.displayTitle }}</p>
<p class="text-xs text-gray-400 truncate">{{ session.displayAuthor }}</p>
</td>
<td class="hidden md:table-cell"></td>
<td class="hidden md:table-cell">
<p class="text-xs">{{ getPlayMethodName(session.playMethod) }}</p>
</td>
<td class="hidden sm:table-cell max-w-32 min-w-32">
<p class="text-xs truncate" v-html="getDeviceInfoString(session.deviceInfo)" />
</td>
<td class="text-center hover:underline" @click.stop="clickCurrentTime(session)">
<p class="text-xs font-mono">{{ $secondsToTimestamp(session.currentTime) }}</p>
</td>
<td class="text-center hidden sm:table-cell">
<ui-tooltip v-if="session.updatedAt" direction="top" :text="$formatDatetime(session.updatedAt, dateFormat, timeFormat)">
<p class="text-xs text-gray-200">{{ $dateDistanceFromNow(session.updatedAt) }}</p>
</ui-tooltip>
</td>
</tr>
</table>
</div>
</app-settings-content>
<modals-listening-session-modal v-model="showSessionModal" :session="selectedSession" @removedSession="removedSession" @closedSession="closedSession" />
@ -180,6 +219,7 @@ export default {
selectedSession: null,
listeningSessions: [],
openListeningSessions: [],
openShareListeningSessions: [],
numPages: 0,
total: 0,
currentPage: 0,
@ -455,6 +495,7 @@ export default {
s.open = true
return s
})
this.openShareListeningSessions = data.shareSessions || []
},
init() {
this.loadSessions(0)

View file

@ -160,7 +160,7 @@ export default {
}
// Include episode downloads for podcasts
var item = await app.$axios.$get(`/api/items/${params.id}?expanded=1&include=downloads,rssfeed`).catch((error) => {
var item = await app.$axios.$get(`/api/items/${params.id}?expanded=1&include=downloads,rssfeed,share`).catch((error) => {
console.error('Failed', error)
return false
})
@ -170,7 +170,8 @@ export default {
}
return {
libraryItem: item,
rssFeed: item.rssFeed || null
rssFeed: item.rssFeed || null,
mediaItemShare: item.mediaItemShare || null
}
},
data() {
@ -437,6 +438,13 @@ export default {
})
}
if (this.userIsAdminOrUp && !this.isPodcast && this.tracks.length) {
items.push({
text: this.$strings.LabelShare,
action: 'share'
})
}
if (this.userCanDelete) {
items.push({
text: this.$strings.ButtonDelete,
@ -666,6 +674,16 @@ export default {
this.rssFeed = null
}
},
shareOpen(mediaItemShare) {
if (mediaItemShare.mediaItemId === this.media.id) {
this.mediaItemShare = mediaItemShare
}
},
shareClosed(mediaItemShare) {
if (mediaItemShare.mediaItemId === this.media.id) {
this.mediaItemShare = null
}
},
queueBtnClick() {
if (this.isQueued) {
// Remove from queue
@ -761,6 +779,9 @@ export default {
this.deleteLibraryItem()
} else if (action === 'sendToDevice') {
this.sendToDevice(data)
} else if (action === 'share') {
this.$store.commit('setSelectedLibraryItem', this.libraryItem)
this.$store.commit('globals/setShareModal', this.mediaItemShare)
}
}
},
@ -778,6 +799,8 @@ export default {
this.$root.socket.on('item_updated', this.libraryItemUpdated)
this.$root.socket.on('rss_feed_open', this.rssFeedOpen)
this.$root.socket.on('rss_feed_closed', this.rssFeedClosed)
this.$root.socket.on('share_open', this.shareOpen)
this.$root.socket.on('share_closed', this.shareClosed)
this.$root.socket.on('episode_download_queued', this.episodeDownloadQueued)
this.$root.socket.on('episode_download_started', this.episodeDownloadStarted)
this.$root.socket.on('episode_download_finished', this.episodeDownloadFinished)
@ -787,6 +810,8 @@ export default {
this.$root.socket.off('item_updated', this.libraryItemUpdated)
this.$root.socket.off('rss_feed_open', this.rssFeedOpen)
this.$root.socket.off('rss_feed_closed', this.rssFeedClosed)
this.$root.socket.off('share_open', this.shareOpen)
this.$root.socket.off('share_closed', this.shareClosed)
this.$root.socket.off('episode_download_queued', this.episodeDownloadQueued)
this.$root.socket.off('episode_download_started', this.episodeDownloadStarted)
this.$root.socket.off('episode_download_finished', this.episodeDownloadFinished)

View file

@ -1,10 +1,12 @@
<template>
<div class="page" :class="streamLibraryItem ? 'streaming' : ''">
<app-book-shelf-toolbar page="authors" is-home :authors="authors" />
<div id="bookshelf" class="w-full h-full p-8 overflow-y-auto">
<div id="bookshelf" class="w-full h-full p-8e overflow-y-auto" :style="{ fontSize: sizeMultiplier + 'rem' }">
<!-- Cover size widget -->
<widgets-cover-size-widget class="fixed right-4 z-50" :style="{ bottom: streamLibraryItem ? '181px' : '16px' }" />
<div class="flex flex-wrap justify-center">
<template v-for="author in authorsSorted">
<cards-author-card :key="author.id" :author="author" :width="160" :height="200" class="p-3" @edit="editAuthor" />
<cards-author-card :key="author.id" :author="author" class="p-3e" @edit="editAuthor" />
</template>
</div>
</div>
@ -36,6 +38,9 @@ export default {
}
},
computed: {
sizeMultiplier() {
return this.$store.getters['user/getSizeMultiplier']
},
streamLibraryItem() {
return this.$store.state.streamLibraryItem
},

View file

@ -0,0 +1,278 @@
<template>
<div class="w-full h-dvh max-h-dvh overflow-hidden" :style="{ backgroundColor: coverRgb }">
<div class="w-screen h-screen absolute inset-0 pointer-events-none" style="background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(38, 38, 38, 1) 80%)"></div>
<div class="absolute inset-0 w-screen h-dvh flex items-center justify-center z-10">
<div class="w-full p-2 sm:p-4 md:p-8">
<div v-if="!isMobileLandscape" :style="{ width: coverWidth + 'px', height: coverHeight + 'px' }" class="mx-auto overflow-hidden rounded-xl my-2">
<img ref="coverImg" :src="coverUrl" class="object-contain w-full h-full" @load="coverImageLoaded" />
</div>
<p class="text-2xl lg:text-3xl font-semibold text-center mb-1 line-clamp-2">{{ mediaItemShare.playbackSession.displayTitle || 'No title' }}</p>
<p v-if="mediaItemShare.playbackSession.displayAuthor" class="text-lg lg:text-xl text-slate-400 font-semibold text-center mb-1 truncate">{{ mediaItemShare.playbackSession.displayAuthor }}</p>
<div class="w-full pt-16">
<player-ui ref="audioPlayer" :chapters="chapters" :paused="isPaused" :loading="!hasLoaded" :is-podcast="false" hide-bookmarks hide-sleep-timer @playPause="playPause" @jumpForward="jumpForward" @jumpBackward="jumpBackward" @setVolume="setVolume" @setPlaybackRate="setPlaybackRate" @seek="seek" />
</div>
</div>
</div>
</div>
</template>
<script>
import LocalAudioPlayer from '../../players/LocalAudioPlayer'
import { FastAverageColor } from 'fast-average-color'
export default {
layout: 'blank',
async asyncData({ params, error, app, query }) {
let endpoint = `/public/share/${params.slug}`
if (query.t && !isNaN(query.t)) {
endpoint += `?t=${query.t}`
}
const mediaItemShare = await app.$axios.$get(endpoint, { timeout: 10000 }).catch((error) => {
console.error('Failed', error)
return null
})
if (!mediaItemShare) {
return error({ statusCode: 404, message: 'Media item not found or expired' })
}
return {
mediaItemShare: mediaItemShare
}
},
data() {
return {
localAudioPlayer: new LocalAudioPlayer(),
playerState: null,
playInterval: null,
hasLoaded: false,
totalDuration: 0,
windowWidth: 0,
windowHeight: 0,
listeningTimeSinceSync: 0,
coverRgb: null,
coverBgIsLight: false
}
},
computed: {
playbackSession() {
return this.mediaItemShare.playbackSession
},
coverUrl() {
if (!this.playbackSession.coverPath) return `${this.$config.routerBasePath}/book_placeholder.jpg`
if (process.env.NODE_ENV === 'development') {
return `http://localhost:3333/public/share/${this.mediaItemShare.slug}/cover`
}
return `/public/share/${this.mediaItemShare.slug}/cover`
},
audioTracks() {
return (this.playbackSession.audioTracks || []).map((track) => {
if (process.env.NODE_ENV === 'development') {
track.contentUrl = `${process.env.serverUrl}${track.contentUrl}`
}
track.relativeContentUrl = track.contentUrl
return track
})
},
isPlaying() {
return this.playerState === 'PLAYING'
},
isPaused() {
return !this.isPlaying
},
chapters() {
return this.playbackSession.chapters || []
},
coverAspectRatio() {
const coverAspectRatio = this.playbackSession.coverAspectRatio
return coverAspectRatio === this.$constants.BookCoverAspectRatio.STANDARD ? 1.6 : 1
},
isMobileLandscape() {
return this.windowWidth > this.windowHeight && this.windowHeight < 450
},
coverWidth() {
const availableCoverWidth = Math.min(450, this.windowWidth - 32)
const availableCoverHeight = Math.min(450, this.windowHeight - 250)
const mostCoverHeight = availableCoverWidth * this.coverAspectRatio
if (mostCoverHeight > availableCoverHeight) {
return availableCoverHeight / this.coverAspectRatio
}
return availableCoverWidth
},
coverHeight() {
return this.coverWidth * this.coverAspectRatio
}
},
methods: {
async coverImageLoaded(e) {
if (!this.playbackSession.coverPath) return
const fac = new FastAverageColor()
fac
.getColorAsync(e.target)
.then((color) => {
this.coverRgb = color.rgba
this.coverBgIsLight = color.isLight
document.body.style.backgroundColor = color.hex
})
.catch((e) => {
console.log(e)
})
},
playPause() {
if (!this.localAudioPlayer || !this.hasLoaded) return
this.localAudioPlayer.playPause()
},
jumpForward() {
if (!this.localAudioPlayer || !this.hasLoaded) return
const currentTime = this.localAudioPlayer.getCurrentTime()
const duration = this.localAudioPlayer.getDuration()
this.seek(Math.min(currentTime + 10, duration))
},
jumpBackward() {
if (!this.localAudioPlayer || !this.hasLoaded) return
const currentTime = this.localAudioPlayer.getCurrentTime()
this.seek(Math.max(currentTime - 10, 0))
},
setVolume(volume) {
if (!this.localAudioPlayer || !this.hasLoaded) return
this.localAudioPlayer.setVolume(volume)
},
setPlaybackRate(playbackRate) {
if (!this.localAudioPlayer || !this.hasLoaded) return
this.localAudioPlayer.setPlaybackRate(playbackRate)
},
seek(time) {
if (!this.localAudioPlayer || !this.hasLoaded) return
this.localAudioPlayer.seek(time, this.isPlaying)
this.setCurrentTime(time)
},
setCurrentTime(time) {
if (!this.$refs.audioPlayer) return
// Update UI
this.$refs.audioPlayer.setCurrentTime(time)
},
setDuration() {
if (!this.localAudioPlayer) return
this.totalDuration = this.localAudioPlayer.getDuration()
if (this.$refs.audioPlayer) {
this.$refs.audioPlayer.setDuration(this.totalDuration)
}
},
sendProgressSync(currentTime) {
console.log('Sending progress sync for time', currentTime)
const progress = {
currentTime
}
this.$axios.$patch(`/public/share/${this.mediaItemShare.slug}/progress`, progress, { progress: false }).catch((error) => {
console.error('Failed to send progress sync', error)
})
},
startPlayInterval() {
let lastTick = Date.now()
clearInterval(this.playInterval)
this.playInterval = setInterval(() => {
if (!this.localAudioPlayer) return
const currentTime = this.localAudioPlayer.getCurrentTime()
this.setCurrentTime(currentTime)
const exactTimeElapsed = (Date.now() - lastTick) / 1000
lastTick = Date.now()
this.listeningTimeSinceSync += exactTimeElapsed
if (this.listeningTimeSinceSync >= 30) {
this.listeningTimeSinceSync = 0
this.sendProgressSync(currentTime)
}
}, 1000)
},
stopPlayInterval() {
clearInterval(this.playInterval)
this.playInterval = null
},
playerStateChange(state) {
this.playerState = state
if (state === 'LOADED' || state === 'PLAYING') {
this.setDuration()
}
if (state === 'LOADED') {
this.hasLoaded = true
}
if (state === 'PLAYING') {
this.startPlayInterval()
} else {
this.stopPlayInterval()
}
},
playerTimeUpdate(time) {
this.setCurrentTime(time)
},
getHotkeyName(e) {
var keyCode = e.keyCode || e.which
if (!this.$keynames[keyCode]) {
// Unused hotkey
return null
}
var keyName = this.$keynames[keyCode]
var name = keyName
if (e.shiftKey) name = 'Shift-' + keyName
if (process.env.NODE_ENV !== 'production') {
console.log('Hotkey command', name)
}
return name
},
keyDown(e) {
if (!this.localAudioPlayer || !this.hasLoaded) return
var name = this.getHotkeyName(e)
if (!name) return
// Playing audiobook
if (Object.values(this.$hotkeys.AudioPlayer).includes(name)) {
this.$eventBus.$emit('player-hotkey', name)
e.preventDefault()
}
},
resize() {
this.windowWidth = window.innerWidth
this.windowHeight = window.innerHeight
},
playerError(error) {
console.error('Player error', error)
this.$toast.error('Failed to play audio on device')
},
playerFinished() {
console.log('Player finished')
}
},
mounted() {
this.resize()
window.addEventListener('resize', this.resize)
window.addEventListener('keydown', this.keyDown)
if (process.env.NODE_ENV === 'development') {
console.log('Loaded media item share', this.mediaItemShare)
}
const startTime = this.playbackSession.currentTime || 0
this.localAudioPlayer.set(null, this.audioTracks, false, startTime, false)
this.localAudioPlayer.on('stateChange', this.playerStateChange.bind(this))
this.localAudioPlayer.on('timeupdate', this.playerTimeUpdate.bind(this))
this.localAudioPlayer.on('error', this.playerError.bind(this))
this.localAudioPlayer.on('finished', this.playerFinished.bind(this))
},
beforeDestroy() {
window.removeEventListener('resize', this.resize)
window.removeEventListener('keydown', this.keyDown)
this.localAudioPlayer.off('stateChange', this.playerStateChange.bind(this))
this.localAudioPlayer.off('timeupdate', this.playerTimeUpdate.bind(this))
this.localAudioPlayer.off('error', this.playerError.bind(this))
this.localAudioPlayer.off('finished', this.playerFinished.bind(this))
this.localAudioPlayer.destroy()
}
}
</script>