Merge pull request #1767 from FreedomBen/add-more-backwards-forward-options

Expand time interval options for jump controls
This commit is contained in:
advplyr
2026-01-29 17:38:42 -06:00
committed by GitHub
5 changed files with 96 additions and 86 deletions
+17 -6
View File
@@ -70,14 +70,20 @@
<div id="playerControls" class="absolute right-0 bottom-0 mx-auto" style="max-width: 414px"> <div id="playerControls" class="absolute right-0 bottom-0 mx-auto" style="max-width: 414px">
<div class="flex items-center max-w-full" :class="playerSettings.lockUi ? 'justify-center' : 'justify-between'"> <div class="flex items-center max-w-full" :class="playerSettings.lockUi ? 'justify-center' : 'justify-between'">
<span v-show="showFullscreen && !playerSettings.lockUi" class="material-symbols next-icon text-fg cursor-pointer" :class="isLoading ? 'text-opacity-10' : 'text-opacity-75'" @click.stop="jumpChapterStart">first_page</span> <span v-show="showFullscreen && !playerSettings.lockUi" class="material-symbols next-icon text-fg cursor-pointer" :class="isLoading ? 'text-opacity-10' : 'text-opacity-75'" @click.stop="jumpChapterStart">first_page</span>
<span v-show="!playerSettings.lockUi" class="material-symbols jump-icon text-fg cursor-pointer" :class="isLoading ? 'text-opacity-10' : 'text-opacity-75'" @click.stop="jumpBackwards">{{ jumpBackwardsIcon }}</span> <div v-show="!playerSettings.lockUi" class="jump-icon text-fg cursor-pointer flex flex-col items-center" :class="isLoading ? 'text-opacity-10' : 'text-opacity-75'" @click.stop="jumpBackwards">
<span class="material-symbols text-3xl leading-none">replay</span>
<span v-if="showFullscreen" class="jump-label text-[10px] font-semibold leading-tight">{{ jumpBackwardsLabel }}</span>
</div>
<div class="play-btn cursor-pointer shadow-sm flex items-center justify-center rounded-full text-primary mx-4 relative overflow-hidden" :style="{ backgroundColor: coverRgb }" :class="{ 'animate-spin': seekLoading }" @mousedown.prevent @mouseup.prevent @click.stop="playPauseClick"> <div class="play-btn cursor-pointer shadow-sm flex items-center justify-center rounded-full text-primary mx-4 relative overflow-hidden" :style="{ backgroundColor: coverRgb }" :class="{ 'animate-spin': seekLoading }" @mousedown.prevent @mouseup.prevent @click.stop="playPauseClick">
<div v-if="!coverBgIsLight" class="absolute top-0 left-0 w-full h-full bg-white bg-opacity-20 pointer-events-none" /> <div v-if="!coverBgIsLight" class="absolute top-0 left-0 w-full h-full bg-white bg-opacity-20 pointer-events-none" />
<span v-if="!isLoading" class="material-symbols fill" :class="{ 'text-white': coverRgb && !coverBgIsLight }">{{ seekLoading ? 'autorenew' : !isPlaying ? 'play_arrow' : 'pause' }}</span> <span v-if="!isLoading" class="material-symbols fill" :class="{ 'text-white': coverRgb && !coverBgIsLight }">{{ seekLoading ? 'autorenew' : !isPlaying ? 'play_arrow' : 'pause' }}</span>
<widgets-spinner-icon v-else class="h-8 w-8" /> <widgets-spinner-icon v-else class="h-8 w-8" />
</div> </div>
<span v-show="!playerSettings.lockUi" class="material-symbols jump-icon text-fg cursor-pointer" :class="isLoading ? 'text-opacity-10' : 'text-opacity-75'" @click.stop="jumpForward">{{ jumpForwardIcon }}</span> <div v-show="!playerSettings.lockUi" class="jump-icon text-fg cursor-pointer flex flex-col items-center" :class="isLoading ? 'text-opacity-10' : 'text-opacity-75'" @click.stop="jumpForward">
<span class="material-symbols text-3xl leading-none">forward_media</span>
<span v-if="showFullscreen" class="jump-label text-[10px] font-semibold leading-tight">{{ jumpForwardLabel }}</span>
</div>
<span v-show="showFullscreen && !playerSettings.lockUi" class="material-symbols next-icon text-fg cursor-pointer" :class="nextChapter && !isLoading ? 'text-opacity-75' : 'text-opacity-10'" @click.stop="jumpNextChapter">last_page</span> <span v-show="showFullscreen && !playerSettings.lockUi" class="material-symbols next-icon text-fg cursor-pointer" :class="nextChapter && !isLoading ? 'text-opacity-75' : 'text-opacity-10'" @click.stop="jumpNextChapter">last_page</span>
</div> </div>
</div> </div>
@@ -110,6 +116,7 @@ import { AbsAudioPlayer } from '@/plugins/capacitor'
import { Dialog } from '@capacitor/dialog' import { Dialog } from '@capacitor/dialog'
import { FastAverageColor } from 'fast-average-color' import { FastAverageColor } from 'fast-average-color'
import WrappingMarquee from '@/assets/WrappingMarquee.js' import WrappingMarquee from '@/assets/WrappingMarquee.js'
import jumpLabelMixin from '@/mixins/jumpLabel'
export default { export default {
props: { props: {
@@ -121,6 +128,7 @@ export default {
sleepTimeRemaining: Number, sleepTimeRemaining: Number,
serverLibraryItemId: String serverLibraryItemId: String
}, },
mixins: [jumpLabelMixin],
data() { data() {
return { return {
windowHeight: 0, windowHeight: 0,
@@ -221,11 +229,11 @@ export default {
return items return items
}, },
jumpForwardIcon() { jumpForwardLabel() {
return this.$store.getters['globals/getJumpForwardIcon'](this.jumpForwardTime) return this.getJumpLabel(this.jumpForwardTime)
}, },
jumpBackwardsIcon() { jumpBackwardsLabel() {
return this.$store.getters['globals/getJumpBackwardsIcon'](this.jumpBackwardsTime) return this.getJumpLabel(this.jumpBackwardsTime)
}, },
jumpForwardTime() { jumpForwardTime() {
return this.$store.getters['getJumpForwardTime'] return this.$store.getters['getJumpForwardTime']
@@ -1091,6 +1099,9 @@ export default {
margin: 0px 0px; margin: 0px 0px;
font-size: 1.6rem; font-size: 1.6rem;
} }
#playerControls .jump-label {
margin-top: 2px;
}
#playerControls .play-btn { #playerControls .play-btn {
transition: all 0.15s cubic-bezier(0.39, 0.575, 0.565, 1); transition: all 0.15s cubic-bezier(0.39, 0.575, 0.565, 1);
transition-property: padding, margin, height, width, min-width, min-height; transition-property: padding, margin, height, width, min-width, min-height;
+12
View File
@@ -0,0 +1,12 @@
export default {
methods: {
getJumpLabel(seconds) {
const val = Number(seconds)
if (isNaN(val)) return ''
const useMinutes = val >= 120 // keep 60s as seconds
const key = useMinutes ? 'UnitMinutesShort' : 'UnitSecondsShort'
const unitValue = useMinutes ? val / 60 : val
return this.$getString(key, [this.$formatNumber(unitValue)])
}
}
}
+61 -42
View File
@@ -36,24 +36,24 @@
<!-- Playback settings --> <!-- Playback settings -->
<p class="uppercase text-xs font-semibold text-fg-muted mb-2 mt-10">{{ $strings.HeaderPlaybackSettings }}</p> <p class="uppercase text-xs font-semibold text-fg-muted mb-2 mt-10">{{ $strings.HeaderPlaybackSettings }}</p>
<div class="py-3 flex items-center">
<p class="pr-4 w-36">{{ $strings.LabelJumpBackwardsTime }}</p>
<div @click.stop="showJumpBackwardsOptions">
<ui-text-input :value="jumpBackwardsOption" readonly append-icon="expand_more" style="width: 145px; max-width: 145px" />
</div>
</div>
<div class="py-3 flex items-center">
<p class="pr-4 w-36">{{ $strings.LabelJumpForwardsTime }}</p>
<div @click.stop="showJumpForwardOptions">
<ui-text-input :value="jumpForwardOption" readonly append-icon="expand_more" style="width: 145px; max-width: 145px" />
</div>
</div>
<div class="flex items-center py-3"> <div class="flex items-center py-3">
<div class="w-10 flex justify-center" @click="toggleDisableAutoRewind"> <div class="w-10 flex justify-center" @click="toggleDisableAutoRewind">
<ui-toggle-switch v-model="settings.disableAutoRewind" @input="saveSettings" /> <ui-toggle-switch v-model="settings.disableAutoRewind" @input="saveSettings" />
</div> </div>
<p class="pl-4">{{ $strings.LabelDisableAutoRewind }}</p> <p class="pl-4">{{ $strings.LabelDisableAutoRewind }}</p>
</div> </div>
<div class="flex items-center py-3">
<div class="w-10 flex justify-center" @click="toggleJumpBackwards">
<span class="material-symbols text-4xl">{{ currentJumpBackwardsTimeIcon }}</span>
</div>
<p class="pl-4">{{ $strings.LabelJumpBackwardsTime }}</p>
</div>
<div class="flex items-center py-3">
<div class="w-10 flex justify-center" @click="toggleJumpForward">
<span class="material-symbols text-4xl">{{ currentJumpForwardTimeIcon }}</span>
</div>
<p class="pl-4">{{ $strings.LabelJumpForwardsTime }}</p>
</div>
<div v-if="!isiOS" class="flex items-center py-3"> <div v-if="!isiOS" class="flex items-center py-3">
<div class="w-10 flex justify-center" @click="toggleEnableMp3IndexSeeking"> <div class="w-10 flex justify-center" @click="toggleEnableMp3IndexSeeking">
<ui-toggle-switch v-model="settings.enableMp3IndexSeeking" @input="saveSettings" /> <ui-toggle-switch v-model="settings.enableMp3IndexSeeking" @input="saveSettings" />
@@ -179,7 +179,7 @@
<ui-loading-indicator /> <ui-loading-indicator />
</div> </div>
<modals-dialog v-model="showMoreMenuDialog" :items="moreMenuItems" @action="clickMenuAction" /> <modals-dialog v-model="showMoreMenuDialog" :items="moreMenuItems" :selected="moreMenuSelected" @action="clickMenuAction" />
<modals-sleep-timer-length-modal v-model="showSleepTimerLengthModal" @change="sleepTimerLengthModalSelection" /> <modals-sleep-timer-length-modal v-model="showSleepTimerLengthModal" @change="sleepTimerLengthModalSelection" />
<modals-auto-sleep-timer-rewind-length-modal v-model="showAutoSleepTimerRewindLengthModal" @change="showAutoSleepTimerRewindLengthModalSelection" /> <modals-auto-sleep-timer-rewind-length-modal v-model="showAutoSleepTimerRewindLengthModal" @change="showAutoSleepTimerRewindLengthModalSelection" />
</div> </div>
@@ -187,8 +187,10 @@
<script> <script>
import { Dialog } from '@capacitor/dialog' import { Dialog } from '@capacitor/dialog'
import jumpLabelMixin from '@/mixins/jumpLabel'
export default { export default {
mixins: [jumpLabelMixin],
data() { data() {
return { return {
loading: false, loading: false,
@@ -352,15 +354,21 @@ export default {
isiOS() { isiOS() {
return this.$platform === 'ios' return this.$platform === 'ios'
}, },
jumpForwardItems() { jumpForwardSecondsOptions() {
return this.$store.state.globals.jumpForwardItems || [] return this.$store.state.globals.jumpForwardSecondsOptions || []
}, },
jumpBackwardsItems() { jumpBackwardsSecondsOptions() {
return this.$store.state.globals.jumpBackwardsItems || [] return this.$store.state.globals.jumpBackwardsSecondsOptions || []
}, },
languageOptionItems() { languageOptionItems() {
return this.$languageCodeOptions || [] return this.$languageCodeOptions || []
}, },
jumpForwardOption() {
return this.getJumpLabel(this.settings.jumpForwardTime)
},
jumpBackwardsOption() {
return this.getJumpLabel(this.settings.jumpBackwardsTime)
},
themeOptionItems() { themeOptionItems() {
return [ return [
{ {
@@ -377,20 +385,6 @@ export default {
} }
] ]
}, },
currentJumpForwardTimeIcon() {
return this.jumpForwardItems[this.currentJumpForwardTimeIndex].icon
},
currentJumpForwardTimeIndex() {
var index = this.jumpForwardItems.findIndex((jfi) => jfi.value === this.settings.jumpForwardTime)
return index >= 0 ? index : 1
},
currentJumpBackwardsTimeIcon() {
return this.jumpBackwardsItems[this.currentJumpBackwardsTimeIndex].icon
},
currentJumpBackwardsTimeIndex() {
var index = this.jumpBackwardsItems.findIndex((jfi) => jfi.value === this.settings.jumpBackwardsTime)
return index >= 0 ? index : 1
},
shakeSensitivityOption() { shakeSensitivityOption() {
const item = this.shakeSensitivityItems.find((i) => i.value === this.settings.shakeSensitivity) const item = this.shakeSensitivityItems.find((i) => i.value === this.settings.shakeSensitivity)
return item?.text || 'Error' return item?.text || 'Error'
@@ -434,7 +428,29 @@ export default {
else if (this.moreMenuSetting === 'downloadUsingCellular') return this.downloadUsingCellularItems else if (this.moreMenuSetting === 'downloadUsingCellular') return this.downloadUsingCellularItems
else if (this.moreMenuSetting === 'streamingUsingCellular') return this.streamingUsingCellularItems else if (this.moreMenuSetting === 'streamingUsingCellular') return this.streamingUsingCellularItems
else if (this.moreMenuSetting === 'androidAutoBrowseSeriesSequenceOrder') return this.androidAutoBrowseSeriesSequenceOrderItems else if (this.moreMenuSetting === 'androidAutoBrowseSeriesSequenceOrder') return this.androidAutoBrowseSeriesSequenceOrderItems
else if (this.moreMenuSetting === 'jumpForward')
return this.jumpForwardSecondsOptions.map((value) => ({
text: this.getJumpLabel(value),
value: value
}))
else if (this.moreMenuSetting === 'jumpBackwards')
return this.jumpBackwardsSecondsOptions.map((value) => ({
text: this.getJumpLabel(value),
value: value
}))
return [] return []
},
moreMenuSelected() {
if (this.moreMenuSetting === 'jumpForward') return this.settings.jumpForwardTime
if (this.moreMenuSetting === 'jumpBackwards') return this.settings.jumpBackwardsTime
if (this.moreMenuSetting === 'language') return this.settings.languageCode
if (this.moreMenuSetting === 'theme') return this.theme
if (this.moreMenuSetting === 'downloadUsingCellular') return this.settings.downloadUsingCellular
if (this.moreMenuSetting === 'streamingUsingCellular') return this.settings.streamingUsingCellular
if (this.moreMenuSetting === 'androidAutoBrowseSeriesSequenceOrder') return this.settings.androidAutoBrowseSeriesSequenceOrder
if (this.moreMenuSetting === 'shakeSensitivity') return this.settings.shakeSensitivity
if (this.moreMenuSetting === 'hapticFeedback') return this.settings.hapticFeedback
return null
} }
}, },
methods: { methods: {
@@ -468,6 +484,14 @@ export default {
this.moreMenuSetting = 'theme' this.moreMenuSetting = 'theme'
this.showMoreMenuDialog = true this.showMoreMenuDialog = true
}, },
showJumpForwardOptions() {
this.moreMenuSetting = 'jumpForward'
this.showMoreMenuDialog = true
},
showJumpBackwardsOptions() {
this.moreMenuSetting = 'jumpBackwards'
this.showMoreMenuDialog = true
},
showDownloadUsingCellularOptions() { showDownloadUsingCellularOptions() {
this.moreMenuSetting = 'downloadUsingCellular' this.moreMenuSetting = 'downloadUsingCellular'
this.showMoreMenuDialog = true this.showMoreMenuDialog = true
@@ -503,6 +527,12 @@ export default {
} else if (this.moreMenuSetting === 'androidAutoBrowseSeriesSequenceOrder') { } else if (this.moreMenuSetting === 'androidAutoBrowseSeriesSequenceOrder') {
this.settings.androidAutoBrowseSeriesSequenceOrder = action this.settings.androidAutoBrowseSeriesSequenceOrder = action
this.saveSettings() this.saveSettings()
} else if (this.moreMenuSetting === 'jumpForward') {
this.settings.jumpForwardTime = action
this.saveSettings()
} else if (this.moreMenuSetting === 'jumpBackwards') {
this.settings.jumpBackwardsTime = action
this.saveSettings()
} }
}, },
saveTheme(theme) { saveTheme(theme) {
@@ -598,17 +628,6 @@ export default {
this.$setOrientationLock(this.settings.lockOrientation) this.$setOrientationLock(this.settings.lockOrientation)
this.saveSettings() this.saveSettings()
}, },
toggleJumpForward() {
var next = (this.currentJumpForwardTimeIndex + 1) % 3
this.settings.jumpForwardTime = this.jumpForwardItems[next].value
this.saveSettings()
},
toggleJumpBackwards() {
var next = (this.currentJumpBackwardsTimeIndex + 4) % 3
if (next > 2) return
this.settings.jumpBackwardsTime = this.jumpBackwardsItems[next].value
this.saveSettings()
},
async saveSettings() { async saveSettings() {
await this.$hapticsImpact() await this.$hapticsImpact()
const updatedDeviceData = await this.$db.updateDeviceSettings({ ...this.settings }) const updatedDeviceData = await this.$db.updateDeviceSettings({ ...this.settings })
+3 -37
View File
@@ -5,34 +5,8 @@ export const state = () => ({
series: null, series: null,
localMediaProgress: [], localMediaProgress: [],
lastSearch: null, lastSearch: null,
jumpForwardItems: [ jumpForwardSecondsOptions: [5, 10, 15, 30, 60, 120, 300],
{ jumpBackwardsSecondsOptions: [5, 10, 15, 30, 60, 120, 300],
icon: 'forward_5',
value: 5
},
{
icon: 'forward_10',
value: 10
},
{
icon: 'forward_30',
value: 30
}
],
jumpBackwardsItems: [
{
icon: 'replay_5',
value: 5
},
{
icon: 'replay_10',
value: 10
},
{
icon: 'replay_30',
value: 30
}
],
libraryIcons: ['database', 'audiobookshelf', 'books-1', 'books-2', 'book-1', 'microphone-1', 'microphone-3', 'radio', 'podcast', 'rss', 'headphones', 'music', 'file-picture', 'rocket', 'power', 'star', 'heart'], libraryIcons: ['database', 'audiobookshelf', 'books-1', 'books-2', 'book-1', 'microphone-1', 'microphone-3', 'radio', 'podcast', 'rss', 'headphones', 'music', 'file-picture', 'rocket', 'power', 'star', 'heart'],
selectedPlaylistItems: [], selectedPlaylistItems: [],
showPlaylistsAddCreateModal: false, showPlaylistsAddCreateModal: false,
@@ -111,15 +85,7 @@ export const getters = {
if (episodeId != null && lmp.episodeId != episodeId) return false if (episodeId != null && lmp.episodeId != episodeId) return false
return lmp.libraryItemId == libraryItemId return lmp.libraryItemId == libraryItemId
}) })
}, }
getJumpForwardIcon: (state) => (jumpForwardTime) => {
const item = state.jumpForwardItems.find((i) => i.value == jumpForwardTime)
return item ? item.icon : 'forward_10'
},
getJumpBackwardsIcon: (state) => (jumpBackwardsTime) => {
const item = state.jumpBackwardsItems.find((i) => i.value == jumpBackwardsTime)
return item ? item.icon : 'replay_10'
}
} }
export const actions = { export const actions = {
+3 -1
View File
@@ -359,5 +359,7 @@
"ToastPodcastCreateSuccess": "Podcast created successfully", "ToastPodcastCreateSuccess": "Podcast created successfully",
"ToastRSSFeedCloseFailed": "Failed to close RSS feed", "ToastRSSFeedCloseFailed": "Failed to close RSS feed",
"ToastRSSFeedCloseSuccess": "RSS feed closed", "ToastRSSFeedCloseSuccess": "RSS feed closed",
"ToastStreamingNotAllowedOnCellular": "Streaming is not allowed on cellular data" "ToastStreamingNotAllowedOnCellular": "Streaming is not allowed on cellular data",
"UnitMinutesShort": "{0}m",
"UnitSecondsShort": "{0}s"
} }