From 3e09457b2028221109f6415f7f1911bb417e179c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=96=8C?= <259278618@qq.com> Date: Thu, 13 Aug 2026 16:11:29 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20StoryPlayer=20TTS=20=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E6=8E=A8=E8=BF=9B=EF=BC=88speak=20onEnd=EF=BC=89+=20=E8=B7=B3?= =?UTF-8?q?=E8=BF=87=E6=8C=89=E9=92=AE=E5=B8=B8=E9=A9=BB=20+=20=E7=AB=9E?= =?UTF-8?q?=E6=80=81=E9=98=B2=E6=8A=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui-src/src/components/StoryPlayer.vue | 39 +++++++++++++++++++++------ ui-src/src/utils/speech.js | 20 ++++++++------ 2 files changed, 43 insertions(+), 16 deletions(-) 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 {