diff --git a/frontend/src/views/SettingsView.vue b/frontend/src/views/SettingsView.vue index edc02d7..dd1b408 100644 --- a/frontend/src/views/SettingsView.vue +++ b/frontend/src/views/SettingsView.vue @@ -524,7 +524,6 @@ const voiceBlend = ref([ ]); const blendPreviewText = ref("Hello! I'm your assistant. How can I help you today?"); const blendPreviewing = ref(false); -let _blendAudioUrl = ""; function addBlendSlot() { voiceBlend.value.push({ voice: "af_heart", weight: 1.0 }); @@ -537,6 +536,8 @@ function removeBlendSlot(idx: number) { async function previewBlend() { if (blendPreviewing.value || !blendPreviewText.value.trim()) return; blendPreviewing.value = true; + // Create AudioContext synchronously in user-gesture context to bypass autoplay policy + const ctx = new AudioContext(); try { const blob = await synthesiseSpeech( blendPreviewText.value, @@ -544,10 +545,11 @@ async function previewBlend() { voiceTtsSpeed.value, voiceBlend.value, ); - if (_blendAudioUrl) URL.revokeObjectURL(_blendAudioUrl); - _blendAudioUrl = URL.createObjectURL(blob); - const audio = new Audio(_blendAudioUrl); - audio.play(); + const buf = await ctx.decodeAudioData(await blob.arrayBuffer()); + const src = ctx.createBufferSource(); + src.buffer = buf; + src.connect(ctx.destination); + src.start(0); } catch { toastStore.show("Preview failed — TTS may not be ready", "error"); } finally { @@ -572,20 +574,23 @@ async function loadVoiceTab() { } const voicePreviewing = ref(false); -let _voicePreviewUrl = ""; async function previewVoice() { if (voicePreviewing.value) return; voicePreviewing.value = true; + // Create AudioContext synchronously in user-gesture context to bypass autoplay policy + const ctx = new AudioContext(); try { const blob = await synthesiseSpeech( "Hello! I'm your assistant. How can I help you today?", voiceTtsVoice.value, voiceTtsSpeed.value, ); - if (_voicePreviewUrl) URL.revokeObjectURL(_voicePreviewUrl); - _voicePreviewUrl = URL.createObjectURL(blob); - new Audio(_voicePreviewUrl).play(); + const buf = await ctx.decodeAudioData(await blob.arrayBuffer()); + const src = ctx.createBufferSource(); + src.buffer = buf; + src.connect(ctx.destination); + src.start(0); } catch { toastStore.show("Preview failed — TTS may not be ready", "error"); } finally {