mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-04-20 01:17:45 +02:00
New model update audio player, stream, collections
This commit is contained in:
parent
2d19208340
commit
65df377a49
@ -118,7 +118,7 @@ export default {
|
||||
return this.book.numTracks
|
||||
},
|
||||
isStreaming() {
|
||||
return this.$store.getters['getAudiobookIdStreaming'] === this.audiobookId
|
||||
return this.$store.getters['getLibraryItemIdStreaming'] === this.audiobookId
|
||||
},
|
||||
showReadButton() {
|
||||
return this.showExperimentalFeatures && this.numEbooks
|
||||
@ -168,7 +168,7 @@ export default {
|
||||
})
|
||||
},
|
||||
startStream() {
|
||||
this.$eventBus.$emit('play-audiobook', this.book.id)
|
||||
this.$eventBus.$emit('play-item', this.book.id)
|
||||
},
|
||||
editClick() {
|
||||
this.$emit('edit', this.book)
|
||||
|
@ -1,17 +1,17 @@
|
||||
<template>
|
||||
<div v-if="streamAudiobook" id="streamContainer" class="w-full fixed bottom-0 left-0 right-0 h-48 sm:h-44 md:h-40 z-40 bg-primary px-4 pb-1 md:pb-4 pt-2">
|
||||
<nuxt-link :to="`/audiobook/${streamAudiobook.id}`" class="absolute left-4 cursor-pointer" :style="{ top: bookCoverPosTop + 'px' }">
|
||||
<covers-book-cover :audiobook="streamAudiobook" :width="bookCoverWidth" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||
<div v-if="streamLibraryItem" id="streamContainer" class="w-full fixed bottom-0 left-0 right-0 h-48 sm:h-44 md:h-40 z-40 bg-primary px-4 pb-1 md:pb-4 pt-2">
|
||||
<nuxt-link :to="`/item/${streamLibraryItem.id}`" class="absolute left-4 cursor-pointer" :style="{ top: bookCoverPosTop + 'px' }">
|
||||
<covers-book-cover :library-item="streamLibraryItem" :width="bookCoverWidth" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||
</nuxt-link>
|
||||
<div class="flex items-start pl-24 mb-6 md:mb-0">
|
||||
<div>
|
||||
<nuxt-link :to="`/audiobook/${streamAudiobook.id}`" class="hover:underline cursor-pointer text-base sm:text-lg">
|
||||
<nuxt-link :to="`/item/${streamLibraryItem.id}`" class="hover:underline cursor-pointer text-base sm:text-lg">
|
||||
{{ title }}
|
||||
</nuxt-link>
|
||||
<div class="text-gray-400 flex items-center">
|
||||
<span class="material-icons text-sm">person</span>
|
||||
<p v-if="authorFL" class="pl-1.5 text-sm sm:text-base">
|
||||
<nuxt-link v-for="(author, index) in authorsList" :key="index" :to="`/library/${libraryId}/bookshelf?filter=authors.${$encode(author)}`" class="hover:underline">{{ author }}<span v-if="index < authorsList.length - 1">, </span></nuxt-link>
|
||||
<p v-if="authors.length" class="pl-1.5 text-sm sm:text-base">
|
||||
<nuxt-link v-for="(author, index) in authors" :key="index" :to="`/library/${libraryId}/bookshelf?filter=authors.${$encode(author.id)}`" class="hover:underline">{{ author.name }}<span v-if="index < authors.length - 1">, </span></nuxt-link>
|
||||
</p>
|
||||
<p v-else class="text-sm sm:text-base cursor-pointer pl-2">Unknown</p>
|
||||
</div>
|
||||
@ -89,15 +89,15 @@ export default {
|
||||
return -64
|
||||
},
|
||||
cover() {
|
||||
if (this.streamAudiobook && this.streamAudiobook.cover) return this.streamAudiobook.cover
|
||||
if (this.media.coverPath) return this.media.coverPath
|
||||
return 'Logo.png'
|
||||
},
|
||||
user() {
|
||||
return this.$store.state.user.user
|
||||
},
|
||||
userAudiobook() {
|
||||
if (!this.audiobookId) return
|
||||
return this.$store.getters['user/getUserAudiobook'](this.audiobookId)
|
||||
if (!this.libraryItemId) return
|
||||
return this.$store.getters['user/getUserAudiobook'](this.libraryItemId)
|
||||
},
|
||||
userAudiobookCurrentTime() {
|
||||
return this.userAudiobook ? this.userAudiobook.currentTime || 0 : 0
|
||||
@ -106,32 +106,29 @@ export default {
|
||||
if (!this.userAudiobook) return []
|
||||
return (this.userAudiobook.bookmarks || []).map((bm) => ({ ...bm })).sort((a, b) => a.time - b.time)
|
||||
},
|
||||
streamAudiobook() {
|
||||
return this.$store.state.streamAudiobook
|
||||
streamLibraryItem() {
|
||||
return this.$store.state.streamLibraryItem
|
||||
},
|
||||
audiobookId() {
|
||||
return this.streamAudiobook ? this.streamAudiobook.id : null
|
||||
libraryItemId() {
|
||||
return this.streamLibraryItem ? this.streamLibraryItem.id : null
|
||||
},
|
||||
book() {
|
||||
return this.streamAudiobook ? this.streamAudiobook.book || {} : {}
|
||||
media() {
|
||||
return this.streamLibraryItem ? this.streamLibraryItem.media || {} : {}
|
||||
},
|
||||
mediaMetadata() {
|
||||
return this.media.metadata || {}
|
||||
},
|
||||
chapters() {
|
||||
return this.streamAudiobook ? this.streamAudiobook.chapters || [] : []
|
||||
return this.media.chapters || []
|
||||
},
|
||||
title() {
|
||||
return this.book.title || 'No Title'
|
||||
return this.mediaMetadata.title || 'No Title'
|
||||
},
|
||||
author() {
|
||||
return this.book.author || 'Unknown'
|
||||
},
|
||||
authorFL() {
|
||||
return this.book.authorFL
|
||||
},
|
||||
authorsList() {
|
||||
return this.authorFL ? this.authorFL.split(', ') : []
|
||||
authors() {
|
||||
return this.mediaMetadata.authors || []
|
||||
},
|
||||
libraryId() {
|
||||
return this.streamAudiobook ? this.streamAudiobook.libraryId : null
|
||||
return this.streamLibraryItem ? this.streamLibraryItem.libraryId : null
|
||||
},
|
||||
totalDurationPretty() {
|
||||
return this.$secondsToTimestamp(this.totalDuration)
|
||||
@ -217,7 +214,7 @@ export default {
|
||||
}
|
||||
},
|
||||
showBookmarks() {
|
||||
this.bookmarkAudiobookId = this.audiobookId
|
||||
this.bookmarkAudiobookId = this.libraryItemId
|
||||
this.bookmarkCurrentTime = this.currentTime
|
||||
this.showBookmarksModal = true
|
||||
},
|
||||
@ -227,7 +224,7 @@ export default {
|
||||
},
|
||||
closePlayer() {
|
||||
this.playerHandler.closePlayer()
|
||||
this.$store.commit('setStreamAudiobook', null)
|
||||
this.$store.commit('setLibraryItemStream', null)
|
||||
},
|
||||
streamProgress(data) {
|
||||
if (!data.numSegments) return
|
||||
@ -240,12 +237,12 @@ export default {
|
||||
}
|
||||
},
|
||||
streamOpen(stream) {
|
||||
this.$store.commit('setStreamAudiobook', stream.audiobook)
|
||||
this.$store.commit('setLibraryItemStream', stream.libraryItem)
|
||||
this.playerHandler.prepareStream(stream)
|
||||
},
|
||||
streamClosed(streamId) {
|
||||
// Stream was closed from the server
|
||||
if (this.playerHandler.isPlayingLocalAudiobook && this.playerHandler.currentStreamId === streamId) {
|
||||
if (this.playerHandler.isPlayingLocalItem && this.playerHandler.currentStreamId === streamId) {
|
||||
console.warn('[StreamContainer] Closing stream due to request from server')
|
||||
this.playerHandler.closePlayer()
|
||||
}
|
||||
@ -260,7 +257,7 @@ export default {
|
||||
},
|
||||
streamError(streamId) {
|
||||
// Stream had critical error from the server
|
||||
if (this.playerHandler.isPlayingLocalAudiobook && this.playerHandler.currentStreamId === streamId) {
|
||||
if (this.playerHandler.isPlayingLocalItem && this.playerHandler.currentStreamId === streamId) {
|
||||
console.warn('[StreamContainer] Closing stream due to stream error from server')
|
||||
this.playerHandler.closePlayer()
|
||||
}
|
||||
@ -269,32 +266,32 @@ export default {
|
||||
this.playerHandler.resetStream(startTime, streamId)
|
||||
},
|
||||
castSessionActive(isActive) {
|
||||
if (isActive && this.playerHandler.isPlayingLocalAudiobook) {
|
||||
if (isActive && this.playerHandler.isPlayingLocalItem) {
|
||||
// Cast session started switch to cast player
|
||||
this.playerHandler.switchPlayer()
|
||||
} else if (!isActive && this.playerHandler.isPlayingCastedAudiobook) {
|
||||
} else if (!isActive && this.playerHandler.isPlayingCastedItem) {
|
||||
// Cast session ended switch to local player
|
||||
this.playerHandler.switchPlayer()
|
||||
}
|
||||
},
|
||||
async playAudiobook(audiobookId) {
|
||||
var audiobook = await this.$axios.$get(`/api/books/${audiobookId}`).catch((error) => {
|
||||
console.error('Failed to fetch full audiobook', error)
|
||||
async playLibraryItem(libraryItemId) {
|
||||
var libraryItem = await this.$axios.$get(`/api/items/${libraryItemId}?expanded=1`).catch((error) => {
|
||||
console.error('Failed to fetch full item', error)
|
||||
return null
|
||||
})
|
||||
if (!audiobook) return
|
||||
this.$store.commit('setStreamAudiobook', audiobook)
|
||||
if (!libraryItem) return
|
||||
this.$store.commit('setLibraryItemStream', libraryItem)
|
||||
|
||||
this.playerHandler.load(audiobook, true, this.userAudiobookCurrentTime)
|
||||
this.playerHandler.load(libraryItem, true, this.userAudiobookCurrentTime)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$eventBus.$on('cast-session-active', this.castSessionActive)
|
||||
this.$eventBus.$on('play-audiobook', this.playAudiobook)
|
||||
this.$eventBus.$on('play-item', this.playLibraryItem)
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$eventBus.$off('cast-session-active', this.castSessionActive)
|
||||
this.$eventBus.$off('play-audiobook', this.playAudiobook)
|
||||
this.$eventBus.$off('play-item', this.playLibraryItem)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -512,7 +512,7 @@ export default {
|
||||
},
|
||||
play() {
|
||||
var eventBus = this.$eventBus || this.$nuxt.$eventBus
|
||||
eventBus.$emit('play-audiobook', this.libraryItemId)
|
||||
eventBus.$emit('play-item', this.libraryItemId)
|
||||
},
|
||||
mouseover() {
|
||||
this.isHovering = true
|
||||
|
@ -26,8 +26,8 @@
|
||||
</td>
|
||||
<td class="text-sm">{{ user.type }}</td>
|
||||
<td class="hidden lg:table-cell">
|
||||
<div v-if="usersOnline[user.id] && usersOnline[user.id].stream && usersOnline[user.id].stream.audiobook && usersOnline[user.id].stream.audiobook.book">
|
||||
<p class="truncate text-xs">Reading: {{ usersOnline[user.id].stream.audiobook.book.title || '' }}</p>
|
||||
<div v-if="usersOnline[user.id] && usersOnline[user.id].stream && usersOnline[user.id].stream.libraryItem && usersOnline[user.id].stream.libraryItem.media">
|
||||
<p class="truncate text-xs">Reading: {{ usersOnline[user.id].stream.libraryItem.media.metadata.title || '' }}</p>
|
||||
</div>
|
||||
<div v-else-if="user.audiobooks && getLastRead(user.audiobooks)">
|
||||
<p class="truncate text-xs">Last: {{ getLastRead(user.audiobooks) }}</p>
|
||||
|
@ -108,7 +108,7 @@ export default {
|
||||
return this.media.tracks.length
|
||||
},
|
||||
isStreaming() {
|
||||
return this.$store.getters['getAudiobookIdStreaming'] === this.book.id
|
||||
return this.$store.getters['getLibraryItemIdStreaming'] === this.book.id
|
||||
},
|
||||
showPlayBtn() {
|
||||
return !this.isMissing && !this.isInvalid && !this.isStreaming && this.numTracks
|
||||
@ -136,7 +136,7 @@ export default {
|
||||
this.isHovering = false
|
||||
},
|
||||
playClick() {
|
||||
this.$eventBus.$emit('play-audiobook', this.book.id)
|
||||
this.$eventBus.$emit('play-item', this.book.id)
|
||||
},
|
||||
clickEdit() {
|
||||
this.$emit('edit', this.book)
|
||||
|
@ -167,19 +167,6 @@ export default {
|
||||
this.$toast.error(`Stream Failed: ${errorMessage}`)
|
||||
if (this.$refs.streamContainer) this.$refs.streamContainer.streamError(id)
|
||||
},
|
||||
audiobookAdded(audiobook) {
|
||||
this.$store.commit('libraries/updateFilterDataWithAudiobook', audiobook)
|
||||
},
|
||||
audiobooksAdded(audiobooks) {
|
||||
audiobooks.forEach((ab) => {
|
||||
this.audiobookAdded(ab)
|
||||
})
|
||||
},
|
||||
audiobooksUpdated(audiobooks) {
|
||||
audiobooks.forEach((ab) => {
|
||||
this.audiobookUpdated(ab)
|
||||
})
|
||||
},
|
||||
libraryAdded(library) {
|
||||
this.$store.commit('libraries/addUpdate', library)
|
||||
},
|
||||
@ -189,6 +176,9 @@ export default {
|
||||
libraryRemoved(library) {
|
||||
this.$store.commit('libraries/remove', library)
|
||||
},
|
||||
libraryItemAdded(libraryItem) {
|
||||
// this.$store.commit('libraries/updateFilterDataWithAudiobook', libraryItem)
|
||||
},
|
||||
libraryItemUpdated(libraryItem) {
|
||||
if (this.$store.state.selectedLibraryItem && this.$store.state.selectedLibraryItem.id === libraryItem.id) {
|
||||
this.$store.commit('setSelectedLibraryItem', libraryItem)
|
||||
@ -202,6 +192,16 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
libraryItemsUpdated(libraryItems) {
|
||||
libraryItems.forEach((li) => {
|
||||
this.libraryItemUpdated(li)
|
||||
})
|
||||
},
|
||||
libraryItemsAdded(libraryItems) {
|
||||
libraryItems.forEach((ab) => {
|
||||
this.libraryItemAdded(ab)
|
||||
})
|
||||
},
|
||||
scanComplete(data) {
|
||||
console.log('Scan complete received', data)
|
||||
|
||||
@ -376,21 +376,17 @@ export default {
|
||||
this.socket.on('stream_reset', this.streamReset)
|
||||
this.socket.on('stream_error', this.streamError)
|
||||
|
||||
// Audiobook Listeners
|
||||
this.socket.on('audiobook_updated', this.audiobookUpdated)
|
||||
this.socket.on('audiobook_added', this.audiobookAdded)
|
||||
this.socket.on('audiobook_removed', this.audiobookRemoved)
|
||||
this.socket.on('audiobooks_updated', this.audiobooksUpdated)
|
||||
this.socket.on('audiobooks_added', this.audiobooksAdded)
|
||||
|
||||
// Library Listeners
|
||||
this.socket.on('library_updated', this.libraryUpdated)
|
||||
this.socket.on('library_added', this.libraryAdded)
|
||||
this.socket.on('library_removed', this.libraryRemoved)
|
||||
|
||||
// Library Item Listeners
|
||||
this.socket.on('item_added', this.libraryItemAdded)
|
||||
this.socket.on('item_updated', this.libraryItemUpdated)
|
||||
this.socket.on('item_removed', this.libraryItemRemoved)
|
||||
this.socket.on('items_updated', this.libraryItemsUpdated)
|
||||
this.socket.on('items_added', this.libraryItemsAdded)
|
||||
|
||||
// User Listeners
|
||||
this.socket.on('user_updated', this.userUpdated)
|
||||
|
@ -437,7 +437,7 @@ export default {
|
||||
})
|
||||
},
|
||||
startStream() {
|
||||
this.$eventBus.$emit('play-audiobook', this.audiobook.id)
|
||||
this.$eventBus.$emit('play-item', this.audiobook.id)
|
||||
},
|
||||
editClick() {
|
||||
this.$store.commit('setBookshelfBookIds', [])
|
||||
|
@ -87,7 +87,7 @@ export default {
|
||||
})
|
||||
},
|
||||
streaming() {
|
||||
return !!this.playableBooks.find((b) => b.id === this.$store.getters['getAudiobookIdStreaming'])
|
||||
return !!this.playableBooks.find((b) => b.id === this.$store.getters['getLibraryItemIdStreaming'])
|
||||
},
|
||||
showPlayButton() {
|
||||
return this.playableBooks.length
|
||||
@ -120,7 +120,7 @@ export default {
|
||||
clickPlay() {
|
||||
var nextBookNotRead = this.playableBooks.find((pb) => !this.userAudiobooks[pb.id] || !this.userAudiobooks[pb.id].isRead)
|
||||
if (nextBookNotRead) {
|
||||
this.$eventBus.$emit('play-audiobook', nextBookNotRead.id)
|
||||
this.$eventBus.$emit('play-item', nextBookNotRead.id)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -418,7 +418,7 @@ export default {
|
||||
})
|
||||
},
|
||||
startStream() {
|
||||
this.$eventBus.$emit('play-audiobook', this.libraryItem.id)
|
||||
this.$eventBus.$emit('play-item', this.libraryItem.id)
|
||||
},
|
||||
editClick() {
|
||||
this.$store.commit('setBookshelfBookIds', [])
|
||||
|
@ -3,7 +3,7 @@ export default class AudioTrack {
|
||||
this.index = track.index || 0
|
||||
this.startOffset = track.startOffset || 0 // Total time of all previous tracks
|
||||
this.duration = track.duration || 0
|
||||
this.title = track.filename || ''
|
||||
this.title = track.metadata ? track.metadata.filename || '' : ''
|
||||
this.contentUrl = track.contentUrl || null
|
||||
this.mimeType = track.mimeType
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ export default class LocalPlayer extends EventEmitter {
|
||||
this.ctx = ctx
|
||||
this.player = null
|
||||
|
||||
this.audiobook = null
|
||||
this.libraryItem = null
|
||||
this.audioTracks = []
|
||||
this.currentTrackIndex = 0
|
||||
this.hlsStreamId = null
|
||||
@ -110,8 +110,8 @@ export default class LocalPlayer extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
set(audiobook, tracks, hlsStreamId, startTime, playWhenReady = false) {
|
||||
this.audiobook = audiobook
|
||||
set(libraryItem, tracks, hlsStreamId, startTime, playWhenReady = false) {
|
||||
this.libraryItem = libraryItem
|
||||
this.audioTracks = tracks
|
||||
this.hlsStreamId = hlsStreamId
|
||||
this.playWhenReady = playWhenReady
|
||||
@ -198,7 +198,7 @@ export default class LocalPlayer extends EventEmitter {
|
||||
async resetStream(startTime) {
|
||||
this.destroyHlsInstance()
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
this.set(this.audiobook, this.audioTracks, this.hlsStreamId, startTime, true)
|
||||
this.set(this.libraryItem, this.audioTracks, this.hlsStreamId, startTime, true)
|
||||
}
|
||||
|
||||
playPause() {
|
||||
|
@ -5,7 +5,7 @@ import AudioTrack from './AudioTrack'
|
||||
export default class PlayerHandler {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.audiobook = null
|
||||
this.libraryItem = null
|
||||
this.playWhenReady = false
|
||||
this.player = null
|
||||
this.playerState = 'IDLE'
|
||||
@ -22,11 +22,11 @@ export default class PlayerHandler {
|
||||
get isCasting() {
|
||||
return this.ctx.$store.state.globals.isCasting
|
||||
}
|
||||
get isPlayingCastedAudiobook() {
|
||||
return this.audiobook && (this.player instanceof CastPlayer)
|
||||
get isPlayingCastedItem() {
|
||||
return this.libraryItem && (this.player instanceof CastPlayer)
|
||||
}
|
||||
get isPlayingLocalAudiobook() {
|
||||
return this.audiobook && (this.player instanceof LocalPlayer)
|
||||
get isPlayingLocalItem() {
|
||||
return this.libraryItem && (this.player instanceof LocalPlayer)
|
||||
}
|
||||
get userToken() {
|
||||
return this.ctx.$store.getters['user/getToken']
|
||||
@ -35,10 +35,10 @@ export default class PlayerHandler {
|
||||
return this.playerState === 'PLAYING'
|
||||
}
|
||||
|
||||
load(audiobook, playWhenReady, startTime = 0) {
|
||||
load(libraryItem, playWhenReady, startTime = 0) {
|
||||
if (!this.player) this.switchPlayer()
|
||||
|
||||
this.audiobook = audiobook
|
||||
this.libraryItem = libraryItem
|
||||
this.startTime = startTime
|
||||
this.playWhenReady = playWhenReady
|
||||
this.prepare()
|
||||
@ -58,8 +58,8 @@ export default class PlayerHandler {
|
||||
this.player = new CastPlayer(this.ctx)
|
||||
this.setPlayerListeners()
|
||||
|
||||
if (this.audiobook) {
|
||||
// Audiobook was already loaded - prepare for cast
|
||||
if (this.libraryItem) {
|
||||
// libraryItem was already loaded - prepare for cast
|
||||
this.playWhenReady = false
|
||||
this.prepare()
|
||||
}
|
||||
@ -75,8 +75,8 @@ export default class PlayerHandler {
|
||||
this.player = new LocalPlayer(this.ctx)
|
||||
this.setPlayerListeners()
|
||||
|
||||
if (this.audiobook) {
|
||||
// Audiobook was already loaded - prepare for local play
|
||||
if (this.libraryItem) {
|
||||
// libraryItem was already loaded - prepare for local play
|
||||
this.playWhenReady = false
|
||||
this.prepare()
|
||||
}
|
||||
@ -129,10 +129,15 @@ export default class PlayerHandler {
|
||||
var useHls = false
|
||||
|
||||
var runningTotal = 0
|
||||
var audioTracks = (this.audiobook.tracks || []).map((track) => {
|
||||
|
||||
var audioTracks = (this.libraryItem.media.tracks || []).map((track) => {
|
||||
if (!track.metadata) {
|
||||
console.error('INVALID TRACK', track)
|
||||
return null
|
||||
}
|
||||
var audioTrack = new AudioTrack(track)
|
||||
audioTrack.startOffset = runningTotal
|
||||
audioTrack.contentUrl = `/lib/${this.audiobook.libraryId}/${this.audiobook.folderId}/${this.ctx.$encodeUriPath(track.path)}?token=${this.userToken}`
|
||||
audioTrack.contentUrl = `/s/item/${this.libraryItem.id}/${this.ctx.$encodeUriPath(track.metadata.relPath.replace(/^\//, ''))}?token=${this.userToken}`
|
||||
audioTrack.mimeType = this.getMimeTypeForTrack(track)
|
||||
audioTrack.canDirectPlay = !!this.player.playableMimetypes[audioTrack.mimeType]
|
||||
|
||||
@ -156,7 +161,7 @@ export default class PlayerHandler {
|
||||
|
||||
|
||||
if (useHls) {
|
||||
var stream = await this.ctx.$axios.$get(`/api/books/${this.audiobook.id}/stream`).catch((error) => {
|
||||
var stream = await this.ctx.$axios.$get(`/api/items/${this.libraryItem.id}/stream`).catch((error) => {
|
||||
console.error('Failed to start stream', error)
|
||||
})
|
||||
if (stream) {
|
||||
@ -171,7 +176,7 @@ export default class PlayerHandler {
|
||||
}
|
||||
|
||||
getMimeTypeForTrack(track) {
|
||||
var ext = track.ext
|
||||
var ext = track.metadata.ext
|
||||
if (ext === '.mp3' || ext === '.m4b' || ext === '.m4a') {
|
||||
return 'audio/mpeg'
|
||||
} else if (ext === '.mp4') {
|
||||
@ -187,13 +192,13 @@ export default class PlayerHandler {
|
||||
}
|
||||
|
||||
closePlayer() {
|
||||
console.log('[PlayerHandler] CLose Player')
|
||||
console.log('[PlayerHandler] Close Player')
|
||||
if (this.player) {
|
||||
this.player.destroy()
|
||||
}
|
||||
this.player = null
|
||||
this.playerState = 'IDLE'
|
||||
this.audiobook = null
|
||||
this.libraryItem = null
|
||||
this.currentStreamId = null
|
||||
this.startTime = 0
|
||||
this.stopPlayInterval()
|
||||
@ -201,7 +206,7 @@ export default class PlayerHandler {
|
||||
|
||||
prepareStream(stream) {
|
||||
if (!this.player) this.switchPlayer()
|
||||
this.audiobook = stream.audiobook
|
||||
this.libraryItem = stream.libraryItem
|
||||
this.setHlsStream({
|
||||
streamId: stream.id,
|
||||
streamUrl: stream.clientPlaylistUri,
|
||||
@ -212,19 +217,19 @@ export default class PlayerHandler {
|
||||
setHlsStream(stream) {
|
||||
this.currentStreamId = stream.streamId
|
||||
var audioTrack = new AudioTrack({
|
||||
duration: this.audiobook.duration,
|
||||
duration: this.libraryItem.media.duration,
|
||||
contentUrl: stream.streamUrl + '?token=' + this.userToken,
|
||||
mimeType: 'application/vnd.apple.mpegurl'
|
||||
})
|
||||
this.startTime = stream.startTime
|
||||
this.ctx.playerLoading = true
|
||||
this.player.set(this.audiobook, [audioTrack], this.currentStreamId, stream.startTime, this.playWhenReady)
|
||||
this.player.set(this.libraryItem, [audioTrack], this.currentStreamId, stream.startTime, this.playWhenReady)
|
||||
}
|
||||
|
||||
setDirectPlay(audioTracks) {
|
||||
this.currentStreamId = null
|
||||
this.ctx.playerLoading = true
|
||||
this.player.set(this.audiobook, audioTracks, null, this.startTime, this.playWhenReady)
|
||||
this.player.set(this.libraryItem, audioTracks, null, this.startTime, this.playWhenReady)
|
||||
}
|
||||
|
||||
resetStream(startTime, streamId) {
|
||||
@ -265,21 +270,21 @@ export default class PlayerHandler {
|
||||
timeListened: listeningTimeToAdd,
|
||||
currentTime,
|
||||
streamId: this.currentStreamId,
|
||||
audiobookId: this.audiobook.id
|
||||
audiobookId: this.libraryItem.id
|
||||
}
|
||||
this.ctx.$axios.$post('/api/syncStream', syncData, { timeout: 1000 }).catch((error) => {
|
||||
console.error('Failed to update stream progress', error)
|
||||
})
|
||||
} else {
|
||||
// Direct play via chromecast does not yet have backend stream session model
|
||||
// so the progress update for the audiobook is updated this way (instead of through the stream)
|
||||
// so the progress update for the libraryItem is updated this way (instead of through the stream)
|
||||
var duration = this.getDuration()
|
||||
var syncData = {
|
||||
totalDuration: duration,
|
||||
currentTime,
|
||||
progress: duration > 0 ? currentTime / duration : 0,
|
||||
isRead: false,
|
||||
audiobookId: this.audiobook.id,
|
||||
audiobookId: this.libraryItem.id,
|
||||
lastUpdate: Date.now()
|
||||
}
|
||||
this.ctx.$axios.$post('/api/syncLocal', syncData, { timeout: 1000 }).catch((error) => {
|
||||
|
@ -4,7 +4,7 @@ import Vue from 'vue'
|
||||
export const state = () => ({
|
||||
versionData: null,
|
||||
serverSettings: null,
|
||||
streamAudiobook: null,
|
||||
streamLibraryItem: null,
|
||||
editModalTab: 'details',
|
||||
showEditModal: false,
|
||||
showEReader: false,
|
||||
@ -37,8 +37,8 @@ export const getters = {
|
||||
return state.serverSettings.coverAspectRatio === 0 ? 1.6 : 1
|
||||
},
|
||||
getNumAudiobooksSelected: state => state.selectedAudiobooks.length,
|
||||
getAudiobookIdStreaming: state => {
|
||||
return state.streamAudiobook ? state.streamAudiobook.id : null
|
||||
getLibraryItemIdStreaming: state => {
|
||||
return state.streamLibraryItem ? state.streamLibraryItem.id : null
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,8 +106,8 @@ export const mutations = {
|
||||
if (!settings) return
|
||||
state.serverSettings = settings
|
||||
},
|
||||
setStreamAudiobook(state, audiobook) {
|
||||
state.streamAudiobook = audiobook
|
||||
setLibraryItemStream(state, libraryItem) {
|
||||
state.streamLibraryItem = libraryItem
|
||||
},
|
||||
showEditModal(state, libraryItem) {
|
||||
state.editModalTab = 'details'
|
||||
|
@ -81,6 +81,7 @@ class ApiController {
|
||||
this.router.post('/items/:id/cover', LibraryItemController.middleware.bind(this), LibraryItemController.uploadCover.bind(this))
|
||||
this.router.patch('/items/:id/cover', LibraryItemController.middleware.bind(this), LibraryItemController.updateCover.bind(this))
|
||||
this.router.delete('/items/:id/cover', LibraryItemController.middleware.bind(this), LibraryItemController.removeCover.bind(this))
|
||||
this.router.get('/items/:id/stream', LibraryItemController.middleware.bind(this), LibraryItemController.openStream.bind(this))
|
||||
|
||||
//
|
||||
// Book Routes
|
||||
|
@ -15,10 +15,6 @@ class StreamManager {
|
||||
this.StreamsPath = Path.join(global.MetadataPath, 'streams')
|
||||
}
|
||||
|
||||
get audiobooks() {
|
||||
return this.db.audiobooks
|
||||
}
|
||||
|
||||
getStream(streamId) {
|
||||
return this.streams.find(s => s.id === streamId)
|
||||
}
|
||||
@ -27,12 +23,12 @@ class StreamManager {
|
||||
this.streams = this.streams.filter(s => s.id !== stream.id)
|
||||
}
|
||||
|
||||
async openStream(client, audiobook, transcodeOptions = {}) {
|
||||
async openStream(client, libraryItem, transcodeOptions = {}) {
|
||||
if (!client || !client.user) {
|
||||
Logger.error('[StreamManager] Cannot open stream invalid client', client)
|
||||
return
|
||||
}
|
||||
var stream = new Stream(this.StreamsPath, client, audiobook, transcodeOptions)
|
||||
var stream = new Stream(this.StreamsPath, client, libraryItem, transcodeOptions)
|
||||
|
||||
stream.on('closed', () => {
|
||||
this.removeStream(stream)
|
||||
@ -43,7 +39,7 @@ class StreamManager {
|
||||
await stream.generatePlaylist()
|
||||
stream.start()
|
||||
|
||||
Logger.info('Stream Opened for client', client.user.username, 'for audiobook', audiobook.title, 'with streamId', stream.id)
|
||||
Logger.info('Stream Opened for client', client.user.username, 'for item', stream.itemTitle, 'with streamId', stream.id)
|
||||
|
||||
client.stream = stream
|
||||
client.user.stream = stream.id
|
||||
@ -103,25 +99,25 @@ class StreamManager {
|
||||
}
|
||||
}
|
||||
|
||||
async openStreamApiRequest(res, user, audiobook) {
|
||||
Logger.info(`[StreamManager] User "${user.username}" open stream request for "${audiobook.title}"`)
|
||||
async openStreamApiRequest(res, user, libraryItem) {
|
||||
Logger.info(`[StreamManager] User "${user.username}" open stream request for "${libraryItem.media.metadata.title}"`)
|
||||
var client = {
|
||||
user
|
||||
}
|
||||
var stream = await this.openStream(client, audiobook)
|
||||
var stream = await this.openStream(client, libraryItem)
|
||||
this.db.updateUserStream(client.user.id, stream.id)
|
||||
|
||||
res.json({
|
||||
audiobookId: audiobook.id,
|
||||
libraryItemId: libraryItem.id,
|
||||
startTime: stream.startTime,
|
||||
streamId: stream.id,
|
||||
streamUrl: stream.clientPlaylistUri
|
||||
})
|
||||
}
|
||||
|
||||
async openStreamSocketRequest(socket, audiobookId) {
|
||||
Logger.info('[StreamManager] Open Stream Request', socket.id, audiobookId)
|
||||
var audiobook = this.audiobooks.find(ab => ab.id === audiobookId)
|
||||
async openStreamSocketRequest(socket, libraryItemId) {
|
||||
Logger.info('[StreamManager] Open Stream Request', socket.id, libraryItemId)
|
||||
var libraryItem = this.db.libraryItems.find(li => li.id === libraryItemId)
|
||||
var client = socket.sheepClient
|
||||
|
||||
if (client.stream) {
|
||||
@ -131,7 +127,7 @@ class StreamManager {
|
||||
client.stream = null
|
||||
}
|
||||
|
||||
var stream = await this.openStream(client, audiobook)
|
||||
var stream = await this.openStream(client, libraryItem)
|
||||
this.db.updateUserStream(client.user.id, stream.id)
|
||||
|
||||
this.emitter('user_stream_update', client.user.toJSONForPublic(this.streams))
|
||||
|
@ -177,6 +177,11 @@ class LibraryItemController {
|
||||
return this.cacheManager.handleCoverCache(res, libraryItem, options)
|
||||
}
|
||||
|
||||
// GET: api/items/:id/stream
|
||||
openStream(req, res) {
|
||||
this.streamManager.openStreamApiRequest(res, req.user, req.libraryItem)
|
||||
}
|
||||
|
||||
middleware(req, res, next) {
|
||||
var item = this.db.libraryItems.find(li => li.id === req.params.id)
|
||||
if (!item || !item.media) return res.sendStatus(404)
|
||||
|
@ -10,12 +10,12 @@ const hlsPlaylistGenerator = require('../utils/hlsPlaylistGenerator')
|
||||
const UserListeningSession = require('./UserListeningSession')
|
||||
|
||||
class Stream extends EventEmitter {
|
||||
constructor(streamPath, client, audiobook, transcodeOptions = {}) {
|
||||
constructor(streamPath, client, libraryItem, transcodeOptions = {}) {
|
||||
super()
|
||||
|
||||
this.id = getId('str')
|
||||
this.client = client
|
||||
this.audiobook = audiobook
|
||||
this.libraryItem = libraryItem
|
||||
|
||||
this.transcodeOptions = transcodeOptions
|
||||
|
||||
@ -37,7 +37,7 @@ class Stream extends EventEmitter {
|
||||
this.clientCurrentTime = 0
|
||||
|
||||
this.listeningSession = new UserListeningSession()
|
||||
this.listeningSession.setData(audiobook, client.user)
|
||||
this.listeningSession.setData(libraryItem, client.user)
|
||||
|
||||
this.init()
|
||||
}
|
||||
@ -46,21 +46,21 @@ class Stream extends EventEmitter {
|
||||
return this.client ? this.client.socket || null : null
|
||||
}
|
||||
|
||||
get audiobookId() {
|
||||
return this.audiobook.id
|
||||
get libraryItemId() {
|
||||
return this.libraryItem.id
|
||||
}
|
||||
|
||||
get audiobookTitle() {
|
||||
return this.audiobook ? this.audiobook.title : null
|
||||
get itemTitle() {
|
||||
return this.libraryItem ? this.libraryItem.media.metadata.title : null
|
||||
}
|
||||
|
||||
get totalDuration() {
|
||||
return this.audiobook.duration
|
||||
return this.libraryItem.media.duration
|
||||
}
|
||||
|
||||
get tracksAudioFileType() {
|
||||
if (!this.tracks.length) return null
|
||||
return this.tracks[0].ext.toLowerCase().slice(1)
|
||||
return this.tracks[0].metadata.ext.toLowerCase().slice(1)
|
||||
}
|
||||
|
||||
// Fmp4 does not work on iOS devices: https://github.com/advplyr/audiobookshelf-app/issues/85
|
||||
@ -90,7 +90,7 @@ class Stream extends EventEmitter {
|
||||
}
|
||||
|
||||
get tracks() {
|
||||
return this.audiobook.tracks
|
||||
return this.libraryItem.media.tracks
|
||||
}
|
||||
|
||||
get clientUser() {
|
||||
@ -106,7 +106,7 @@ class Stream extends EventEmitter {
|
||||
}
|
||||
|
||||
get clientUserAudiobookData() {
|
||||
return this.client ? this.clientUserAudiobooks[this.audiobookId] : null
|
||||
return this.client ? this.clientUserAudiobooks[this.libraryItemId] : null
|
||||
}
|
||||
|
||||
get clientPlaylistUri() {
|
||||
@ -132,7 +132,7 @@ class Stream extends EventEmitter {
|
||||
id: this.id,
|
||||
clientId: this.client.id,
|
||||
userId: this.client.user.id,
|
||||
audiobook: this.audiobook.toJSONExpanded(),
|
||||
libraryItem: this.libraryItem.toJSONExpanded(),
|
||||
segmentLength: this.segmentLength,
|
||||
playlistPath: this.playlistPath,
|
||||
clientPlaylistUri: this.clientPlaylistUri,
|
||||
@ -147,7 +147,7 @@ class Stream extends EventEmitter {
|
||||
init() {
|
||||
if (this.clientUserAudiobookData) {
|
||||
var timeRemaining = this.totalDuration - this.clientUserAudiobookData.currentTime
|
||||
Logger.info('[STREAM] User has progress for audiobook', this.clientUserAudiobookData.progress, `Time Remaining: ${timeRemaining}s`)
|
||||
Logger.info('[STREAM] User has progress for item', this.clientUserAudiobookData.progress, `Time Remaining: ${timeRemaining}s`)
|
||||
if (timeRemaining > 15) {
|
||||
this.startTime = this.clientUserAudiobookData.currentTime
|
||||
this.clientCurrentTime = this.startTime
|
||||
@ -194,7 +194,7 @@ class Stream extends EventEmitter {
|
||||
return null
|
||||
}
|
||||
this.listeningSession = new UserListeningSession()
|
||||
this.listeningSession.setData(this.audiobook, this.clientUser)
|
||||
this.listeningSession.setData(this.libraryItem, this.clientUser)
|
||||
Logger.debug(`[Stream] Listening session rolled to next day`)
|
||||
}
|
||||
|
||||
@ -284,7 +284,6 @@ class Stream extends EventEmitter {
|
||||
}
|
||||
|
||||
startLoop() {
|
||||
// Logger.info(`[Stream] ${this.audiobookTitle} (${this.id}) Start Loop`)
|
||||
if (this.socket) {
|
||||
this.socket.emit('stream_progress', { stream: this.id, chunks: [], numSegments: 0, percent: '0%' })
|
||||
}
|
||||
@ -295,7 +294,7 @@ class Stream extends EventEmitter {
|
||||
this.checkFiles()
|
||||
} else {
|
||||
if (this.socket) {
|
||||
Logger.info(`[Stream] ${this.audiobookTitle} sending stream_ready`)
|
||||
Logger.info(`[Stream] ${this.itemTitle} sending stream_ready`)
|
||||
this.socket.emit('stream_ready')
|
||||
}
|
||||
clearInterval(intervalId)
|
||||
|
@ -61,7 +61,7 @@ class UserAudiobookData {
|
||||
}
|
||||
|
||||
updateProgressFromStream(stream) {
|
||||
this.audiobookId = stream.audiobookId
|
||||
this.audiobookId = stream.libraryItemId
|
||||
this.totalDuration = stream.totalDuration
|
||||
this.progress = stream.clientProgress
|
||||
this.currentTime = stream.clientCurrentTime
|
||||
|
@ -61,14 +61,15 @@ class UserListeningSession {
|
||||
this.startedAt = session.startedAt
|
||||
}
|
||||
|
||||
setData(audiobook, user) {
|
||||
setData(libraryItem, user) {
|
||||
this.id = getId('ls')
|
||||
this.userId = user.id
|
||||
this.audiobookId = audiobook.id
|
||||
this.audiobookTitle = audiobook.title || ''
|
||||
this.audiobookAuthor = audiobook.authorFL || ''
|
||||
this.audiobookDuration = audiobook.duration || 0
|
||||
this.audiobookGenres = [...audiobook.genres]
|
||||
this.audiobookId = libraryItem.id
|
||||
// TODO: For podcasts this needs to be generic
|
||||
this.audiobookTitle = libraryItem.media.metadata.title || ''
|
||||
this.audiobookAuthor = libraryItem.media.metadata.authorName || ''
|
||||
this.audiobookDuration = libraryItem.media.duration || 0
|
||||
this.audiobookGenres = [...libraryItem.media.metadata.genres]
|
||||
|
||||
this.timeListening = 0
|
||||
this.lastUpdate = Date.now()
|
||||
|
@ -30,7 +30,7 @@ async function writeConcatFile(tracks, outputPath, startTime = 0) {
|
||||
|
||||
var tracksToInclude = tracks.filter(t => t.index >= trackToStartWithIndex)
|
||||
var trackPaths = tracksToInclude.map(t => {
|
||||
var line = 'file ' + escapeSingleQuotes(t.fullPath) + '\n' + `duration ${t.duration}`
|
||||
var line = 'file ' + escapeSingleQuotes(t.metadata.path) + '\n' + `duration ${t.duration}`
|
||||
return line
|
||||
})
|
||||
var inputstr = trackPaths.join('\n\n')
|
||||
|
Loading…
Reference in New Issue
Block a user