mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	WIP: Add adjustable skip amount (#3113)
* Add playback settings string to en-us * Add playback settings UI for jump forwards and jump backwards * Remove jump forwards and jump backwards settings * Remove jump forwards and jump backwards en-us strings * Update player UI to include player settings button * Add label view player settings string * Add PlayerSettingsModal component Includes a toggle switch for enabling/disabling the chapter track feature. * Add player settings modal component to MediaPlayerContainer * Handle useChapterTrack changes in PlayerUI * Add jump forwards and jump backwards settings to user store * Add jump forwards and jump backwards label strings * Add jump forwards and jump backwards settings to PlayerSettingsModal * Update jump forwards and jump backwards to handle user state values in PlayerHandler * Update jump backwards icon in PlayerPlaybackControls * Add playback settings string to en-us * Add playback settings UI for jump forwards and jump backwards * Remove jump forwards and jump backwards settings * Remove jump forwards and jump backwards en-us strings * Update player UI to include player settings button * Add label view player settings string * Add PlayerSettingsModal component Includes a toggle switch for enabling/disabling the chapter track feature. * Add player settings modal component to MediaPlayerContainer * Handle useChapterTrack changes in PlayerUI * Add jump forwards and jump backwards settings to user store * Add jump forwards and jump backwards label strings * Add jump forwards and jump backwards settings to PlayerSettingsModal * Update jump forwards and jump backwards to handle user state values in PlayerHandler * Update jump backwards icon in PlayerPlaybackControls * Add jump amounts to playback controls tooltips * Fix merge issues and add new Material Symbols to player ui * Alphabetize strings in en-us.json * Update dropdown component with SelectInput to support menu overflowing modal * Update localization for player settings * Update en-us strings order --------- Co-authored-by: advplyr <advplyr@protonmail.com>
This commit is contained in:
		
							parent
							
								
									b6875a44cf
								
							
						
					
					
						commit
						43b7ccd61a
					
				| @ -51,6 +51,7 @@ | ||||
|       @showBookmarks="showBookmarks" | ||||
|       @showSleepTimer="showSleepTimerModal = true" | ||||
|       @showPlayerQueueItems="showPlayerQueueItemsModal = true" | ||||
|       @showPlayerSettings="showPlayerSettingsModal = true" | ||||
|     /> | ||||
| 
 | ||||
|     <modals-bookmarks-modal v-model="showBookmarksModal" :bookmarks="bookmarks" :current-time="bookmarkCurrentTime" :library-item-id="libraryItemId" @select="selectBookmark" /> | ||||
| @ -58,6 +59,8 @@ | ||||
|     <modals-sleep-timer-modal v-model="showSleepTimerModal" :timer-set="sleepTimerSet" :timer-time="sleepTimerTime" :remaining="sleepTimerRemaining" @set="setSleepTimer" @cancel="cancelSleepTimer" @increment="incrementSleepTimer" @decrement="decrementSleepTimer" /> | ||||
| 
 | ||||
|     <modals-player-queue-items-modal v-model="showPlayerQueueItemsModal" :library-item-id="libraryItemId" /> | ||||
| 
 | ||||
|     <modals-player-settings-modal v-model="showPlayerSettingsModal" /> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| @ -76,6 +79,7 @@ export default { | ||||
|       currentTime: 0, | ||||
|       showSleepTimerModal: false, | ||||
|       showPlayerQueueItemsModal: false, | ||||
|       showPlayerSettingsModal: false, | ||||
|       sleepTimerSet: false, | ||||
|       sleepTimerTime: 0, | ||||
|       sleepTimerRemaining: 0, | ||||
|  | ||||
							
								
								
									
										70
									
								
								client/components/modals/PlayerSettingsModal.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								client/components/modals/PlayerSettingsModal.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,70 @@ | ||||
| <template> | ||||
|   <modals-modal v-model="show" name="player-settings" :width="500" :height="'unset'"> | ||||
|     <div ref="container" class="w-full rounded-lg bg-bg box-shadow-md overflow-y-auto overflow-x-hidden p-4" style="max-height: 80vh; min-height: 40vh"> | ||||
|       <h3 class="text-xl font-semibold mb-8">{{ $strings.HeaderPlayerSettings }}</h3> | ||||
|       <div class="flex items-center mb-4"> | ||||
|         <ui-toggle-switch v-model="useChapterTrack" @input="setUseChapterTrack" /> | ||||
|         <div class="pl-4"> | ||||
|           <span>{{ $strings.LabelUseChapterTrack }}</span> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="flex items-center mb-4"> | ||||
|         <ui-select-input v-model="jumpForwardAmount" :label="$strings.LabelJumpForwardAmount" menuMaxHeight="250px" :items="jumpValues" @input="setJumpForwardAmount" /> | ||||
|       </div> | ||||
|       <div class="flex items-center"> | ||||
|         <ui-select-input v-model="jumpBackwardAmount" :label="$strings.LabelJumpBackwardAmount" menuMaxHeight="250px" :items="jumpValues" @input="setJumpBackwardAmount" /> | ||||
|       </div> | ||||
|     </div> | ||||
|   </modals-modal> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| export default { | ||||
|   props: { | ||||
|     value: Boolean | ||||
|   }, | ||||
|   data() { | ||||
|     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 } | ||||
|       ], | ||||
|       jumpForwardAmount: 10, | ||||
|       jumpBackwardAmount: 10 | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     show: { | ||||
|       get() { | ||||
|         return this.value | ||||
|       }, | ||||
|       set(val) { | ||||
|         this.$emit('input', val) | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     setUseChapterTrack() { | ||||
|       this.$store.dispatch('user/updateUserSettings', { useChapterTrack: this.useChapterTrack }) | ||||
|     }, | ||||
|     setJumpForwardAmount(val) { | ||||
|       this.jumpForwardAmount = val | ||||
|       this.$store.dispatch('user/updateUserSettings', { jumpForwardAmount: val }) | ||||
|     }, | ||||
|     setJumpBackwardAmount(val) { | ||||
|       this.jumpBackwardAmount = val | ||||
|       this.$store.dispatch('user/updateUserSettings', { jumpBackwardAmount: val }) | ||||
|     } | ||||
|   }, | ||||
|   mounted() { | ||||
|     this.useChapterTrack = this.$store.getters['user/getUserSetting']('useChapterTrack') | ||||
|     this.jumpForwardAmount = this.$store.getters['user/getUserSetting']('jumpForwardAmount') | ||||
|     this.jumpBackwardAmount = this.$store.getters['user/getUserSetting']('jumpBackwardAmount') | ||||
|   } | ||||
| } | ||||
| </script> | ||||
| @ -7,17 +7,17 @@ | ||||
|           <span class="material-symbols text-2xl sm:text-3xl">first_page</span> | ||||
|         </button> | ||||
|       </ui-tooltip> | ||||
|       <ui-tooltip direction="top" :text="$strings.ButtonJumpBackward"> | ||||
|         <button :aria-label="$strings.ButtonJumpBackward" class="text-gray-300" @mousedown.prevent @mouseup.prevent @click.stop="jumpBackward"> | ||||
|           <span class="material-symbols text-2xl sm:text-3xl">replay_10</span> | ||||
|       <ui-tooltip direction="top" :text="jumpBackwardText"> | ||||
|         <button :aria-label="jumpForwardText" class="text-gray-300" @mousedown.prevent @mouseup.prevent @click.stop="jumpBackward"> | ||||
|           <span class="material-symbols text-2xl sm:text-3xl">replay</span> | ||||
|         </button> | ||||
|       </ui-tooltip> | ||||
|       <button :aria-label="paused ? $strings.ButtonPlay : $strings.ButtonPause" class="p-2 shadow-sm bg-accent flex items-center justify-center rounded-full text-primary mx-4 lg:mx-8" :class="seekLoading ? 'animate-spin' : ''" @mousedown.prevent @mouseup.prevent @click.stop="playPause"> | ||||
|         <span class="material-symbols fill text-2xl">{{ seekLoading ? 'autorenew' : paused ? 'play_arrow' : 'pause' }}</span> | ||||
|       </button> | ||||
|       <ui-tooltip direction="top" :text="$strings.ButtonJumpForward"> | ||||
|         <button :aria-label="$strings.ButtonJumpForward" class="text-gray-300" @mousedown.prevent @mouseup.prevent @click.stop="jumpForward"> | ||||
|           <span class="material-symbols text-2xl sm:text-3xl">forward_10</span> | ||||
|       <ui-tooltip direction="top" :text="jumpForwardText"> | ||||
|         <button :aria-label="jumpForwardText" class="text-gray-300" @mousedown.prevent @mouseup.prevent @click.stop="jumpForward"> | ||||
|           <span class="material-symbols text-2xl sm:text-3xl">forward_media</span> | ||||
|         </button> | ||||
|       </ui-tooltip> | ||||
|       <ui-tooltip direction="top" :text="$strings.ButtonNextChapter" class="ml-4 lg:ml-8"> | ||||
| @ -56,6 +56,12 @@ export default { | ||||
|       set(val) { | ||||
|         this.$emit('update:playbackRate', val) | ||||
|       } | ||||
|     }, | ||||
|     jumpForwardText() { | ||||
|       return this.getJumpText('jumpForwardAmount', this.$strings.ButtonJumpForward) | ||||
|     }, | ||||
|     jumpBackwardText() { | ||||
|       return this.getJumpText('jumpBackwardAmount', this.$strings.ButtonJumpBackward) | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
| @ -83,8 +89,22 @@ export default { | ||||
|       this.$store.dispatch('user/updateUserSettings', { playbackRate }).catch((err) => { | ||||
|         console.error('Failed to update settings', err) | ||||
|       }) | ||||
|     }, | ||||
|     getJumpText(setting, prefix) { | ||||
|       const amount = this.$store.getters['user/getUserSetting'](setting) | ||||
|       if (!amount) return prefix | ||||
| 
 | ||||
|       let formattedTime = '' | ||||
|       if (amount <= 60) { | ||||
|         formattedTime = this.$getString('LabelJumpAmountSeconds', [amount]) | ||||
|       } else { | ||||
|         const minutes = Math.floor(amount / 60) | ||||
|         formattedTime = this.$getString('LabelJumpAmountMinutes', [minutes]) | ||||
|       } | ||||
| 
 | ||||
|       return `${prefix} - ${formattedTime}` | ||||
|     } | ||||
|   }, | ||||
|   mounted() {} | ||||
| } | ||||
| </script> | ||||
| </script> | ||||
|  | ||||
| @ -36,9 +36,9 @@ | ||||
|           </button> | ||||
|         </ui-tooltip> | ||||
| 
 | ||||
|         <ui-tooltip v-if="chapters.length" direction="top" :text="useChapterTrack ? $strings.LabelUseFullTrack : $strings.LabelUseChapterTrack"> | ||||
|           <button :aria-label="useChapterTrack ? $strings.LabelUseFullTrack : $strings.LabelUseChapterTrack" class="text-gray-300 mx-1 lg:mx-2 hover:text-white" @mousedown.prevent @mouseup.prevent @click.stop="setUseChapterTrack"> | ||||
|             <span class="material-symbols text-2xl sm:text-3xl transform transition-transform" :class="useChapterTrack ? 'rotate-180' : ''">timelapse</span> | ||||
|         <ui-tooltip direction="top" :text="$strings.LabelViewPlayerSettings"> | ||||
|           <button :aria-label="$strings.LabelViewPlayerSettings" class="outline-none text-gray-300 mx-1 lg:mx-2 hover:text-white" @mousedown.prevent @mouseup.prevent @click.stop="$emit('showPlayerSettings')"> | ||||
|             <span class="material-symbols text-2xl sm:text-2.5xl">settings_slow_motion</span> | ||||
|           </button> | ||||
|         </ui-tooltip> | ||||
|       </div> | ||||
| @ -90,13 +90,16 @@ export default { | ||||
|       seekLoading: false, | ||||
|       showChaptersModal: false, | ||||
|       currentTime: 0, | ||||
|       duration: 0, | ||||
|       useChapterTrack: false | ||||
|       duration: 0 | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     playbackRate() { | ||||
|       this.updateTimestamp() | ||||
|     }, | ||||
|     useChapterTrack() { | ||||
|       if (this.$refs.trackbar) this.$refs.trackbar.setUseChapterTrack(this.useChapterTrack) | ||||
|       this.updateTimestamp() | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
| @ -162,6 +165,10 @@ export default { | ||||
|     }, | ||||
|     playerQueueItems() { | ||||
|       return this.$store.state.playerQueueItems || [] | ||||
|     }, | ||||
|     useChapterTrack() { | ||||
|       const _useChapterTrack = this.$store.getters['user/getUserSetting']('useChapterTrack') || false | ||||
|       return this.chapters.length ? _useChapterTrack : false | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
| @ -310,9 +317,6 @@ export default { | ||||
|     init() { | ||||
|       this.playbackRate = this.$store.getters['user/getUserSetting']('playbackRate') || 1 | ||||
| 
 | ||||
|       const _useChapterTrack = this.$store.getters['user/getUserSetting']('useChapterTrack') || false | ||||
|       this.useChapterTrack = this.chapters.length ? _useChapterTrack : false | ||||
| 
 | ||||
|       if (this.$refs.trackbar) this.$refs.trackbar.setUseChapterTrack(this.useChapterTrack) | ||||
|       this.setPlaybackRate(this.playbackRate) | ||||
|     }, | ||||
|  | ||||
							
								
								
									
										151
									
								
								client/components/ui/SelectInput.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								client/components/ui/SelectInput.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,151 @@ | ||||
| <template> | ||||
|   <div class="relative w-full"> | ||||
|     <p v-if="label" class="text-sm font-semibold px-1" :class="disabled ? 'text-gray-300' : ''">{{ label }}</p> | ||||
|     <button ref="buttonWrapper" type="button" :aria-label="longLabel" :disabled="disabled" class="relative w-full border rounded shadow-sm pl-3 pr-8 py-2 text-left sm:text-sm" :class="buttonClass" aria-haspopup="listbox" aria-expanded="true" @click.stop.prevent="clickShowMenu"> | ||||
|       <span class="flex items-center"> | ||||
|         <span class="block truncate font-sans" :class="{ 'font-semibold': selectedSubtext, 'text-sm': small }">{{ selectedText }}</span> | ||||
|         <span v-if="selectedSubtext">: </span> | ||||
|         <span v-if="selectedSubtext" class="font-normal block truncate font-sans text-sm text-gray-400">{{ selectedSubtext }}</span> | ||||
|       </span> | ||||
|       <span class="ml-3 absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none"> | ||||
|         <span class="material-symbols text-2xl">expand_more</span> | ||||
|       </span> | ||||
|     </button> | ||||
| 
 | ||||
|     <transition name="menu"> | ||||
|       <ul ref="menu" v-show="showMenu" class="absolute z-60 -mt-px w-full bg-primary border border-black-200 shadow-lg rounded-md py-1 ring-1 ring-black ring-opacity-5 overflow-auto sm:text-sm" tabindex="-1" role="listbox" :style="{ maxHeight: menuMaxHeight }" v-click-outside="clickOutsideObj"> | ||||
|         <template v-for="item in itemsToShow"> | ||||
|           <li :key="item.value" class="text-gray-100 relative py-2 cursor-pointer hover:bg-black-400" :id="'listbox-option-' + item.value" role="option" tabindex="0" @keyup.enter="clickedOption(item.value)" @click.stop.prevent="clickedOption(item.value)"> | ||||
|             <div class="flex items-center"> | ||||
|               <span class="ml-3 block truncate font-sans text-sm" :class="{ 'font-semibold': item.subtext }">{{ item.text }}</span> | ||||
|               <span v-if="item.subtext">: </span> | ||||
|               <span v-if="item.subtext" class="font-normal block truncate font-sans text-sm text-gray-400">{{ item.subtext }}</span> | ||||
|             </div> | ||||
|           </li> | ||||
|         </template> | ||||
|       </ul> | ||||
|     </transition> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| export default { | ||||
|   props: { | ||||
|     value: [String, Number], | ||||
|     label: { | ||||
|       type: String, | ||||
|       default: '' | ||||
|     }, | ||||
|     items: { | ||||
|       type: Array, | ||||
|       default: () => [] | ||||
|     }, | ||||
|     disabled: Boolean, | ||||
|     small: Boolean, | ||||
|     menuMaxHeight: { | ||||
|       type: String, | ||||
|       default: '224px' | ||||
|     } | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       clickOutsideObj: { | ||||
|         handler: this.clickedOutside, | ||||
|         events: ['click'], | ||||
|         isActive: true | ||||
|       }, | ||||
|       menu: null, | ||||
|       showMenu: false | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     selected: { | ||||
|       get() { | ||||
|         return this.value | ||||
|       }, | ||||
|       set(val) { | ||||
|         this.$emit('input', val) | ||||
|       } | ||||
|     }, | ||||
|     itemsToShow() { | ||||
|       return this.items.map((i) => { | ||||
|         if (typeof i === 'string' || typeof i === 'number') { | ||||
|           return { | ||||
|             text: i, | ||||
|             value: i | ||||
|           } | ||||
|         } | ||||
|         return i | ||||
|       }) | ||||
|     }, | ||||
|     selectedItem() { | ||||
|       return this.itemsToShow.find((i) => i.value === this.selected) | ||||
|     }, | ||||
|     selectedText() { | ||||
|       return this.selectedItem ? this.selectedItem.text : '' | ||||
|     }, | ||||
|     selectedSubtext() { | ||||
|       return this.selectedItem ? this.selectedItem.subtext : '' | ||||
|     }, | ||||
|     buttonClass() { | ||||
|       var classes = [] | ||||
|       if (this.small) classes.push('h-9') | ||||
|       else classes.push('h-10') | ||||
| 
 | ||||
|       if (this.disabled) classes.push('cursor-not-allowed border-gray-600 bg-primary bg-opacity-70 border-opacity-70 text-gray-400') | ||||
|       else classes.push('cursor-pointer border-gray-600 bg-primary text-gray-100') | ||||
| 
 | ||||
|       return classes.join(' ') | ||||
|     }, | ||||
|     longLabel() { | ||||
|       let result = '' | ||||
|       if (this.label) result += this.label + ': ' | ||||
|       if (this.selectedText) result += this.selectedText | ||||
|       if (this.selectedSubtext) result += ' ' + this.selectedSubtext | ||||
|       return result | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     recalcMenuPos() { | ||||
|       if (!this.menu || !this.$refs.buttonWrapper) return | ||||
|       const boundingBox = this.$refs.buttonWrapper.getBoundingClientRect() | ||||
|       this.menu.style.top = boundingBox.y + boundingBox.height - 4 + 'px' | ||||
|       this.menu.style.left = boundingBox.x + 'px' | ||||
|       this.menu.style.width = boundingBox.width + 'px' | ||||
|     }, | ||||
|     unmountMountMenu() { | ||||
|       if (!this.$refs.menu || !this.$refs.buttonWrapper) return | ||||
|       this.menu = this.$refs.menu | ||||
|       this.menu.remove() | ||||
|     }, | ||||
|     clickShowMenu() { | ||||
|       if (this.disabled) return | ||||
|       if (!this.showMenu) this.handleShowMenu() | ||||
|       else this.handleCloseMenu() | ||||
|     }, | ||||
|     handleShowMenu() { | ||||
|       if (!this.menu) { | ||||
|         this.unmountMountMenu() | ||||
|       } | ||||
|       document.body.appendChild(this.menu) | ||||
|       this.recalcMenuPos() | ||||
|       this.showMenu = true | ||||
|     }, | ||||
|     handleCloseMenu() { | ||||
|       this.showMenu = false | ||||
|       if (this.menu) this.menu.remove() | ||||
|     }, | ||||
|     clickedOutside() { | ||||
|       this.handleCloseMenu() | ||||
|     }, | ||||
|     clickedOption(itemValue) { | ||||
|       this.selected = itemValue | ||||
|       this.handleCloseMenu() | ||||
|     } | ||||
|   }, | ||||
|   mounted() {}, | ||||
|   beforeDestroy() { | ||||
|     if (this.menu) this.menu.remove() | ||||
|   } | ||||
| } | ||||
| </script> | ||||
| @ -51,6 +51,12 @@ export default class PlayerHandler { | ||||
|     if (!this.episodeId) return null | ||||
|     return this.libraryItem.media.episodes.find(ep => ep.id === this.episodeId) | ||||
|   } | ||||
|   get jumpForwardAmount() { | ||||
|     return this.ctx.$store.getters['user/getUserSetting']('jumpForwardAmount') | ||||
|   } | ||||
|   get jumpBackwardAmount() { | ||||
|     return this.ctx.$store.getters['user/getUserSetting']('jumpBackwardAmount') | ||||
|   } | ||||
| 
 | ||||
|   setSessionId(sessionId) { | ||||
|     this.currentSessionId = sessionId | ||||
| @ -381,13 +387,15 @@ export default class PlayerHandler { | ||||
|   jumpBackward() { | ||||
|     if (!this.player) return | ||||
|     var currentTime = this.getCurrentTime() | ||||
|     this.seek(Math.max(0, currentTime - 10)) | ||||
|     const jumpAmount = this.jumpBackwardAmount | ||||
|     this.seek(Math.max(0, currentTime - jumpAmount)) | ||||
|   } | ||||
| 
 | ||||
|   jumpForward() { | ||||
|     if (!this.player) return | ||||
|     var currentTime = this.getCurrentTime() | ||||
|     this.seek(Math.min(currentTime + 10, this.getDuration())) | ||||
|     const jumpAmount = this.jumpForwardAmount | ||||
|     this.seek(Math.min(currentTime + jumpAmount, this.getDuration())) | ||||
|   } | ||||
| 
 | ||||
|   setVolume(volume) { | ||||
|  | ||||
| @ -14,7 +14,9 @@ export const state = () => ({ | ||||
|     seriesSortDesc: false, | ||||
|     seriesFilterBy: 'all', | ||||
|     authorSortBy: 'name', | ||||
|     authorSortDesc: false | ||||
|     authorSortDesc: false, | ||||
|     jumpForwardAmount: 10, | ||||
|     jumpBackwardAmount: 10, | ||||
|   } | ||||
| }) | ||||
| 
 | ||||
|  | ||||
| @ -155,6 +155,7 @@ | ||||
|   "HeaderPasswordAuthentication": "Password Authentication", | ||||
|   "HeaderPermissions": "Permissions", | ||||
|   "HeaderPlayerQueue": "Player Queue", | ||||
|   "HeaderPlayerSettings": "Player Settings", | ||||
|   "HeaderPlaylist": "Playlist", | ||||
|   "HeaderPlaylistItems": "Playlist Items", | ||||
|   "HeaderPodcastsToAdd": "Podcasts to Add", | ||||
| @ -343,6 +344,10 @@ | ||||
|   "LabelIntervalEveryHour": "Every hour", | ||||
|   "LabelInvert": "Invert", | ||||
|   "LabelItem": "Item", | ||||
|   "LabelJumpAmountMinutes": "{0} minutes", | ||||
|   "LabelJumpAmountSeconds": "{0} seconds", | ||||
|   "LabelJumpBackwardAmount": "Jump backward amount", | ||||
|   "LabelJumpForwardAmount": "Jump forward amount", | ||||
|   "LabelLanguage": "Language", | ||||
|   "LabelLanguageDefaultServer": "Default Server Language", | ||||
|   "LabelLanguages": "Languages", | ||||
| @ -598,6 +603,7 @@ | ||||
|   "LabelVersion": "Version", | ||||
|   "LabelViewBookmarks": "View bookmarks", | ||||
|   "LabelViewChapters": "View chapters", | ||||
|   "LabelViewPlayerSettings": "View player settings", | ||||
|   "LabelViewQueue": "View player queue", | ||||
|   "LabelVolume": "Volume", | ||||
|   "LabelWeekdaysToRun": "Weekdays to run", | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user