wishartgroup commited on
Commit
35331f3
Β·
verified Β·
1 Parent(s): 60f33c5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +39 -14
app.py CHANGED
@@ -109,14 +109,20 @@ def _parse_avatar_config(avatar_dir):
109
  def _resolve_voice(config: dict) -> str:
110
  """
111
  Pick the TTS voice from an avatar config.
112
- Precedence:
113
- 1. edgettsname β†’ raw edge-tts voice ID (e.g. "en-GB-RyanNeural")
114
- 2. groqttsname β†’ legacy short name, mapped via VOICE_MAP in tts.py
115
- 3. fallback default
116
- set_voice() in tts.py accepts either form (it detects edge-tts IDs by the "Neural" suffix
117
- and falls back to the legacy mapping otherwise), so we just hand back whichever is present.
 
118
  """
119
- return config.get("edgettsname") or config.get("groqttsname") or "diana"
 
 
 
 
 
120
 
121
 
122
  def _switch_avatar(avatar_name: str):
@@ -1072,6 +1078,7 @@ const btnClear=document.getElementById('btnClear');
1072
  // WEBSOCKET FRAME STREAMING
1073
  // ======================================================================
1074
  let ws=null, frameImg=new Image();
 
1075
 
1076
  function drawFrame(data){
1077
  const blob=new Blob([data], {type:'image/jpeg'});
@@ -1083,6 +1090,15 @@ function drawFrame(data){
1083
  frameImg.src=url;
1084
  }
1085
 
 
 
 
 
 
 
 
 
 
1086
  function connectWebSocket(){
1087
  const proto=location.protocol==='https:'?'wss:':'ws:';
1088
  ws=new WebSocket(proto+'//'+location.host+'/ws/video');
@@ -1094,15 +1110,20 @@ function connectWebSocket(){
1094
  if(typeof e.data==='string'){
1095
  const msg=JSON.parse(e.data);
1096
  if(msg.type==='speech_start' && msg.audio_url){
1097
- // Preload audio then play β€” frames draw continuously
1098
- const audio=new Audio(msg.audio_url);
1099
- audio.addEventListener('canplaythrough', ()=>{
1100
- audio.play().catch(err=>console.warn('[AUDIO]',err));
1101
- }, {once:true});
1102
- audio.load();
 
 
 
 
 
 
1103
  }
1104
  } else {
1105
- // Always draw frames immediately β€” server paces at 25fps
1106
  drawFrame(e.data);
1107
  }
1108
  };
@@ -1158,6 +1179,10 @@ async function sendMessage(){
1158
  typing.contentEl.classList.remove('typing-dots');
1159
  typing.contentEl.textContent=data.text;
1160
 
 
 
 
 
1161
  // Audio + lipsync frames arrive via WebSocket automatically
1162
  // Wait for speech duration before allowing next message
1163
  if(data.audio_duration){
 
109
  def _resolve_voice(config: dict) -> str:
110
  """
111
  Pick the TTS voice from an avatar config.
112
+ Precedence (first wins):
113
+ 1. cartesiavoicename β†’ Cartesia voice UUID (e.g. "e07c00bc-4134-...")
114
+ 2. edgettsname β†’ raw edge-tts voice ID (e.g. "en-GB-RyanNeural")
115
+ 3. groqttsname β†’ legacy short name, mapped via VOICE_MAP in tts.py
116
+ 4. fallback default
117
+ set_voice() in tts.py detects the format and routes to the right backend:
118
+ UUID β†’ Cartesia API, else β†’ edge-tts (raw or via VOICE_MAP).
119
  """
120
+ return (
121
+ config.get("cartesiavoicename")
122
+ or config.get("edgettsname")
123
+ or config.get("groqttsname")
124
+ or "diana"
125
+ )
126
 
127
 
128
  def _switch_avatar(avatar_name: str):
 
1078
  // WEBSOCKET FRAME STREAMING
1079
  // ======================================================================
1080
  let ws=null, frameImg=new Image();
1081
+ let pendingAudio=null, pendingAudioUrl=null;
1082
 
1083
  function drawFrame(data){
1084
  const blob=new Blob([data], {type:'image/jpeg'});
 
1090
  frameImg.src=url;
1091
  }
1092
 
1093
+ function preloadAudio(url){
1094
+ // Called from chat_stream response β€” gives ~2s headstart on HTTP fetch + buffer
1095
+ // so audio is ready to play instantly when speech_start fires.
1096
+ if(!url) return;
1097
+ pendingAudio=new Audio(url);
1098
+ pendingAudioUrl=url;
1099
+ pendingAudio.load();
1100
+ }
1101
+
1102
  function connectWebSocket(){
1103
  const proto=location.protocol==='https:'?'wss:':'ws:';
1104
  ws=new WebSocket(proto+'//'+location.host+'/ws/video');
 
1110
  if(typeof e.data==='string'){
1111
  const msg=JSON.parse(e.data);
1112
  if(msg.type==='speech_start' && msg.audio_url){
1113
+ if(pendingAudio && pendingAudioUrl===msg.audio_url){
1114
+ // Audio was preloaded after /api/chat/stream returned β€” play instantly
1115
+ pendingAudio.play().catch(err=>console.warn('[AUDIO]',err));
1116
+ pendingAudio=null; pendingAudioUrl=null;
1117
+ } else {
1118
+ // Fallback (greeting / no preload) β€” load + wait for canplaythrough
1119
+ const audio=new Audio(msg.audio_url);
1120
+ audio.addEventListener('canplaythrough', ()=>{
1121
+ audio.play().catch(err=>console.warn('[AUDIO]',err));
1122
+ }, {once:true});
1123
+ audio.load();
1124
+ }
1125
  }
1126
  } else {
 
1127
  drawFrame(e.data);
1128
  }
1129
  };
 
1179
  typing.contentEl.classList.remove('typing-dots');
1180
  typing.contentEl.textContent=data.text;
1181
 
1182
+ // Start fetching the TTS audio NOW β€” gives ~2s headstart so it's
1183
+ // buffered and ready to play instantly when WS emits speech_start.
1184
+ if(data.audio_url) preloadAudio(data.audio_url);
1185
+
1186
  // Audio + lipsync frames arrive via WebSocket automatically
1187
  // Wait for speech duration before allowing next message
1188
  if(data.audio_duration){