From 733f61075f2fd5dc2965d996979836eb1403b936 Mon Sep 17 00:00:00 2001 From: Greg Lorenzen <31518305+glorenzen@users.noreply.github.com> Date: Sun, 14 Jul 2024 11:56:48 -0700 Subject: [PATCH] WIP: Add "End of chapter" option for sleep timer (#3151) * Add SleepTimerTypes for countdown and chapter * Add functionality for 'end of chapter' sleep timer * Fix custom time for sleep timer * Include end of chapter string for sleep timer * Increase chapter end tolerance to 0.75 * Show sleep time options in modal when timer is active * Add SleepTimerTypes for countdown and chapter * Add functionality for 'end of chapter' sleep timer * Fix custom time for sleep timer * Include end of chapter string for sleep timer * Increase chapter end tolerance to 0.75 * Show sleep time options in modal when timer is active * Sleep timer cleanup * Localization for sleep timer modal, UI updates --------- Co-authored-by: advplyr --- .../components/app/MediaPlayerContainer.vue | 45 ++++-- .../components/modals/PlayerSettingsModal.vue | 12 +- client/components/modals/SleepTimerModal.vue | 137 ++++++++++-------- .../player/PlayerPlaybackControls.vue | 4 +- client/components/player/PlayerUi.vue | 29 ++-- client/plugins/constants.js | 8 +- client/strings/en-us.json | 7 +- 7 files changed, 147 insertions(+), 95 deletions(-) diff --git a/client/components/app/MediaPlayerContainer.vue b/client/components/app/MediaPlayerContainer.vue index 5c04d053..cbc76803 100644 --- a/client/components/app/MediaPlayerContainer.vue +++ b/client/components/app/MediaPlayerContainer.vue @@ -35,11 +35,13 @@ - + @@ -81,8 +83,8 @@ export default { showPlayerQueueItemsModal: false, showPlayerSettingsModal: false, sleepTimerSet: false, - sleepTimerTime: 0, sleepTimerRemaining: 0, + sleepTimerType: null, sleepTimer: null, displayTitle: null, currentPlaybackRate: 1, @@ -149,6 +151,9 @@ export default { if (this.streamEpisode) return this.streamEpisode.chapters || [] return this.media.chapters || [] }, + currentChapter() { + return this.chapters.find((chapter) => chapter.start <= this.currentTime && this.currentTime < chapter.end) + }, title() { if (this.playerHandler.displayTitle) return this.playerHandler.displayTitle return this.mediaMetadata.title || 'No Title' @@ -208,14 +213,18 @@ export default { this.$store.commit('setIsPlaying', isPlaying) this.updateMediaSessionPlaybackState() }, - setSleepTimer(seconds) { + setSleepTimer(time) { this.sleepTimerSet = true - this.sleepTimerTime = seconds - this.sleepTimerRemaining = seconds - this.runSleepTimer() this.showSleepTimerModal = false + + this.sleepTimerType = time.timerType + if (this.sleepTimerType === this.$constants.SleepTimerTypes.COUNTDOWN) { + this.runSleepTimer(time) + } }, - runSleepTimer() { + runSleepTimer(time) { + this.sleepTimerRemaining = time.seconds + var lastTick = Date.now() clearInterval(this.sleepTimer) this.sleepTimer = setInterval(() => { @@ -224,12 +233,23 @@ export default { this.sleepTimerRemaining -= elapsed / 1000 if (this.sleepTimerRemaining <= 0) { - this.clearSleepTimer() - this.playerHandler.pause() - this.$toast.info('Sleep Timer Done.. zZzzZz') + this.sleepTimerEnd() } }, 1000) }, + checkChapterEnd(time) { + if (!this.currentChapter) return + const chapterEndTime = this.currentChapter.end + const tolerance = 0.75 + if (time >= chapterEndTime - tolerance) { + this.sleepTimerEnd() + } + }, + sleepTimerEnd() { + this.clearSleepTimer() + this.playerHandler.pause() + this.$toast.info('Sleep Timer Done.. zZzzZz') + }, cancelSleepTimer() { this.showSleepTimerModal = false this.clearSleepTimer() @@ -239,6 +259,7 @@ export default { this.sleepTimerRemaining = 0 this.sleepTimer = null this.sleepTimerSet = false + this.sleepTimerType = null }, incrementSleepTimer(amount) { if (!this.sleepTimerSet) return @@ -279,6 +300,10 @@ export default { if (this.$refs.audioPlayer) { this.$refs.audioPlayer.setCurrentTime(time) } + + if (this.sleepTimerType === this.$constants.SleepTimerTypes.CHAPTER && this.sleepTimerSet) { + this.checkChapterEnd(time) + } }, setDuration(duration) { this.totalDuration = duration diff --git a/client/components/modals/PlayerSettingsModal.vue b/client/components/modals/PlayerSettingsModal.vue index 10c3cd65..ec178d9c 100644 --- a/client/components/modals/PlayerSettingsModal.vue +++ b/client/components/modals/PlayerSettingsModal.vue @@ -27,12 +27,12 @@ export default { return { useChapterTrack: false, jumpValues: [ - { text: this.$getString('LabelJumpAmountSeconds', ['10']), value: 10 }, - { text: this.$getString('LabelJumpAmountSeconds', ['15']), value: 15 }, - { text: this.$getString('LabelJumpAmountSeconds', ['30']), value: 30 }, - { text: this.$getString('LabelJumpAmountSeconds', ['60']), value: 60 }, - { text: this.$getString('LabelJumpAmountMinutes', ['2']), value: 120 }, - { text: this.$getString('LabelJumpAmountMinutes', ['5']), value: 300 } + { text: this.$getString('LabelTimeDurationXSeconds', ['10']), value: 10 }, + { text: this.$getString('LabelTimeDurationXSeconds', ['15']), value: 15 }, + { text: this.$getString('LabelTimeDurationXSeconds', ['30']), value: 30 }, + { text: this.$getString('LabelTimeDurationXSeconds', ['60']), value: 60 }, + { text: this.$getString('LabelTimeDurationXMinutes', ['2']), value: 120 }, + { text: this.$getString('LabelTimeDurationXMinutes', ['5']), value: 300 } ], jumpForwardAmount: 10, jumpBackwardAmount: 10 diff --git a/client/components/modals/SleepTimerModal.vue b/client/components/modals/SleepTimerModal.vue index 051c5d3d..43b55217 100644 --- a/client/components/modals/SleepTimerModal.vue +++ b/client/components/modals/SleepTimerModal.vue @@ -6,34 +6,36 @@ -
-
+
+
- + Set
-
-
- +
+
+ +
+ remove - 30m + 30m - + -

{{ $secondsToTimestamp(remaining) }}

+

{{ $secondsToTimestamp(remaining) }}

- + - + add - 30m + 30m
{{ $strings.ButtonCancel }} @@ -47,52 +49,13 @@ export default { props: { value: Boolean, timerSet: Boolean, - timerTime: Number, - remaining: Number + timerType: String, + remaining: Number, + hasChapters: Boolean }, data() { return { - customTime: null, - sleepTimes: [ - { - seconds: 60 * 5, - text: '5 minutes' - }, - { - seconds: 60 * 15, - text: '15 minutes' - }, - { - seconds: 60 * 20, - text: '20 minutes' - }, - { - seconds: 60 * 30, - text: '30 minutes' - }, - { - seconds: 60 * 45, - text: '45 minutes' - }, - { - seconds: 60 * 60, - text: '60 minutes' - }, - { - seconds: 60 * 90, - text: '90 minutes' - }, - { - seconds: 60 * 120, - text: '2 hours' - } - ] - } - }, - watch: { - show(newVal) { - if (newVal) { - } + customTime: null } }, computed: { @@ -103,6 +66,54 @@ export default { set(val) { this.$emit('input', val) } + }, + sleepTimes() { + const times = [ + { + seconds: 60 * 5, + text: this.$getString('LabelTimeDurationXMinutes', ['5']), + timerType: this.$constants.SleepTimerTypes.COUNTDOWN + }, + { + seconds: 60 * 15, + text: this.$getString('LabelTimeDurationXMinutes', ['15']), + timerType: this.$constants.SleepTimerTypes.COUNTDOWN + }, + { + seconds: 60 * 20, + text: this.$getString('LabelTimeDurationXMinutes', ['20']), + timerType: this.$constants.SleepTimerTypes.COUNTDOWN + }, + { + seconds: 60 * 30, + text: this.$getString('LabelTimeDurationXMinutes', ['30']), + timerType: this.$constants.SleepTimerTypes.COUNTDOWN + }, + { + seconds: 60 * 45, + text: this.$getString('LabelTimeDurationXMinutes', ['45']), + timerType: this.$constants.SleepTimerTypes.COUNTDOWN + }, + { + seconds: 60 * 60, + text: this.$getString('LabelTimeDurationXMinutes', ['60']), + timerType: this.$constants.SleepTimerTypes.COUNTDOWN + }, + { + seconds: 60 * 90, + text: this.$getString('LabelTimeDurationXMinutes', ['90']), + timerType: this.$constants.SleepTimerTypes.COUNTDOWN + }, + { + seconds: 60 * 120, + text: this.$getString('LabelTimeDurationXHours', ['2']), + timerType: this.$constants.SleepTimerTypes.COUNTDOWN + } + ] + if (this.hasChapters) { + times.push({ seconds: -1, text: this.$strings.LabelEndOfChapter, timerType: this.$constants.SleepTimerTypes.CHAPTER }) + } + return times } }, methods: { @@ -113,10 +124,14 @@ export default { } const timeInSeconds = Math.round(Number(this.customTime) * 60) - this.setTime(timeInSeconds) + const time = { + seconds: timeInSeconds, + timerType: this.$constants.SleepTimerTypes.COUNTDOWN + } + this.setTime(time) }, - setTime(seconds) { - this.$emit('set', seconds) + setTime(time) { + this.$emit('set', time) }, increment(amount) { this.$emit('increment', amount) @@ -130,4 +145,4 @@ export default { } } } - \ No newline at end of file + diff --git a/client/components/player/PlayerPlaybackControls.vue b/client/components/player/PlayerPlaybackControls.vue index 664385bd..76397e98 100644 --- a/client/components/player/PlayerPlaybackControls.vue +++ b/client/components/player/PlayerPlaybackControls.vue @@ -96,10 +96,10 @@ export default { let formattedTime = '' if (amount <= 60) { - formattedTime = this.$getString('LabelJumpAmountSeconds', [amount]) + formattedTime = this.$getString('LabelTimeDurationXSeconds', [amount]) } else { const minutes = Math.floor(amount / 60) - formattedTime = this.$getString('LabelJumpAmountMinutes', [minutes]) + formattedTime = this.$getString('LabelTimeDurationXMinutes', [minutes]) } return `${prefix} - ${formattedTime}` diff --git a/client/components/player/PlayerUi.vue b/client/components/player/PlayerUi.vue index 21d1b7aa..68452061 100644 --- a/client/components/player/PlayerUi.vue +++ b/client/components/player/PlayerUi.vue @@ -13,7 +13,7 @@ snooze
snooze -

{{ sleepTimerRemainingString }}

+

{{ sleepTimerRemainingString }}

@@ -72,12 +72,14 @@ export default { type: Array, default: () => [] }, + currentChapter: Object, bookmarks: { type: Array, default: () => [] }, sleepTimerSet: Boolean, sleepTimerRemaining: Number, + sleepTimerType: String, isPodcast: Boolean, hideBookmarks: Boolean, hideSleepTimer: Boolean @@ -104,16 +106,20 @@ export default { }, computed: { sleepTimerRemainingString() { - var rounded = Math.round(this.sleepTimerRemaining) - if (rounded < 90) { - return `${rounded}s` + if (this.sleepTimerType === this.$constants.SleepTimerTypes.CHAPTER) { + return 'EoC' + } else { + var rounded = Math.round(this.sleepTimerRemaining) + if (rounded < 90) { + return `${rounded}s` + } + var minutesRounded = Math.round(rounded / 60) + if (minutesRounded <= 90) { + return `${minutesRounded}m` + } + var hoursRounded = Math.round(minutesRounded / 60) + return `${hoursRounded}h` } - var minutesRounded = Math.round(rounded / 60) - if (minutesRounded < 90) { - return `${minutesRounded}m` - } - var hoursRounded = Math.round(minutesRounded / 60) - return `${hoursRounded}h` }, token() { return this.$store.getters['user/getToken'] @@ -138,9 +144,6 @@ export default { if (!duration) return 0 return Math.round((100 * time) / duration) }, - currentChapter() { - return this.chapters.find((chapter) => chapter.start <= this.currentTime && this.currentTime < chapter.end) - }, currentChapterName() { return this.currentChapter ? this.currentChapter.title : '' }, diff --git a/client/plugins/constants.js b/client/plugins/constants.js index f001f6ce..d89fbbbd 100644 --- a/client/plugins/constants.js +++ b/client/plugins/constants.js @@ -32,12 +32,18 @@ const PlayMethod = { LOCAL: 3 } +const SleepTimerTypes = { + COUNTDOWN: 'countdown', + CHAPTER: 'chapter' +} + const Constants = { SupportedFileTypes, DownloadStatus, BookCoverAspectRatio, BookshelfView, - PlayMethod + PlayMethod, + SleepTimerTypes } const KeyNames = { diff --git a/client/strings/en-us.json b/client/strings/en-us.json index eaaa5d7c..a0933d4d 100644 --- a/client/strings/en-us.json +++ b/client/strings/en-us.json @@ -292,6 +292,7 @@ "LabelEmbeddedCover": "Embedded Cover", "LabelEnable": "Enable", "LabelEnd": "End", + "LabelEndOfChapter": "End of Chapter", "LabelEpisode": "Episode", "LabelEpisodeTitle": "Episode Title", "LabelEpisodeType": "Episode Type", @@ -345,8 +346,6 @@ "LabelIntervalEveryHour": "Every hour", "LabelInvert": "Invert", "LabelItem": "Item", - "LabelJumpAmountMinutes": "{0} minutes", - "LabelJumpAmountSeconds": "{0} seconds", "LabelJumpBackwardAmount": "Jump backward amount", "LabelJumpForwardAmount": "Jump forward amount", "LabelLanguage": "Language", @@ -565,6 +564,10 @@ "LabelThemeDark": "Dark", "LabelThemeLight": "Light", "LabelTimeBase": "Time Base", + "LabelTimeDurationXHours": "{0} hours", + "LabelTimeDurationXMinutes": "{0} minutes", + "LabelTimeDurationXSeconds": "{0} seconds", + "LabelTimeInMinutes": "Time in minutes", "LabelTimeListened": "Time Listened", "LabelTimeListenedToday": "Time Listened Today", "LabelTimeRemaining": "{0} remaining",