mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-11-07 16:44:16 +01:00
Add:Experimental direct play support
This commit is contained in:
parent
eb82d9c300
commit
ecf62c5443
@ -114,7 +114,8 @@ export default {
|
||||
showChaptersModal: false,
|
||||
currentTime: 0,
|
||||
trackOffsetLeft: 16, // Track is 16px from edge
|
||||
duration: 0
|
||||
duration: 0,
|
||||
chapterTicks: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -138,7 +139,6 @@ export default {
|
||||
},
|
||||
timeRemainingPretty() {
|
||||
if (this.timeRemaining < 0) {
|
||||
console.warn('Time remaining < 0', this.duration, this.currentTime, this.timeRemaining)
|
||||
return this.$secondsToTimestamp(this.timeRemaining * -1)
|
||||
}
|
||||
return '-' + this.$secondsToTimestamp(this.timeRemaining)
|
||||
@ -147,15 +147,6 @@ export default {
|
||||
if (!this.duration) return 0
|
||||
return Math.round((100 * this.currentTime) / this.duration)
|
||||
},
|
||||
chapterTicks() {
|
||||
return this.chapters.map((chap) => {
|
||||
var perc = chap.start / this.duration
|
||||
return {
|
||||
title: chap.title,
|
||||
left: perc * this.trackWidth
|
||||
}
|
||||
})
|
||||
},
|
||||
currentChapter() {
|
||||
return this.chapters.find((chapter) => chapter.start <= this.currentTime && this.currentTime < chapter.end)
|
||||
},
|
||||
@ -169,6 +160,14 @@ export default {
|
||||
methods: {
|
||||
setDuration(duration) {
|
||||
this.duration = duration
|
||||
|
||||
this.chapterTicks = this.chapters.map((chap) => {
|
||||
var perc = chap.start / this.duration
|
||||
return {
|
||||
title: chap.title,
|
||||
left: perc * this.trackWidth
|
||||
}
|
||||
})
|
||||
},
|
||||
setCurrentTime(time) {
|
||||
this.currentTime = time
|
||||
|
@ -72,6 +72,9 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showExperimentalFeatures() {
|
||||
return this.$store.state.showExperimentalFeatures
|
||||
},
|
||||
coverAspectRatio() {
|
||||
return this.$store.getters['getServerSetting']('coverAspectRatio')
|
||||
},
|
||||
|
@ -17,6 +17,8 @@ export default class CastPlayer extends EventEmitter {
|
||||
this.playWhenReady = false
|
||||
this.defaultPlaybackRate = 1
|
||||
|
||||
this.playableMimetypes = {}
|
||||
|
||||
this.coverUrl = ''
|
||||
this.castPlayerState = 'IDLE'
|
||||
|
||||
|
@ -14,10 +14,13 @@ export default class LocalPlayer extends EventEmitter {
|
||||
this.hlsStreamId = null
|
||||
this.hlsInstance = null
|
||||
this.usingNativeplayer = false
|
||||
this.currentTime = 0
|
||||
this.startTime = 0
|
||||
this.trackStartTime = 0
|
||||
this.playWhenReady = false
|
||||
this.defaultPlaybackRate = 1
|
||||
|
||||
this.playableMimetypes = {}
|
||||
|
||||
this.initialize()
|
||||
}
|
||||
|
||||
@ -38,9 +41,16 @@ export default class LocalPlayer extends EventEmitter {
|
||||
this.player.addEventListener('play', this.evtPlay.bind(this))
|
||||
this.player.addEventListener('pause', this.evtPause.bind(this))
|
||||
this.player.addEventListener('progress', this.evtProgress.bind(this))
|
||||
this.player.addEventListener('ended', this.evtEnded.bind(this))
|
||||
this.player.addEventListener('error', this.evtError.bind(this))
|
||||
this.player.addEventListener('loadedmetadata', this.evtLoadedMetadata.bind(this))
|
||||
this.player.addEventListener('timeupdate', this.evtTimeupdate.bind(this))
|
||||
|
||||
var mimeTypes = ['audio/flac', 'audio/mpeg', 'audio/mp4', 'audio/ogg', 'audio/aac']
|
||||
mimeTypes.forEach((mt) => {
|
||||
this.playableMimetypes[mt] = this.player.canPlayType(mt)
|
||||
})
|
||||
console.log(`[LocalPlayer] Supported mime types`, this.playableMimetypes)
|
||||
}
|
||||
|
||||
evtPlay() {
|
||||
@ -53,11 +63,27 @@ export default class LocalPlayer extends EventEmitter {
|
||||
var lastBufferTime = this.getLastBufferedTime()
|
||||
this.emit('buffertimeUpdate', lastBufferTime)
|
||||
}
|
||||
evtEnded() {
|
||||
if (this.currentTrackIndex < this.audioTracks.length - 1) {
|
||||
console.log(`[LocalPlayer] Track ended - loading next track ${this.currentTrackIndex + 1}`)
|
||||
// Has next track
|
||||
this.currentTrackIndex++
|
||||
this.playWhenReady = true
|
||||
this.startTime = this.currentTrack.startOffset
|
||||
this.loadCurrentTrack()
|
||||
} else {
|
||||
console.log(`[LocalPlayer] Ended`)
|
||||
}
|
||||
}
|
||||
evtError(error) {
|
||||
console.error('Player error', error)
|
||||
this.emit('error', error)
|
||||
}
|
||||
evtLoadedMetadata(data) {
|
||||
console.log('Audio Loaded Metadata', data)
|
||||
if (!this.hlsStreamId) {
|
||||
this.player.currentTime = this.trackStartTime
|
||||
}
|
||||
|
||||
this.emit('stateChange', 'LOADED')
|
||||
if (this.playWhenReady) {
|
||||
this.playWhenReady = false
|
||||
@ -89,23 +115,33 @@ export default class LocalPlayer extends EventEmitter {
|
||||
this.audioTracks = tracks
|
||||
this.hlsStreamId = hlsStreamId
|
||||
this.playWhenReady = playWhenReady
|
||||
this.startTime = startTime
|
||||
|
||||
if (this.hlsInstance) {
|
||||
this.destroyHlsInstance()
|
||||
}
|
||||
|
||||
this.currentTime = startTime
|
||||
if (this.hlsStreamId) {
|
||||
this.setHlsStream()
|
||||
} else {
|
||||
this.setDirectPlay()
|
||||
}
|
||||
}
|
||||
|
||||
setHlsStream() {
|
||||
this.trackStartTime = 0
|
||||
|
||||
// iOS does not support Media Elements but allows for HLS in the native audio player
|
||||
if (!Hls.isSupported()) {
|
||||
console.warn('HLS is not supported - fallback to using audio element')
|
||||
this.usingNativeplayer = true
|
||||
this.player.src = this.currentTrack.relativeContentUrl
|
||||
this.player.currentTime = this.currentTime
|
||||
this.player.currentTime = this.startTime
|
||||
return
|
||||
}
|
||||
|
||||
var hlsOptions = {
|
||||
startPosition: this.currentTime || -1
|
||||
startPosition: this.startTime || -1
|
||||
// No longer needed because token is put in a query string
|
||||
// xhrSetup: (xhr) => {
|
||||
// xhr.setRequestHeader('Authorization', `Bearer ${this.token}`)
|
||||
@ -133,6 +169,23 @@ export default class LocalPlayer extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
setDirectPlay() {
|
||||
// Set initial track and track time offset
|
||||
var trackIndex = this.audioTracks.findIndex(t => this.startTime >= t.startOffset && this.startTime < (t.startOffset + t.duration))
|
||||
this.currentTrackIndex = trackIndex >= 0 ? trackIndex : 0
|
||||
|
||||
this.loadCurrentTrack()
|
||||
}
|
||||
|
||||
loadCurrentTrack() {
|
||||
if (!this.currentTrack) return
|
||||
// When direct play track is loaded current time needs to be set
|
||||
this.trackStartTime = Math.max(0, this.startTime - (this.currentTrack.startOffset || 0))
|
||||
this.player.src = this.currentTrack.relativeContentUrl
|
||||
console.log(`[LocalPlayer] Loading track src ${this.currentTrack.relativeContentUrl}`)
|
||||
this.player.load()
|
||||
}
|
||||
|
||||
destroyHlsInstance() {
|
||||
if (!this.hlsInstance) return
|
||||
if (this.hlsInstance.destroy) {
|
||||
@ -181,8 +234,31 @@ export default class LocalPlayer extends EventEmitter {
|
||||
|
||||
seek(time) {
|
||||
if (!this.player) return
|
||||
var offsetTime = time - (this.currentTrack.startOffset || 0)
|
||||
this.player.currentTime = Math.max(0, offsetTime)
|
||||
if (this.hlsStreamId) {
|
||||
// Seeking HLS stream
|
||||
var offsetTime = time - (this.currentTrack.startOffset || 0)
|
||||
this.player.currentTime = Math.max(0, offsetTime)
|
||||
} else {
|
||||
// Seeking Direct play
|
||||
if (time < this.currentTrack.startOffset || time > this.currentTrack.startOffset + this.currentTrack.duration) {
|
||||
// Change Track
|
||||
var trackIndex = this.audioTracks.findIndex(t => time >= t.startOffset && time < (t.startOffset + t.duration))
|
||||
if (trackIndex >= 0) {
|
||||
this.startTime = time
|
||||
this.currentTrackIndex = trackIndex
|
||||
|
||||
if (!this.player.paused) {
|
||||
// audio player playing so play when track loads
|
||||
this.playWhenReady = true
|
||||
}
|
||||
this.loadCurrentTrack()
|
||||
}
|
||||
} else {
|
||||
var offsetTime = time - (this.currentTrack.startOffset || 0)
|
||||
this.player.currentTime = Math.max(0, offsetTime)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setVolume(volume) {
|
||||
|
@ -38,7 +38,6 @@ export default class PlayerHandler {
|
||||
load(audiobook, playWhenReady, startTime = 0) {
|
||||
if (!this.player) this.switchPlayer()
|
||||
|
||||
console.log('Load audiobook', audiobook)
|
||||
this.audiobook = audiobook
|
||||
this.startTime = startTime
|
||||
this.playWhenReady = playWhenReady
|
||||
@ -88,6 +87,15 @@ export default class PlayerHandler {
|
||||
this.player.on('stateChange', this.playerStateChange.bind(this))
|
||||
this.player.on('timeupdate', this.playerTimeupdate.bind(this))
|
||||
this.player.on('buffertimeUpdate', this.playerBufferTimeUpdate.bind(this))
|
||||
this.player.on('error', this.playerError.bind(this))
|
||||
}
|
||||
|
||||
playerError() {
|
||||
// Switch to HLS stream on error
|
||||
if (!this.isCasting && !this.currentStreamId && (this.player instanceof LocalPlayer)) {
|
||||
console.log(`[PlayerHandler] Audio player error switching to HLS stream`)
|
||||
this.prepare(true)
|
||||
}
|
||||
}
|
||||
|
||||
playerStateChange(state) {
|
||||
@ -117,8 +125,36 @@ export default class PlayerHandler {
|
||||
this.ctx.setBufferTime(buffertime)
|
||||
}
|
||||
|
||||
async prepare() {
|
||||
var useHls = !this.isCasting
|
||||
async prepare(forceHls = false) {
|
||||
var useHls = false
|
||||
|
||||
var runningTotal = 0
|
||||
var audioTracks = (this.audiobook.tracks || []).map((track) => {
|
||||
var audioTrack = new AudioTrack(track)
|
||||
audioTrack.startOffset = runningTotal
|
||||
audioTrack.contentUrl = `/lib/${this.audiobook.libraryId}/${this.audiobook.folderId}/${track.path}?token=${this.userToken}`
|
||||
audioTrack.mimeType = this.getMimeTypeForTrack(track)
|
||||
audioTrack.canDirectPlay = !!this.player.playableMimetypes[audioTrack.mimeType]
|
||||
|
||||
runningTotal += audioTrack.duration
|
||||
return audioTrack
|
||||
})
|
||||
|
||||
// All html5 audio player plays use HLS unless experimental features is on
|
||||
if (!this.isCasting) {
|
||||
if (forceHls || !this.ctx.showExperimentalFeatures) {
|
||||
useHls = true
|
||||
} else {
|
||||
// Use HLS if any audio track cannot be direct played
|
||||
useHls = !!audioTracks.find(at => !at.canDirectPlay)
|
||||
|
||||
if (useHls) {
|
||||
console.warn(`[PlayerHandler] An audio track cannot be direct played`, audioTracks.find(at => !at.canDirectPlay))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (useHls) {
|
||||
var stream = await this.ctx.$axios.$get(`/api/books/${this.audiobook.id}/stream`).catch((error) => {
|
||||
console.error('Failed to start stream', error)
|
||||
@ -126,23 +162,30 @@ export default class PlayerHandler {
|
||||
if (stream) {
|
||||
console.log(`[PlayerHandler] prepare hls stream`, stream)
|
||||
this.setHlsStream(stream)
|
||||
} else {
|
||||
console.error(`[PlayerHandler] Failed to start HLS stream`)
|
||||
}
|
||||
} else {
|
||||
// Setup tracks
|
||||
var runningTotal = 0
|
||||
var audioTracks = (this.audiobook.tracks || []).map((track) => {
|
||||
var audioTrack = new AudioTrack(track)
|
||||
audioTrack.startOffset = runningTotal
|
||||
audioTrack.contentUrl = `/lib/${this.audiobook.libraryId}/${this.audiobook.folderId}/${track.path}?token=${this.userToken}`
|
||||
audioTrack.mimeType = (track.codec === 'm4b' || track.codec === 'm4a') ? 'audio/mp4' : `audio/${track.codec}`
|
||||
|
||||
runningTotal += audioTrack.duration
|
||||
return audioTrack
|
||||
})
|
||||
this.setDirectPlay(audioTracks)
|
||||
}
|
||||
}
|
||||
|
||||
getMimeTypeForTrack(track) {
|
||||
var ext = track.ext
|
||||
if (ext === '.mp3' || ext === '.m4b' || ext === '.m4a') {
|
||||
return 'audio/mpeg'
|
||||
} else if (ext === '.mp4') {
|
||||
return 'audio/mp4'
|
||||
} else if (ext === '.ogg') {
|
||||
return 'audio/ogg'
|
||||
} else if (ext === '.aac' || ext === '.m4p') {
|
||||
return 'audio/aac'
|
||||
} else if (ext === '.flac') {
|
||||
return 'audio/flac'
|
||||
}
|
||||
return 'audio/mpeg'
|
||||
}
|
||||
|
||||
closePlayer() {
|
||||
console.log('[PlayerHandler] CLose Player')
|
||||
if (this.player) {
|
||||
|
Loading…
Reference in New Issue
Block a user