fix(stage-ui): TTS audio playback fails when navigating from config pages

This commit is contained in:
Neko Ayaka
2025-12-07 23:38:45 +08:00
parent f68f1a7040
commit ed3919ce76
2 changed files with 34 additions and 3 deletions
@@ -308,6 +308,12 @@ defineExpose({
})
onPlaybackStarted(({ text }) => {
// NOTICE: currently, postCaption, postPresent from useBroadcastChannel may throw error
// once we navigate away from the page that created the BroadcastChannel,
// as the channel gets closed on unmount, leading to "Failed to execute 'postMessage' on 'BroadcastChannel': The channel is closed."
// error that may block hooks or throw exceptions silently.
//
// TODO: we should consider better way to manage BroadcastChannel lifecycle to avoid such issues.
assistantCaption.value += ` ${text}`
postCaption({ type: 'caption-assistant', text: assistantCaption.value })
postPresent({ type: 'assistant-append', text })
+28 -3
View File
@@ -151,6 +151,12 @@ export const usePipelineCharacterSpeechPlaybackQueueStore = defineStore('pipelin
handlers: [
(ctx) => {
return new Promise((resolve) => {
// NOTICE: here clearPlaying is called because that createQueue guarantees that only one handler is running at a time,
// so we can safely stop any currently playing audio before starting a new one. If multiple audios were to play
// simultaneously, this would lead to overlapping sounds.
//
// TODO: when migrating to better solution for audio playback management, be careful with this part.
// as without proper singleton gated, this may lead to audio cutoffs.
clearPlaying()
if (!audioContext.value) {
@@ -168,15 +174,34 @@ export const usePipelineCharacterSpeechPlaybackQueueStore = defineStore('pipelin
source.connect(audioAnalyser.value!)
// Start playing the audio
for (const hook of onPlaybackStartedHooks.value) hook({ text: ctx.data.text })
for (const hook of onPlaybackStartedHooks.value) {
try {
hook({ text: ctx.data.text })
}
catch (err) {
// NOTICE: onPlaybackStarted hook errors should not block audio playback.
// in currently use case of Stage.vue, BroadcastChannel is involved,
// navigating from pages may cause unexpected onUnmounted calls to close the channel,
// which throws error when posting message to closed channel.
//
// TODO: we should consider better way to manage BroadcastChannel lifecycle to avoid such issues.
console.error('Error in onPlaybackStarted hook:', err)
}
}
currentAudioSource.value = source
source.start(0)
source.onended = () => {
// Play special token: delay or emotion
if (ctx.data.special) {
for (const hook of onPlaybackFinishedHooks.value)
hook({ special: ctx.data.special })
for (const hook of onPlaybackFinishedHooks.value) {
try {
hook({ special: ctx.data.special })
}
catch (err) {
console.error('Error in onPlaybackFinished hook:', err)
}
}
}
if (currentAudioSource.value === source) {