diff --git a/components/app/AudioPlayer.vue b/components/app/AudioPlayer.vue
index 9f9e58fc..ec1940e5 100644
--- a/components/app/AudioPlayer.vue
+++ b/components/app/AudioPlayer.vue
@@ -7,8 +7,24 @@
@@ -110,7 +125,6 @@ export default {
src: null,
volume: 0.5,
readyTrackWidth: 0,
- bufferTrackWidth: 0,
playedTrackWidth: 0,
seekedTime: 0,
seekLoading: false,
@@ -122,7 +136,8 @@ export default {
touchEndY: 0,
listenTimeInterval: null,
listeningTimeSinceLastUpdate: 0,
- totalListeningTimeInSession: 0
+ totalListeningTimeInSession: 0,
+ useChapterTrack: false
}
},
computed: {
@@ -134,6 +149,20 @@ export default {
this.$emit('update:playing', val)
}
},
+ menuItems() {
+ var items = []
+ items.push({
+ text: 'Chapter Track',
+ value: 'chapter_track',
+ icon: this.useChapterTrack ? 'check_box' : 'check_box_outline_blank'
+ })
+ items.push({
+ text: 'Close Player',
+ value: 'close',
+ icon: 'close'
+ })
+ return items
+ },
bookCoverAspectRatio() {
return this.$store.getters['getBookCoverAspectRatio']
},
@@ -157,24 +186,43 @@ export default {
},
currentChapter() {
if (!this.audiobook || !this.chapters.length) return null
- return this.chapters.find((ch) => ch.start <= this.currentTime && ch.end > this.currentTime)
+ return this.chapters.find((ch) => Number(ch.start.toFixed(2)) <= this.currentTime && Number(ch.end.toFixed(2)) > this.currentTime)
},
nextChapter() {
if (!this.chapters.length) return
- return this.chapters.find((c) => c.start >= this.currentTime)
+ return this.chapters.find((c) => Number(c.start.toFixed(2)) > this.currentTime)
},
currentChapterTitle() {
return this.currentChapter ? this.currentChapter.title : ''
},
+ currentChapterDuration() {
+ return this.currentChapter ? this.currentChapter.end - this.currentChapter.start : this.totalDuration
+ },
downloadedCover() {
return this.download ? this.download.cover : null
},
totalDurationPretty() {
return this.$secondsToTimestamp(this.totalDuration)
},
+ currentTimePretty() {
+ return this.$secondsToTimestamp(this.currentTime)
+ },
timeRemaining() {
+ if (this.useChapterTrack && this.currentChapter) {
+ var currChapTime = this.currentTime - this.currentChapter.start
+ return (this.currentChapterDuration - currChapTime) / this.currentPlaybackRate
+ }
+ return this.totalTimeRemaining
+ },
+ totalTimeRemaining() {
return (this.totalDuration - this.currentTime) / this.currentPlaybackRate
},
+ totalTimeRemainingPretty() {
+ if (this.totalTimeRemaining < 0) {
+ return this.$secondsToTimestamp(this.totalTimeRemaining * -1)
+ }
+ return '-' + this.$secondsToTimestamp(this.totalTimeRemaining)
+ },
timeRemainingPretty() {
if (this.timeRemaining < 0) {
return this.$secondsToTimestamp(this.timeRemaining * -1)
@@ -262,6 +310,11 @@ export default {
},
clickContainer() {
this.showFullscreen = true
+
+ // Update track for total time bar if useChapterTrack is set
+ this.$nextTick(() => {
+ this.updateTrack()
+ })
},
collapseFullscreen() {
this.showFullscreen = false
@@ -309,7 +362,7 @@ export default {
},
setStreamReady() {
this.readyTrackWidth = this.trackWidth
- this.$refs.readyTrack.style.width = this.trackWidth + 'px'
+ this.updateReadyTrack()
},
setChunksReady(chunks, numSegments) {
var largestSeg = 0
@@ -329,7 +382,17 @@ export default {
return
}
this.readyTrackWidth = widthReady
- this.$refs.readyTrack.style.width = widthReady + 'px'
+ this.updateReadyTrack()
+ },
+ updateReadyTrack() {
+ if (this.useChapterTrack) {
+ if (this.$refs.totalReadyTrack) {
+ this.$refs.totalReadyTrack.style.width = this.readyTrackWidth + 'px'
+ }
+ this.$refs.readyTrack.style.width = this.trackWidth + 'px'
+ } else {
+ this.$refs.readyTrack.style.width = this.readyTrackWidth + 'px'
+ }
},
updateTimestamp() {
var ts = this.$refs.currentTimestamp
@@ -337,8 +400,14 @@ export default {
console.error('No timestamp el')
return
}
- var currTimeClean = this.$secondsToTimestamp(this.currentTime)
- ts.innerText = currTimeClean
+ var currTimeStr = ''
+ if (this.useChapterTrack && this.currentChapter) {
+ var currChapTime = Math.max(0, this.currentTime - this.currentChapter.start)
+ currTimeStr = this.$secondsToTimestamp(currChapTime)
+ } else {
+ currTimeStr = this.$secondsToTimestamp(this.currentTime)
+ }
+ ts.innerText = currTimeStr
},
timeupdate() {
if (!this.$refs.playedTrack) {
@@ -356,16 +425,25 @@ export default {
}
this.updateTimestamp()
- // if (this.noSyncUpdateTime) this.noSyncUpdateTime = false
- // else this.sendStreamUpdate()
-
- var perc = this.currentTime / this.totalDuration
- var ptWidth = Math.round(perc * this.trackWidth)
+ this.updateTrack()
+ },
+ updateTrack() {
+ var percentDone = this.currentTime / this.totalDuration
+ var totalPercentDone = percentDone
+ if (this.useChapterTrack && this.currentChapter) {
+ var currChapTime = this.currentTime - this.currentChapter.start
+ percentDone = currChapTime / this.currentChapterDuration
+ }
+ var ptWidth = Math.round(percentDone * this.trackWidth)
if (this.playedTrackWidth === ptWidth) {
return
}
this.$refs.playedTrack.style.width = ptWidth + 'px'
this.playedTrackWidth = ptWidth
+
+ if (this.useChapterTrack) {
+ this.$refs.totalPlayedTrack.style.width = Math.round(totalPercentDone * this.trackWidth) + 'px'
+ }
},
seek(time) {
if (this.loading) return
@@ -398,7 +476,12 @@ export default {
var offsetX = e.offsetX
var perc = offsetX / this.trackWidth
- var time = perc * this.totalDuration
+ var time = 0
+ if (this.useChapterTrack && this.currentChapter) {
+ time = perc * this.currentChapterDuration + this.currentChapter.start
+ } else {
+ time = perc * this.totalDuration
+ }
if (isNaN(time) || time === null) {
console.error('Invalid time', perc, time)
return
@@ -548,7 +631,6 @@ export default {
}
},
onMetadata(data) {
- console.log('Native Audio On Metadata', JSON.stringify(data))
this.totalDuration = Number((data.duration / 1000).toFixed(2))
this.$emit('setTotalDuration', this.totalDuration)
this.currentTime = Number((data.currentTime / 1000).toFixed(2))
@@ -558,11 +640,11 @@ export default {
this.setFromObj()
}
- // if (this.stateName === 'ready_no_sync' || this.stateName === 'buffering_no_sync') this.noSyncUpdateTime = true
-
this.timeupdate()
},
- init() {
+ async init() {
+ this.useChapterTrack = await this.$localStore.getUseChapterTrack()
+
this.onPlayingUpdateListener = MyNativeAudio.addListener('onPlayingUpdate', this.onPlayingUpdate)
this.onMetadataListener = MyNativeAudio.addListener('onMetadata', this.onMetadata)
@@ -575,10 +657,7 @@ export default {
handleGesture() {
var touchDistance = this.touchEndY - this.touchStartY
if (touchDistance > 100) {
- console.log('Collapsing')
this.collapseFullscreen()
- } else {
- console.log('Not collapsing touch distance =', touchDistance)
}
},
touchstart(e) {
@@ -601,6 +680,20 @@ export default {
return
}
this.handleGesture()
+ },
+ clickMenuAction(action) {
+ if (action === 'chapter_track') {
+ this.useChapterTrack = !this.useChapterTrack
+
+ this.$nextTick(() => {
+ this.updateTimestamp()
+ this.updateTrack()
+ this.updateReadyTrack()
+ })
+ this.$localStore.setUseChapterTrack(this.useChapterTrack)
+ } else if (action === 'close') {
+ this.$emit('close')
+ }
}
},
mounted() {
@@ -654,6 +747,12 @@ export default {
height: 60px;
}
+.total-track {
+ bottom: 215px;
+ left: 0;
+ right: 0;
+}
+
.title-author-texts {
transition: all 0.15s cubic-bezier(0.39, 0.575, 0.565, 1);
transition-property: left, bottom, width, height;
diff --git a/components/app/AudioPlayerContainer.vue b/components/app/AudioPlayerContainer.vue
index 833d2804..16ce7b2b 100644
--- a/components/app/AudioPlayerContainer.vue
+++ b/components/app/AudioPlayerContainer.vue
@@ -22,7 +22,7 @@
/>
-
+
@@ -411,11 +411,14 @@ export default {
this.streamOpen(this.$server.stream)
}
},
- changePlaybackSpeed(speed) {
- console.log(`[AudioPlayerContainer] Change Playback Speed: ${speed}`)
+ updatePlaybackSpeed(speed) {
if (this.$refs.audioPlayer) {
+ console.log(`[AudioPlayerContainer] Update Playback Speed: ${speed}`)
this.$refs.audioPlayer.setPlaybackSpeed(speed)
}
+ },
+ changePlaybackSpeed(speed) {
+ console.log(`[AudioPlayerContainer] Change Playback Speed: ${speed}`)
this.$store.dispatch('user/updateUserSettings', { playbackRate: speed })
},
settingsUpdated(settings) {
diff --git a/components/modals/ChaptersModal.vue b/components/modals/ChaptersModal.vue
index 18ec54d6..62d6bbbb 100644
--- a/components/modals/ChaptersModal.vue
+++ b/components/modals/ChaptersModal.vue
@@ -11,10 +11,9 @@
-
-
+
{{ chapter.title }}
-
-
+
{{ $secondsToTimestamp(chapter.start) }}
diff --git a/components/modals/PlaybackSpeedModal.vue b/components/modals/PlaybackSpeedModal.vue
index 85693e05..08e2030a 100644
--- a/components/modals/PlaybackSpeedModal.vue
+++ b/components/modals/PlaybackSpeedModal.vue
@@ -6,9 +6,9 @@
-
+
-
+
-
@@ -17,6 +17,17 @@
+
+
+
+
{{ playbackRate }}тип
+
+
+
@@ -26,10 +37,21 @@
export default {
props: {
value: Boolean,
- playbackSpeed: Number
+ playbackRate: Number
},
data() {
- return {}
+ return {
+ currentPlaybackRate: 0,
+ MIN_SPEED: 0.5,
+ MAX_SPEED: 3
+ }
+ },
+ watch: {
+ show(newVal) {
+ if (newVal) {
+ this.currentPlaybackRate = this.selected
+ }
+ }
},
computed: {
show: {
@@ -42,27 +64,56 @@ export default {
},
selected: {
get() {
- return this.playbackSpeed
+ return this.playbackRate
},
set(val) {
- this.$emit('update:playbackSpeed', val)
+ this.$emit('update:playbackRate', val)
}
},
rates() {
- return [0.25, 0.5, 0.8, 1, 1.3, 1.5, 2, 2.5, 3]
+ return [0.5, 1, 1.2, 1.5, 2]
+ },
+ canIncrement() {
+ return this.playbackRate + 0.1 <= this.MAX_SPEED
+ },
+ canDecrement() {
+ return this.playbackRate - 0.1 >= this.MIN_SPEED
}
},
methods: {
- clickedOption(speed) {
- if (this.selected === speed) {
- this.show = false
- return
+ increment() {
+ if (this.selected + 0.1 > this.MAX_SPEED) return
+ var newPlaybackRate = this.selected + 0.1
+ this.selected = Number(newPlaybackRate.toFixed(1))
+ },
+ decrement() {
+ if (this.selected - 0.1 < this.MIN_SPEED) return
+ var newPlaybackRate = this.selected - 0.1
+ this.selected = Number(newPlaybackRate.toFixed(1))
+ },
+ closeMenu() {
+ if (this.currentPlaybackRate !== this.selected) {
+ this.$emit('change', this.selected)
}
- this.selected = speed
this.show = false
- this.$nextTick(() => this.$emit('change', speed))
+ },
+ clickedOption(rate) {
+ this.selected = Number(rate)
+ this.$nextTick(this.closeMenu)
}
},
mounted() {}
}
+
+
\ No newline at end of file
diff --git a/components/ui/DropdownMenu.vue b/components/ui/DropdownMenu.vue
new file mode 100644
index 00000000..9d6344f4
--- /dev/null
+++ b/components/ui/DropdownMenu.vue
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+ -
+
+ {{ item.icon }}
+ {{ item.text }}
+
+
+
+ -
+
+ {{ item.icon }}
+ {{ item.text }}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/localStore.js b/plugins/localStore.js
index 29d2c6b4..983601b5 100644
--- a/plugins/localStore.js
+++ b/plugins/localStore.js
@@ -167,6 +167,24 @@ class LocalStorage {
return null
}
}
+
+ async setUseChapterTrack(useChapterTrack) {
+ try {
+ await Storage.set({ key: 'useChapterTrack', value: useChapterTrack ? '1' : '0' })
+ } catch (error) {
+ console.error('[LocalStorage] Failed to set use chapter track', error)
+ }
+ }
+
+ async getUseChapterTrack() {
+ try {
+ var obj = await Storage.get({ key: 'useChapterTrack' }) || {}
+ return obj.value === '1'
+ } catch (error) {
+ console.error('[LocalStorage] Failed to get use chapter track', error)
+ return false
+ }
+ }
}