diff --git a/ui-src/src/components/StoryPlayer.vue b/ui-src/src/components/StoryPlayer.vue index 43f45fe..07a3758 100644 --- a/ui-src/src/components/StoryPlayer.vue +++ b/ui-src/src/components/StoryPlayer.vue @@ -31,6 +31,7 @@ import { speak, stopSpeak } from '../utils/speech.js' const EMOJI = { normal: '🙂', think: '🤔', happy: '😊', sad: '😢', surprise: '😮' } const PER_CHAR = 260 // 每字高亮推进毫秒(与 TTS 朗读大致同步) const PAUSE_MS = 700 // 句间停顿 +const MAX_LINE_MS = 20000 // 单句最长等待(TTS 无声兜底,防卡死) export default { name: 'StoryPlayer', @@ -42,7 +43,7 @@ export default { }, emits: ['done'], data() { - return { lineIndex: 0, charIndex: 0, speaking: false, moodTick: false, timers: [] } + return { lineIndex: 0, charIndex: 0, moodTick: false, timers: [], ended: false, lineDone: { hl: false, sp: false } } }, computed: { moodEmoji() { @@ -76,23 +77,45 @@ export default { } this.lineIndex = i this.charIndex = 0 + this.ended = false + this.lineDone = { hl: false, sp: false } this.moodTick = true - this.after(300, () => { this.moodTick = false }) - this.speaking = true - speak(line.text, '') + this.after(350, () => { this.moodTick = false }) uni.pageScrollTo({ scrollTop: 99999, duration: 200 }) const total = Array.from(line.text).length + // 逐字高亮推进(纯视觉节奏) const step = setInterval(() => { this.charIndex++ - if (this.charIndex > total) { + if (this.charIndex >= total) { clearInterval(step) - this.speaking = false - this.after(PAUSE_MS, () => this.playLine(i + 1)) + this.lineDone.hl = true + this.tryNext(i) } }, PER_CHAR) this.timers.push(step) + // 朗读读完才进下一句(TTS 慢时高亮等朗读);onEnd 与高亮都完成 → 停顿后下一句 + speak(line.text, '', () => { + this.lineDone.sp = true + this.tryNext(i) + }) + // 兜底:TTS 无声/超时(20s)强制推进防卡死 + this.after(MAX_LINE_MS, () => this.forceNext(i)) + }, + tryNext(i) { + if (this.ended || this.lineIndex !== i || !this.lineDone.hl || !this.lineDone.sp) return + this.ended = true + this.clearTimers() + this.after(PAUSE_MS, () => { this.ended = false; this.playLine(i + 1) }) + }, + forceNext(i) { + if (this.ended || this.lineIndex !== i) return + this.ended = true + this.clearTimers() + stopSpeak() + this.after(PAUSE_MS, () => { this.ended = false; this.playLine(i + 1) }) }, finish() { + this.ended = true this.clearTimers() stopSpeak() this.$emit('done') @@ -103,7 +126,7 @@ export default {