AgentNewTwo commited on
Commit
3ed8155
·
1 Parent(s): 5576640

Buffer ICE without changing WebRTC component identity

Browse files
Files changed (1) hide show
  1. streaming_app.py +52 -0
streaming_app.py CHANGED
@@ -1,5 +1,6 @@
1
  from __future__ import annotations
2
 
 
3
  import copy
4
  import json
5
  import multiprocessing
@@ -8,6 +9,7 @@ import tarfile
8
  import threading
9
  import time
10
  import uuid
 
11
  from pathlib import Path
12
 
13
  import gradio as gr
@@ -224,6 +226,47 @@ async def get_turn_configuration():
224
  return configuration
225
 
226
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
  def new_bridge(oauth_token: gr.OAuthToken | None):
228
  remember_turn_token(oauth_token)
229
  input_queue = _manager.Queue(INPUT_QUEUE_FRAMES)
@@ -284,6 +327,8 @@ class PersonaPlexQueueHandler(StreamHandler):
284
  input_sample_rate=OUTPUT_SAMPLE_RATE,
285
  fps=WEBRTC_FRAME_RATE,
286
  )
 
 
287
 
288
  def _bridge(self):
289
  # FastRTC prepends the WebRTC component value to additional inputs.
@@ -301,6 +346,9 @@ class PersonaPlexQueueHandler(StreamHandler):
301
  return
302
  sample_rate, array = frame
303
  audio = np.asarray(array, dtype=np.int16).reshape(-1)
 
 
 
304
  if bounded_put(input_queue, (int(sample_rate), audio)):
305
  counters["input_drops"] = int(counters.get("input_drops", 0)) + 1
306
 
@@ -315,6 +363,9 @@ class PersonaPlexQueueHandler(StreamHandler):
315
  audio = output_queue.get_nowait()
316
  except queue.Empty:
317
  return None
 
 
 
318
  return OUTPUT_SAMPLE_RATE, np.asarray(audio, dtype=np.int16).reshape(1, -1)
319
 
320
  def copy(self):
@@ -407,6 +458,7 @@ def run_streaming_session(
407
  drain_proxy_queue(input_queue)
408
  ready_seconds = round(time.perf_counter() - session_started, 3)
409
  ready_event.set()
 
410
  live_started = time.perf_counter()
411
  status = (
412
  f"**Engine ready in {ready_seconds:.1f}s.** Start the WebRTC microphone and speak naturally. "
 
1
  from __future__ import annotations
2
 
3
+ import asyncio
4
  import copy
5
  import json
6
  import multiprocessing
 
9
  import threading
10
  import time
11
  import uuid
12
+ from collections import defaultdict
13
  from pathlib import Path
14
 
15
  import gradio as gr
 
226
  return configuration
227
 
228
 
229
+ _fastrtc_handle_offer = WebRTC.handle_offer
230
+
231
+
232
+ async def reliable_handle_offer(self, body, set_outputs):
233
+ """Buffer trickled ICE candidates until FastRTC registers their SDP offer."""
234
+ lock = getattr(self, "_roleforge_signaling_lock", None)
235
+ if lock is None:
236
+ lock = asyncio.Lock()
237
+ self._roleforge_signaling_lock = lock
238
+ self._roleforge_pending_ice = defaultdict(list)
239
+
240
+ async with lock:
241
+ webrtc_id = body.get("webrtc_id")
242
+ is_candidate = body.get("type") == "ice-candidate" and "candidate" in body
243
+
244
+ if is_candidate and webrtc_id not in self.pcs:
245
+ pending = self._roleforge_pending_ice[webrtc_id]
246
+ if len(pending) < 32:
247
+ pending.append(body)
248
+ print(f"Buffered early ICE candidate for pending connection: {webrtc_id}")
249
+ return {"status": "success"}
250
+
251
+ response = await _fastrtc_handle_offer(self, body, set_outputs)
252
+
253
+ if not is_candidate and webrtc_id in self.pcs:
254
+ pending = self._roleforge_pending_ice.pop(webrtc_id, [])
255
+ for candidate in pending:
256
+ await _fastrtc_handle_offer(self, candidate, set_outputs)
257
+ print(
258
+ f"Registered WebRTC offer and replayed {len(pending)} early ICE candidates: "
259
+ f"{webrtc_id}"
260
+ )
261
+
262
+ return response
263
+
264
+
265
+ # Preserve FastRTC's exact WebRTC component class/frontend bundle. Subclassing a
266
+ # custom Gradio component changes its frontend component name and breaks SSR.
267
+ WebRTC.handle_offer = reliable_handle_offer
268
+
269
+
270
  def new_bridge(oauth_token: gr.OAuthToken | None):
271
  remember_turn_token(oauth_token)
272
  input_queue = _manager.Queue(INPUT_QUEUE_FRAMES)
 
327
  input_sample_rate=OUTPUT_SAMPLE_RATE,
328
  fps=WEBRTC_FRAME_RATE,
329
  )
330
+ self._logged_input = False
331
+ self._logged_output = False
332
 
333
  def _bridge(self):
334
  # FastRTC prepends the WebRTC component value to additional inputs.
 
346
  return
347
  sample_rate, array = frame
348
  audio = np.asarray(array, dtype=np.int16).reshape(-1)
349
+ if not self._logged_input:
350
+ print("WebRTC audio bridge received its first input frame.")
351
+ self._logged_input = True
352
  if bounded_put(input_queue, (int(sample_rate), audio)):
353
  counters["input_drops"] = int(counters.get("input_drops", 0)) + 1
354
 
 
363
  audio = output_queue.get_nowait()
364
  except queue.Empty:
365
  return None
366
+ if not self._logged_output:
367
+ print("WebRTC audio bridge emitted its first output frame.")
368
+ self._logged_output = True
369
  return OUTPUT_SAMPLE_RATE, np.asarray(audio, dtype=np.int16).reshape(1, -1)
370
 
371
  def copy(self):
 
458
  drain_proxy_queue(input_queue)
459
  ready_seconds = round(time.perf_counter() - session_started, 3)
460
  ready_event.set()
461
+ print("PersonaPlex GPU session is ready and waiting for WebRTC audio.")
462
  live_started = time.perf_counter()
463
  status = (
464
  f"**Engine ready in {ready_seconds:.1f}s.** Start the WebRTC microphone and speak naturally. "