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:
Lunatic 2026-02-27 14:33:19 +08:00
parent 1d0b7e383a
commit d157388680
3 changed files with 159 additions and 2 deletions

View file

@ -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)
// introoutro
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) {
// skipIntrointro
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() {