mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-02-28 21:19:42 +00:00
Add per-chapter auto skip intro/outro for web player
Per-book skip settings stored in browser localStorage. Checks each chapter's intro/outro zone during playback and automatically seeks past them, matching the behavior of the Android app implementation.
This commit is contained in:
parent
1d0b7e383a
commit
d157388680
3 changed files with 159 additions and 2 deletions
|
|
@ -308,6 +308,9 @@ export default {
|
|||
if (this.sleepTimerType === this.$constants.SleepTimerTypes.CHAPTER && this.sleepTimerSet) {
|
||||
this.checkChapterEnd()
|
||||
}
|
||||
|
||||
// 检查章节intro/outro跳过
|
||||
this.checkAndSkipIntroOutro(time)
|
||||
},
|
||||
setDuration(duration) {
|
||||
this.totalDuration = duration
|
||||
|
|
@ -543,6 +546,81 @@ export default {
|
|||
this.playerHandler.resetPlayer() // Closes player without reporting to server
|
||||
this.$store.commit('setMediaPlaying', null)
|
||||
}
|
||||
},
|
||||
|
||||
// 获取当前书籍的跳过设置
|
||||
getBookSkipSettings() {
|
||||
if (!this.streamLibraryItem) return null
|
||||
const bookSkipSettings = this.$store.getters['user/getUserSetting']('bookSkipSettings') || {}
|
||||
return bookSkipSettings[this.streamLibraryItem.id] || {}
|
||||
},
|
||||
|
||||
// 检查并执行章节intro/outro跳过
|
||||
checkAndSkipIntroOutro(currentTime) {
|
||||
const skipSettings = this.getBookSkipSettings()
|
||||
if (!skipSettings) return
|
||||
|
||||
const doSkipIntro = skipSettings.skipIntro && skipSettings.introDuration > 0
|
||||
const doSkipOutro = skipSettings.skipOutro && skipSettings.outroDuration > 0
|
||||
if (!doSkipIntro && !doSkipOutro) return
|
||||
if (!this.isPlaying || !this.chapters.length) return
|
||||
|
||||
// 防重入:正在跳过时等待到达目标位置后再解除
|
||||
if (this._isSkipping) {
|
||||
if (this._skipTarget != null && currentTime >= this._skipTarget - 0.5) {
|
||||
this._isSkipping = false
|
||||
this._skipTarget = null
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const chapter = this.chapters.find((ch) => ch.start <= currentTime && currentTime < ch.end)
|
||||
if (!chapter) return
|
||||
|
||||
const introDuration = doSkipIntro ? skipSettings.introDuration : 0
|
||||
const outroDuration = doSkipOutro ? skipSettings.outroDuration : 0
|
||||
|
||||
const introEndTime = Math.min(chapter.start + introDuration, chapter.end)
|
||||
const outroStartTime = Math.max(chapter.end - outroDuration, chapter.start)
|
||||
|
||||
// 短章节:intro和outro区间重叠则不跳
|
||||
if (doSkipIntro && doSkipOutro && introEndTime > outroStartTime) return
|
||||
|
||||
// 检查是否在intro区间
|
||||
if (doSkipIntro && currentTime < introEndTime) {
|
||||
const target = introEndTime + 0.5
|
||||
this._isSkipping = true
|
||||
this._skipTarget = target
|
||||
this.seek(target)
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否在outro区间
|
||||
if (doSkipOutro && currentTime >= outroStartTime) {
|
||||
const chapterIndex = this.chapters.indexOf(chapter)
|
||||
const nextChapter = this.chapters[chapterIndex + 1]
|
||||
|
||||
if (nextChapter) {
|
||||
// 有下一章:跳到下一章开头(如果同时开了skipIntro则跳过intro)
|
||||
let target = nextChapter.start
|
||||
if (doSkipIntro) {
|
||||
const nextIntroEnd = Math.min(nextChapter.start + introDuration, nextChapter.end)
|
||||
const nextOutroStart = Math.max(nextChapter.end - outroDuration, nextChapter.start)
|
||||
// 确保下一章intro/outro不重叠
|
||||
if (nextIntroEnd <= nextOutroStart) {
|
||||
target = nextIntroEnd + 0.5
|
||||
}
|
||||
}
|
||||
this._isSkipping = true
|
||||
this._skipTarget = target
|
||||
this.seek(target)
|
||||
} else {
|
||||
// 最后一章:跳到结尾,触发播放完成
|
||||
this._isSkipping = true
|
||||
this._skipTarget = chapter.end
|
||||
this.seek(chapter.end)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,29 @@
|
|||
<div class="flex items-center mb-4">
|
||||
<ui-select-input v-model="playbackRateIncrementDecrement" :label="$strings.LabelPlaybackRateIncrementDecrement" menuMaxHeight="250px" :items="playbackRateIncrementDecrementValues" @input="setPlaybackRateIncrementDecrementAmount" />
|
||||
</div>
|
||||
|
||||
<!-- 书籍跳过配置 -->
|
||||
<div class="border-t pt-4 mt-6" v-if="currentLibraryItemId">
|
||||
<h4 class="text-lg font-medium mb-4">本书跳过设置</h4>
|
||||
|
||||
<div class="flex items-center mb-4">
|
||||
<ui-toggle-switch v-model="skipIntro" @input="setSkipIntro" />
|
||||
<div class="pl-4 flex-1">
|
||||
<span>跳过开头</span>
|
||||
</div>
|
||||
<ui-text-input v-model="introDuration" type="number" min="0" max="60" @input="setIntroDuration" class="w-20" />
|
||||
<span class="ml-2 text-sm text-gray-400">秒</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center mb-4">
|
||||
<ui-toggle-switch v-model="skipOutro" @input="setSkipOutro" />
|
||||
<div class="pl-4 flex-1">
|
||||
<span>跳过结尾</span>
|
||||
</div>
|
||||
<ui-text-input v-model="outroDuration" type="number" min="0" max="60" @input="setOutroDuration" class="w-20" />
|
||||
<span class="ml-2 text-sm text-gray-400">秒</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</modals-modal>
|
||||
</template>
|
||||
|
|
@ -40,7 +63,14 @@ export default {
|
|||
jumpForwardAmount: 10,
|
||||
jumpBackwardAmount: 10,
|
||||
playbackRateIncrementDecrementValues: [0.1, 0.05],
|
||||
playbackRateIncrementDecrement: 0.1
|
||||
playbackRateIncrementDecrement: 0.1,
|
||||
|
||||
// 书籍跳过设置
|
||||
currentLibraryItemId: null,
|
||||
skipIntro: false,
|
||||
introDuration: 10,
|
||||
skipOutro: false,
|
||||
outroDuration: 10
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -69,19 +99,67 @@ export default {
|
|||
this.playbackRateIncrementDecrement = val
|
||||
this.$store.dispatch('user/updateUserSettings', { playbackRateIncrementDecrement: val })
|
||||
},
|
||||
|
||||
// 书籍跳过设置方法
|
||||
setSkipIntro() {
|
||||
this.updateBookSkipSetting('skipIntro', this.skipIntro)
|
||||
},
|
||||
setIntroDuration() {
|
||||
this.introDuration = Math.max(0, Math.min(60, parseInt(this.introDuration) || 0))
|
||||
this.updateBookSkipSetting('introDuration', this.introDuration)
|
||||
},
|
||||
setSkipOutro() {
|
||||
this.updateBookSkipSetting('skipOutro', this.skipOutro)
|
||||
},
|
||||
setOutroDuration() {
|
||||
this.outroDuration = Math.max(0, Math.min(60, parseInt(this.outroDuration) || 0))
|
||||
this.updateBookSkipSetting('outroDuration', this.outroDuration)
|
||||
},
|
||||
updateBookSkipSetting(key, value) {
|
||||
if (!this.currentLibraryItemId) return
|
||||
|
||||
const bookSkipSettings = { ...this.$store.getters['user/getUserSetting']('bookSkipSettings') || {} }
|
||||
if (!bookSkipSettings[this.currentLibraryItemId]) {
|
||||
bookSkipSettings[this.currentLibraryItemId] = {}
|
||||
}
|
||||
bookSkipSettings[this.currentLibraryItemId][key] = value
|
||||
this.$store.dispatch('user/updateUserSettings', { bookSkipSettings })
|
||||
},
|
||||
settingsUpdated() {
|
||||
this.useChapterTrack = this.$store.getters['user/getUserSetting']('useChapterTrack')
|
||||
this.jumpForwardAmount = this.$store.getters['user/getUserSetting']('jumpForwardAmount')
|
||||
this.jumpBackwardAmount = this.$store.getters['user/getUserSetting']('jumpBackwardAmount')
|
||||
this.playbackRateIncrementDecrement = this.$store.getters['user/getUserSetting']('playbackRateIncrementDecrement')
|
||||
|
||||
// 加载当前书籍的跳过设置
|
||||
this.loadBookSkipSettings()
|
||||
},
|
||||
loadBookSkipSettings() {
|
||||
// 获取当前播放的书籍ID
|
||||
const mediaPlayerContainer = this.$root.$refs.mediaPlayerContainer || this.$parent.$refs.mediaPlayerContainer
|
||||
if (mediaPlayerContainer && mediaPlayerContainer.streamLibraryItem) {
|
||||
this.currentLibraryItemId = mediaPlayerContainer.streamLibraryItem.id
|
||||
|
||||
const bookSkipSettings = this.$store.getters['user/getUserSetting']('bookSkipSettings') || {}
|
||||
const currentBookSettings = bookSkipSettings[this.currentLibraryItemId] || {}
|
||||
|
||||
this.skipIntro = currentBookSettings.skipIntro || false
|
||||
this.introDuration = currentBookSettings.introDuration || 10
|
||||
this.skipOutro = currentBookSettings.skipOutro || false
|
||||
this.outroDuration = currentBookSettings.outroDuration || 10
|
||||
} else {
|
||||
this.currentLibraryItemId = null
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.settingsUpdated()
|
||||
this.$eventBus.$on('user-settings', this.settingsUpdated)
|
||||
this.$eventBus.$on('playback-session-changed', this.loadBookSkipSettings)
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$eventBus.$off('user-settings', this.settingsUpdated)
|
||||
this.$eventBus.$off('playback-session-changed', this.loadBookSkipSettings)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@ export const state = () => ({
|
|||
authorSortBy: 'name',
|
||||
authorSortDesc: false,
|
||||
jumpForwardAmount: 10,
|
||||
jumpBackwardAmount: 10
|
||||
jumpBackwardAmount: 10,
|
||||
bookSkipSettings: {} // 书籍跳过配置 { [libraryItemId]: { skipIntro, introDuration, skipOutro, outroDuration } }
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue