Update app.py
Browse files
app.py
CHANGED
|
@@ -86,7 +86,6 @@ class GlobalState:
|
|
| 86 |
def push_audio(self, audio_bytes: bytes):
|
| 87 |
"""
|
| 88 |
Pushes raw audio bytes.
|
| 89 |
-
|
| 90 |
If the queue is full (meaning we are lagging), throw away the OLDEST audio.
|
| 91 |
"""
|
| 92 |
# Limit queue to ~0.5 seconds of audio (approx 5-6 chunks of 4096 bytes)
|
|
@@ -263,6 +262,29 @@ class MovementManager:
|
|
| 263 |
print(f"Playback Error: {e}")
|
| 264 |
self.is_playing = False
|
| 265 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 266 |
manager = MovementManager()
|
| 267 |
|
| 268 |
|
|
@@ -289,6 +311,13 @@ async def robot_endpoint(ws: WebSocket):
|
|
| 289 |
finally:
|
| 290 |
state.robot_ws = None
|
| 291 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 292 |
@app.websocket("/video_stream")
|
| 293 |
async def stream_endpoint(ws: WebSocket):
|
| 294 |
"""Endpoint for Robot/Sim to send video frames."""
|
|
@@ -364,7 +393,6 @@ def webrtc_audio_generator():
|
|
| 364 |
def webrtc_video_generator():
|
| 365 |
"""
|
| 366 |
Generator for FastRTC WebRTC (mode='receive', modality='video').
|
| 367 |
-
|
| 368 |
It reads JPEG bytes from state.latest_frame_bytes, decodes them with OpenCV,
|
| 369 |
and yields HxWx3 uint8 frames as expected by FastRTC.
|
| 370 |
"""
|
|
@@ -437,17 +465,25 @@ with gr.Blocks(title="Reachy Controller", theme=gr.themes.Soft()) as demo:
|
|
| 437 |
|
| 438 |
# --- RIGHT COLUMN: View ---
|
| 439 |
with gr.Column(scale=2):
|
| 440 |
-
robot_video = WebRTC(
|
| 441 |
-
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
)
|
| 445 |
-
robot_video.stream(
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 451 |
|
| 452 |
# --- Movement Builders ---
|
| 453 |
with gr.Tabs():
|
|
@@ -502,4 +538,4 @@ app = gr.mount_gradio_app(app, demo, path="/")
|
|
| 502 |
if __name__ == "__main__":
|
| 503 |
print("🚀 Server starting on http://0.0.0.0:7860")
|
| 504 |
print("ℹ️ Point your Robot/Sim to: ws://<YOUR_PC_IP>:7860/robot")
|
| 505 |
-
uvicorn.run(app, host="0.0.0.0", port=7860, proxy_headers=True, forwarded_allow_ips="*")
|
|
|
|
| 86 |
def push_audio(self, audio_bytes: bytes):
|
| 87 |
"""
|
| 88 |
Pushes raw audio bytes.
|
|
|
|
| 89 |
If the queue is full (meaning we are lagging), throw away the OLDEST audio.
|
| 90 |
"""
|
| 91 |
# Limit queue to ~0.5 seconds of audio (approx 5-6 chunks of 4096 bytes)
|
|
|
|
| 262 |
print(f"Playback Error: {e}")
|
| 263 |
self.is_playing = False
|
| 264 |
|
| 265 |
+
def generate_mjpeg_stream(self):
|
| 266 |
+
last_timestamp = 0.0
|
| 267 |
+
|
| 268 |
+
while True:
|
| 269 |
+
# 1. Check if frame has changed
|
| 270 |
+
with state.frame_lock:
|
| 271 |
+
current_bytes = state.latest_frame_bytes
|
| 272 |
+
current_timestamp = state.latest_frame_ts
|
| 273 |
+
|
| 274 |
+
# 2. Only yield if this is a new frame
|
| 275 |
+
if current_timestamp > last_timestamp:
|
| 276 |
+
last_timestamp = current_timestamp
|
| 277 |
+
if current_bytes is not None:
|
| 278 |
+
yield (b'--frame\r\n'
|
| 279 |
+
b'Content-Type: image/jpeg\r\n\r\n' + current_bytes + b'\r\n')
|
| 280 |
+
else:
|
| 281 |
+
# If no new frame, sleep a bit longer to save CPU
|
| 282 |
+
time.sleep(0.02)
|
| 283 |
+
continue
|
| 284 |
+
|
| 285 |
+
# Cap FPS slightly to prevent saturation
|
| 286 |
+
time.sleep(0.02)
|
| 287 |
+
|
| 288 |
manager = MovementManager()
|
| 289 |
|
| 290 |
|
|
|
|
| 311 |
finally:
|
| 312 |
state.robot_ws = None
|
| 313 |
|
| 314 |
+
@app.get("/video_feed")
|
| 315 |
+
def video_feed():
|
| 316 |
+
return StreamingResponse(
|
| 317 |
+
manager.generate_mjpeg_stream(),
|
| 318 |
+
media_type="multipart/x-mixed-replace; boundary=frame"
|
| 319 |
+
)
|
| 320 |
+
|
| 321 |
@app.websocket("/video_stream")
|
| 322 |
async def stream_endpoint(ws: WebSocket):
|
| 323 |
"""Endpoint for Robot/Sim to send video frames."""
|
|
|
|
| 393 |
def webrtc_video_generator():
|
| 394 |
"""
|
| 395 |
Generator for FastRTC WebRTC (mode='receive', modality='video').
|
|
|
|
| 396 |
It reads JPEG bytes from state.latest_frame_bytes, decodes them with OpenCV,
|
| 397 |
and yields HxWx3 uint8 frames as expected by FastRTC.
|
| 398 |
"""
|
|
|
|
| 465 |
|
| 466 |
# --- RIGHT COLUMN: View ---
|
| 467 |
with gr.Column(scale=2):
|
| 468 |
+
# robot_video = WebRTC(
|
| 469 |
+
# label="Robot Video",
|
| 470 |
+
# modality="video",
|
| 471 |
+
# mode="receive",
|
| 472 |
+
# )
|
| 473 |
+
# robot_video.stream(
|
| 474 |
+
# fn=lambda: webrtc_video_generator(),
|
| 475 |
+
# inputs=None,
|
| 476 |
+
# outputs=[robot_video],
|
| 477 |
+
# trigger=listen_btn.click,
|
| 478 |
+
# )
|
| 479 |
+
html_code = """
|
| 480 |
+
<html>
|
| 481 |
+
<body>
|
| 482 |
+
<img src="/video_feed" style="width: 100%; max-width: 1080px; border-radius: 8px;">
|
| 483 |
+
</body>
|
| 484 |
+
</html>
|
| 485 |
+
"""
|
| 486 |
+
sim_view = gr.HTML(value=html_code, label="🎬 Robot Simulation")
|
| 487 |
|
| 488 |
# --- Movement Builders ---
|
| 489 |
with gr.Tabs():
|
|
|
|
| 538 |
if __name__ == "__main__":
|
| 539 |
print("🚀 Server starting on http://0.0.0.0:7860")
|
| 540 |
print("ℹ️ Point your Robot/Sim to: ws://<YOUR_PC_IP>:7860/robot")
|
| 541 |
+
uvicorn.run(app, host="0.0.0.0", port=7860, proxy_headers=True, forwarded_allow_ips="*")
|