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 @@
cast
-
- close +
+ + more_vert + +
+
+ +
+
+

{{ currentTimePretty }}

+
+

{{ totalTimeRemainingPretty }}

+
+
+
+
+
+
@@ -55,7 +71,6 @@
-
@@ -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 @@
    -
    +
    -
      +
      +
      + +
      +

      {{ 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 @@ + + + \ 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 + } + } }