@@ -2921,32 +2921,31 @@ <h1><span class="brand-simp">Simpatico</span><span class="brand-hr">HR</span></h
29212921 // ══════════════════════════════════════════════════════════════
29222922 // Track whether TTS has been unlocked by a user gesture
29232923 let _ttsUnlocked = false ;
2924-
2925- // Unlock TTS with a real utterance (empty strings are silently ignored by Chrome)
2926- function unlockTTS ( ) {
2924+ async function startSession ( ) {
2925+ // STEP 1: UNLOCK TTS ENGINE ON USER GESTURE (synchronous)
2926+ // Chrome requires speak() during a click handler callback.
2927+ // CRITICAL: NO synth.cancel() before speak() - it kills the engine
29272928 try {
2928- state . voice . synth . cancel ( ) ; // clear any stale queue
2929- const warmup = new SpeechSynthesisUtterance ( '.' ) ;
2930- warmup . volume = 0.01 ; // near-silent but non-zero (volume 0 may not init audio pipeline)
2931- warmup . rate = 10 ; // fastest possible
2929+ const synth = state . voice . synth ;
2930+ const voices = synth . getVoices ( ) ;
2931+ console . log ( '[TTS-Init] Voices:' , voices . length ,
2932+ 'preferred:' , state . voice . preferredVoice ?. name || 'NONE' ,
2933+ 'synth:' , synth . speaking , synth . pending , synth . paused ) ;
2934+
2935+ const warmup = new SpeechSynthesisUtterance ( 'test' ) ;
2936+ warmup . volume = 0.01 ;
2937+ warmup . rate = 10 ;
29322938 warmup . pitch = 0.01 ;
2933- if ( state . voice . preferredVoice ) warmup . voice = state . voice . preferredVoice ;
2934- warmup . lang = state . interviewLanguage || 'en-IN' ;
2935- warmup . onend = ( ) => { _ttsUnlocked = true ; console . log ( '[TTS] ✓ Engine unlocked via warmup ' ) ; } ;
2936- warmup . onerror = ( ) => { console . warn ( '[TTS] Warmup utterance error — will retry' ) ; } ;
2937- state . voice . synth . speak ( warmup ) ;
2938- console . log ( '[TTS] Warmup utterance dispatched (text=".", vol=0.01) ' ) ;
2939+ // Intentionally NOT setting voice/lang on warmup
2940+ warmup . onstart = ( ) => { console . log ( '[TTS-Init] Warmup STARTED' ) ; } ;
2941+ warmup . onend = ( ) => { _ttsUnlocked = true ; console . log ( '[TTS-Init] Warmup ENDED - unlocked ' ) ; } ;
2942+ warmup . onerror = ( e ) => { console . warn ( '[TTS-Init ] Warmup error:' , e . error ) ; _ttsUnlocked = true ; } ;
2943+ synth . speak ( warmup ) ;
2944+ console . log ( '[TTS-Init ] Warmup dispatched' ) ;
29392945 } catch ( e ) {
2940- console . warn ( '[TTS] Warmup failed:' , e ) ;
2946+ console . error ( '[TTS-Init] Exception:' , e ) ;
2947+ _ttsUnlocked = true ;
29412948 }
2942- }
2943-
2944- async function startSession ( ) {
2945- // ── CRITICAL: Warm up TTS engine on user gesture ──
2946- // Chrome's autoplay policy requires the FIRST speechSynthesis.speak()
2947- // to happen during a user gesture callback. Must use NON-EMPTY text
2948- // (empty strings are silently ignored) with non-zero volume.
2949- unlockTTS ( ) ;
29502949
29512950 // Request screen share
29522951 if ( ! state . media . screen ) {
@@ -2973,11 +2972,6 @@ <h1><span class="brand-simp">Simpatico</span><span class="brand-hr">HR</span></h
29732972 }
29742973 }
29752974
2976- // Re-unlock TTS after getDisplayMedia (the system dialog breaks gesture context)
2977- if ( ! _ttsUnlocked ) {
2978- console . log ( '[TTS] Re-unlocking after screen share dialog...' ) ;
2979- unlockTTS ( ) ;
2980- }
29812975
29822976 document . getElementById ( 'setupOverlay' ) . style . display = 'none' ;
29832977 document . getElementById ( 'interviewApp' ) . style . display = 'flex' ;
@@ -3006,10 +3000,17 @@ <h1><span class="brand-simp">Simpatico</span><span class="brand-hr">HR</span></h
30063000 // Wait for TTS warmup to actually complete (onend sets _ttsUnlocked)
30073001 // The '.' utterance at rate=10 finishes in ~50ms, but we wait up to 2s as safety
30083002 const ttsWaitStart = Date . now ( ) ;
3009- while ( ! _ttsUnlocked && ( Date . now ( ) - ttsWaitStart ) < 2000 ) {
3003+ while ( ! _ttsUnlocked && ( Date . now ( ) - ttsWaitStart ) < 3000 ) {
30103004 await sleep ( 50 ) ;
30113005 }
3012- console . log ( '[TTS] Warmup wait done, unlocked=' , _ttsUnlocked , 'waited=' , Date . now ( ) - ttsWaitStart , 'ms' ) ;
3006+ console . log ( '[TTS-Init] Ready to speak. unlocked=' , _ttsUnlocked ,
3007+ 'waited=' , Date . now ( ) - ttsWaitStart , 'ms' ,
3008+ 'synth.speaking=' , state . voice . synth . speaking ,
3009+ 'voices=' , state . voice . synth . getVoices ( ) . length ) ;
3010+
3011+ if ( ! _ttsUnlocked ) {
3012+ console . warn ( '[TTS-Init] WARNING: warmup never completed. Speaking anyway...' ) ;
3013+ }
30133014
30143015 await aiSpeak ( introQuestion ) ;
30153016 }
0 commit comments