AgentNewTwo commited on
Commit
2aceb54
·
1 Parent(s): a4a16d2

Buffer early WebRTC ICE candidates

Browse files
Files changed (1) hide show
  1. streaming_app.py +57 -1
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
@@ -16,6 +18,7 @@ import sentencepiece
16
  import spaces
17
  import torch
18
  from fastrtc import StreamHandler, WebRTC, get_cloudflare_turn_credentials_async
 
19
  from huggingface_hub import hf_hub_download
20
 
21
 
@@ -224,6 +227,50 @@ 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 +331,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 +350,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 +367,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 +462,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. "
@@ -561,7 +617,7 @@ with gr.Blocks(title="RoleForge Streaming Spike", theme=gr.themes.Soft(), css=CS
561
  with gr.Column(scale=2):
562
  gr.Markdown("### 🎙️ Live Stage")
563
  status = gr.Markdown("Sign in, choose the cue, then start the live engine.")
564
- webrtc = WebRTC(
565
  label="2. Connect microphone after Engine ready",
566
  modality="audio",
567
  mode="send-receive",
 
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
 
18
  import spaces
19
  import torch
20
  from fastrtc import StreamHandler, WebRTC, get_cloudflare_turn_credentials_async
21
+ from gradio.components.base import server
22
  from huggingface_hub import hf_hub_download
23
 
24
 
 
227
  return configuration
228
 
229
 
230
+ class ReliableWebRTC(WebRTC):
231
+ """Serialize FastRTC signaling and replay ICE candidates that beat the SDP offer."""
232
+
233
+ def __init__(self, *args, **kwargs):
234
+ super().__init__(*args, **kwargs)
235
+ self._signaling_lock = None
236
+ self._pending_ice = defaultdict(list)
237
+
238
+ @server
239
+ async def offer(self, body):
240
+ if self._signaling_lock is None:
241
+ self._signaling_lock = asyncio.Lock()
242
+
243
+ async with self._signaling_lock:
244
+ webrtc_id = body.get("webrtc_id")
245
+ is_candidate = body.get("type") == "ice-candidate" and "candidate" in body
246
+
247
+ if is_candidate and webrtc_id not in self.pcs:
248
+ pending = self._pending_ice[webrtc_id]
249
+ if len(pending) < 32:
250
+ pending.append(body)
251
+ print(f"Buffered early ICE candidate for pending connection: {webrtc_id}")
252
+ return {"status": "success"}
253
+
254
+ response = await self.handle_offer(
255
+ body,
256
+ self.set_additional_outputs(webrtc_id),
257
+ )
258
+
259
+ if not is_candidate and webrtc_id in self.pcs:
260
+ pending = self._pending_ice.pop(webrtc_id, [])
261
+ for candidate in pending:
262
+ await self.handle_offer(
263
+ candidate,
264
+ self.set_additional_outputs(webrtc_id),
265
+ )
266
+ print(
267
+ f"Registered WebRTC offer and replayed {len(pending)} early ICE candidates: "
268
+ f"{webrtc_id}"
269
+ )
270
+
271
+ return response
272
+
273
+
274
  def new_bridge(oauth_token: gr.OAuthToken | None):
275
  remember_turn_token(oauth_token)
276
  input_queue = _manager.Queue(INPUT_QUEUE_FRAMES)
 
331
  input_sample_rate=OUTPUT_SAMPLE_RATE,
332
  fps=WEBRTC_FRAME_RATE,
333
  )
334
+ self._logged_input = False
335
+ self._logged_output = False
336
 
337
  def _bridge(self):
338
  # FastRTC prepends the WebRTC component value to additional inputs.
 
350
  return
351
  sample_rate, array = frame
352
  audio = np.asarray(array, dtype=np.int16).reshape(-1)
353
+ if not self._logged_input:
354
+ print("WebRTC audio bridge received its first input frame.")
355
+ self._logged_input = True
356
  if bounded_put(input_queue, (int(sample_rate), audio)):
357
  counters["input_drops"] = int(counters.get("input_drops", 0)) + 1
358
 
 
367
  audio = output_queue.get_nowait()
368
  except queue.Empty:
369
  return None
370
+ if not self._logged_output:
371
+ print("WebRTC audio bridge emitted its first output frame.")
372
+ self._logged_output = True
373
  return OUTPUT_SAMPLE_RATE, np.asarray(audio, dtype=np.int16).reshape(1, -1)
374
 
375
  def copy(self):
 
462
  drain_proxy_queue(input_queue)
463
  ready_seconds = round(time.perf_counter() - session_started, 3)
464
  ready_event.set()
465
+ print("PersonaPlex GPU session is ready and waiting for WebRTC audio.")
466
  live_started = time.perf_counter()
467
  status = (
468
  f"**Engine ready in {ready_seconds:.1f}s.** Start the WebRTC microphone and speak naturally. "
 
617
  with gr.Column(scale=2):
618
  gr.Markdown("### 🎙️ Live Stage")
619
  status = gr.Markdown("Sign in, choose the cue, then start the live engine.")
620
+ webrtc = ReliableWebRTC(
621
  label="2. Connect microphone after Engine ready",
622
  modality="audio",
623
  mode="send-receive",