diff --git a/client/components/app/MediaPlayerContainer.vue b/client/components/app/MediaPlayerContainer.vue
index 1a2b1d30a..474b5a598 100644
--- a/client/components/app/MediaPlayerContainer.vue
+++ b/client/components/app/MediaPlayerContainer.vue
@@ -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() {
diff --git a/client/components/modals/PlayerSettingsModal.vue b/client/components/modals/PlayerSettingsModal.vue
index dfac28cfd..b7bbd1c6a 100644
--- a/client/components/modals/PlayerSettingsModal.vue
+++ b/client/components/modals/PlayerSettingsModal.vue
@@ -17,6 +17,29 @@
+
+
+
@@ -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)
}
}
diff --git a/client/store/user.js b/client/store/user.js
index 96e79d12f..e8bfb3da3 100644
--- a/client/store/user.js
+++ b/client/store/user.js
@@ -18,7 +18,8 @@ export const state = () => ({
authorSortBy: 'name',
authorSortDesc: false,
jumpForwardAmount: 10,
- jumpBackwardAmount: 10
+ jumpBackwardAmount: 10,
+ bookSkipSettings: {} // 书籍跳过配置 { [libraryItemId]: { skipIntro, introDuration, skipOutro, outroDuration } }
}
})