From 033f3685f578f6f1f8a318feaf02475cad94d28e Mon Sep 17 00:00:00 2001 From: Vuong Hoang Date: Thu, 9 Jul 2026 01:41:26 -0700 Subject: [PATCH] fix(gateway-chat): resume AudioContext on user gesture (no-sound / autoplay) Browsers suspend the Web Audio AudioContext until a user gesture; speakQuotes fires on reply-complete (no active gesture), so a suspended context played silently. Prime/resume the context on any click or keydown (capture phase) so it's running before playback. Server side was fine throughout (/tts + /tts/stream both 200 with valid audio). --- stacks/gateway-chat/conf/index.html | 6 ++++++ tools/gateway-chat.html | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/stacks/gateway-chat/conf/index.html b/stacks/gateway-chat/conf/index.html index 2a1b032..c29dbd3 100644 --- a/stacks/gateway-chat/conf/index.html +++ b/stacks/gateway-chat/conf/index.html @@ -147,6 +147,12 @@ function showErr(text){ const m = addMsg('error','err'); m.body.className = 'err // --- mOrpheus TTS: streaming-decode auto-voice of quoted text (Web Audio, low TTFA) --- let audioCtx = null; // shared AudioContext let ttsGen = 0; // generation counter — a newer reply's stream supersedes older ones +function primeAudio(){ // browsers suspend the AudioContext until a user gesture; resume on ANY + if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)(); + if (audioCtx.state === 'suspended') audioCtx.resume(); // interaction so async playback isn't silent +} +document.addEventListener('click', primeAudio, true); +document.addEventListener('keydown', primeAudio, true); function extractQuotes(text){ const re = /“([^”]+)”|"([^"]+)"/g; // typographic "…" or straight "…" const out = []; let m; diff --git a/tools/gateway-chat.html b/tools/gateway-chat.html index 2a1b032..c29dbd3 100644 --- a/tools/gateway-chat.html +++ b/tools/gateway-chat.html @@ -147,6 +147,12 @@ function showErr(text){ const m = addMsg('error','err'); m.body.className = 'err // --- mOrpheus TTS: streaming-decode auto-voice of quoted text (Web Audio, low TTFA) --- let audioCtx = null; // shared AudioContext let ttsGen = 0; // generation counter — a newer reply's stream supersedes older ones +function primeAudio(){ // browsers suspend the AudioContext until a user gesture; resume on ANY + if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)(); + if (audioCtx.state === 'suspended') audioCtx.resume(); // interaction so async playback isn't silent +} +document.addEventListener('click', primeAudio, true); +document.addEventListener('keydown', primeAudio, true); function extractQuotes(text){ const re = /“([^”]+)”|"([^"]+)"/g; // typographic "…" or straight "…" const out = []; let m;