mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-12-20 19:06:06 +01:00
185 lines
6.2 KiB
Vue
185 lines
6.2 KiB
Vue
|
<template>
|
||
|
<div class="relative">
|
||
|
<!-- Track -->
|
||
|
<div ref="track" class="w-full h-2 bg-gray-700 relative cursor-pointer transform duration-100 hover:scale-y-125 overflow-hidden" @mousemove="mousemoveTrack" @mouseleave="mouseleaveTrack" @click.stop="clickTrack">
|
||
|
<div ref="readyTrack" class="h-full bg-gray-600 absolute top-0 left-0 pointer-events-none" />
|
||
|
<div ref="bufferTrack" class="h-full bg-gray-500 absolute top-0 left-0 pointer-events-none" />
|
||
|
<div ref="playedTrack" class="h-full bg-gray-200 absolute top-0 left-0 pointer-events-none" />
|
||
|
<div ref="trackCursor" class="h-full w-0.5 bg-gray-50 absolute top-0 left-0 opacity-0 pointer-events-none" />
|
||
|
<div v-if="loading" class="h-full w-1/4 absolute left-0 top-0 loadingTrack pointer-events-none bg-white bg-opacity-25" />
|
||
|
</div>
|
||
|
<div ref="track" class="w-full h-2 relative overflow-hidden">
|
||
|
<template v-for="(tick, index) in chapterTicks">
|
||
|
<div :key="index" :style="{ left: tick.left + 'px' }" class="absolute top-0 w-px bg-white bg-opacity-30 h-1 pointer-events-none" />
|
||
|
</template>
|
||
|
</div>
|
||
|
|
||
|
<!-- Hover timestamp -->
|
||
|
<div ref="hoverTimestamp" class="absolute -top-8 left-0 bg-white text-black rounded-full opacity-0 pointer-events-none">
|
||
|
<p ref="hoverTimestampText" class="text-xs font-mono text-center px-2 py-0.5 truncate whitespace-nowrap">00:00</p>
|
||
|
</div>
|
||
|
<div ref="hoverTimestampArrow" class="absolute -top-3 left-0 bg-white text-black rounded-full opacity-0 pointer-events-none">
|
||
|
<div class="absolute -bottom-1.5 left-0 right-0 w-full flex justify-center">
|
||
|
<div class="arrow-down" />
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</template>
|
||
|
|
||
|
<script>
|
||
|
export default {
|
||
|
props: {
|
||
|
loading: Boolean,
|
||
|
duration: Number,
|
||
|
chapters: {
|
||
|
type: Array,
|
||
|
default: () => []
|
||
|
}
|
||
|
},
|
||
|
data() {
|
||
|
return {
|
||
|
trackWidth: 0,
|
||
|
currentTime: 0,
|
||
|
percentReady: 0,
|
||
|
bufferTime: 0,
|
||
|
chapterTicks: [],
|
||
|
trackOffsetLeft: 16, // Track is 16px from edge
|
||
|
playedTrackWidth: 0,
|
||
|
readyTrackWidth: 0,
|
||
|
bufferTrackWidth: 0
|
||
|
}
|
||
|
},
|
||
|
watch: {
|
||
|
duration: {
|
||
|
immediate: true,
|
||
|
handler() {
|
||
|
this.setChapterTicks()
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
computed: {},
|
||
|
methods: {
|
||
|
clickTrack(e) {
|
||
|
if (this.loading) return
|
||
|
|
||
|
var offsetX = e.offsetX
|
||
|
var perc = offsetX / this.trackWidth
|
||
|
var time = perc * this.duration
|
||
|
if (isNaN(time) || time === null) {
|
||
|
console.error('Invalid time', perc, time)
|
||
|
return
|
||
|
}
|
||
|
this.$emit('seek', time)
|
||
|
},
|
||
|
setBufferTime(time) {
|
||
|
this.bufferTime = time
|
||
|
this.updateBufferTrack()
|
||
|
},
|
||
|
updateBufferTrack() {
|
||
|
var bufferlen = (this.bufferTime / this.duration) * this.trackWidth
|
||
|
bufferlen = Math.round(bufferlen)
|
||
|
if (this.bufferTrackWidth === bufferlen || !this.$refs.bufferTrack) return
|
||
|
if (this.$refs.bufferTrack) this.$refs.bufferTrack.style.width = bufferlen + 'px'
|
||
|
this.bufferTrackWidth = bufferlen
|
||
|
},
|
||
|
setPercentageReady(percent) {
|
||
|
this.percentReady = percent
|
||
|
this.updateReadyTrack()
|
||
|
},
|
||
|
updateReadyTrack() {
|
||
|
var widthReady = Math.round(this.trackWidth * this.percentReady)
|
||
|
if (this.readyTrackWidth === widthReady) return
|
||
|
this.readyTrackWidth = widthReady
|
||
|
if (this.$refs.readyTrack) this.$refs.readyTrack.style.width = widthReady + 'px'
|
||
|
},
|
||
|
setCurrentTime(time) {
|
||
|
this.currentTime = time
|
||
|
this.updatePlayedTrackWidth()
|
||
|
},
|
||
|
updatePlayedTrackWidth() {
|
||
|
var perc = this.currentTime / this.duration
|
||
|
var ptWidth = Math.round(perc * this.trackWidth)
|
||
|
if (this.playedTrackWidth === ptWidth) {
|
||
|
return
|
||
|
}
|
||
|
if (this.$refs.playedTrack) this.$refs.playedTrack.style.width = ptWidth + 'px'
|
||
|
this.playedTrackWidth = ptWidth
|
||
|
},
|
||
|
setChapterTicks() {
|
||
|
this.chapterTicks = this.chapters.map((chap) => {
|
||
|
var perc = chap.start / this.duration
|
||
|
return {
|
||
|
title: chap.title,
|
||
|
left: perc * this.trackWidth
|
||
|
}
|
||
|
})
|
||
|
},
|
||
|
mousemoveTrack(e) {
|
||
|
var offsetX = e.offsetX
|
||
|
var time = (offsetX / this.trackWidth) * this.duration
|
||
|
|
||
|
console.log('Mousemove track', this.trackWidth, this.duration)
|
||
|
|
||
|
if (this.$refs.hoverTimestamp) {
|
||
|
var width = this.$refs.hoverTimestamp.clientWidth
|
||
|
this.$refs.hoverTimestamp.style.opacity = 1
|
||
|
var posLeft = offsetX - width / 2
|
||
|
if (posLeft + width + this.trackOffsetLeft > window.innerWidth) {
|
||
|
posLeft = window.innerWidth - width - this.trackOffsetLeft
|
||
|
} else if (posLeft < -this.trackOffsetLeft) {
|
||
|
posLeft = -this.trackOffsetLeft
|
||
|
}
|
||
|
this.$refs.hoverTimestamp.style.left = posLeft + 'px'
|
||
|
}
|
||
|
|
||
|
if (this.$refs.hoverTimestampArrow) {
|
||
|
var width = this.$refs.hoverTimestampArrow.clientWidth
|
||
|
var posLeft = offsetX - width / 2
|
||
|
this.$refs.hoverTimestampArrow.style.opacity = 1
|
||
|
this.$refs.hoverTimestampArrow.style.left = posLeft + 'px'
|
||
|
}
|
||
|
if (this.$refs.hoverTimestampText) {
|
||
|
var hoverText = this.$secondsToTimestamp(time)
|
||
|
|
||
|
var chapter = this.chapters.find((chapter) => chapter.start <= time && time < chapter.end)
|
||
|
if (chapter && chapter.title) {
|
||
|
hoverText += ` - ${chapter.title}`
|
||
|
}
|
||
|
this.$refs.hoverTimestampText.innerText = hoverText
|
||
|
}
|
||
|
if (this.$refs.trackCursor) {
|
||
|
this.$refs.trackCursor.style.opacity = 1
|
||
|
this.$refs.trackCursor.style.left = offsetX - 1 + 'px'
|
||
|
}
|
||
|
},
|
||
|
mouseleaveTrack() {
|
||
|
if (this.$refs.hoverTimestamp) {
|
||
|
this.$refs.hoverTimestamp.style.opacity = 0
|
||
|
}
|
||
|
if (this.$refs.hoverTimestampArrow) {
|
||
|
this.$refs.hoverTimestampArrow.style.opacity = 0
|
||
|
}
|
||
|
if (this.$refs.trackCursor) {
|
||
|
this.$refs.trackCursor.style.opacity = 0
|
||
|
}
|
||
|
},
|
||
|
setTrackWidth() {
|
||
|
if (this.$refs.track) {
|
||
|
this.trackWidth = this.$refs.track.clientWidth
|
||
|
} else {
|
||
|
console.error('Track not loaded', this.$refs)
|
||
|
}
|
||
|
},
|
||
|
windowResize() {
|
||
|
this.setTrackWidth()
|
||
|
}
|
||
|
},
|
||
|
mounted() {
|
||
|
this.setTrackWidth()
|
||
|
window.addEventListener('resize', this.windowResize)
|
||
|
},
|
||
|
beforeDestroy() {
|
||
|
window.removeEventListener('resize', this.windowResize)
|
||
|
}
|
||
|
}
|
||
|
</script>
|