22from fastapi .staticfiles import StaticFiles
33from fastapi .responses import FileResponse
44from fastapi .middleware .cors import CORSMiddleware
5+ import socketio
56import os
67import logging
78import glob
89import asyncio
910from typing import List , Dict , Any
1011import threading
1112import queue
13+ import time
1214from pathlib import Path
1315from . import config
16+ from .webrtc_signaling import get_socketio_app
1417
1518# Import our custom recording functionality
1619from .recording import (
7679 allow_headers = ["*" ], # Allows all headers
7780)
7881
82+ # Get Socket.IO app for WebRTC signaling
83+ sio = get_socketio_app ()
84+
85+ # Create Socket.IO ASGI app
86+ socket_app = socketio .ASGIApp (sio , app )
87+
7988# Create static directory if it doesn't exist
8089os .makedirs ("app/static" , exist_ok = True )
8190
@@ -665,6 +674,143 @@ def get_robot_config(robot_type: str, available_configs: str = ""):
665674 return {"status" : "error" , "message" : str (e )}
666675
667676
677+ # ============================================================================
678+ # WEBRTC STREAM MANAGEMENT ENDPOINTS
679+ # ============================================================================
680+
681+ @app .get ("/webrtc/streams" )
682+ def get_active_streams ():
683+ """Get list of all active WebRTC streams"""
684+ try :
685+ from .webrtc_signaling import active_streams , stream_buffers
686+
687+ streams = []
688+ for stream_id , stream_data in active_streams .items ():
689+ stream_copy = stream_data .copy ()
690+ stream_copy ['buffer_size' ] = len (stream_buffers .get (stream_id , []))
691+ streams .append (stream_copy )
692+
693+ return {"success" : True , "streams" : streams }
694+ except Exception as e :
695+ logger .error (f"Error getting active streams: { str (e )} " )
696+ return {"success" : False , "error" : str (e )}
697+
698+ @app .get ("/webrtc/streams/{stream_id}" )
699+ def get_stream_info (stream_id : str ):
700+ """Get detailed information about a specific stream"""
701+ try :
702+ from .webrtc_signaling import active_streams , stream_buffers
703+
704+ if stream_id not in active_streams :
705+ return {"success" : False , "error" : "Stream not found" }
706+
707+ stream_info = active_streams [stream_id ].copy ()
708+ stream_info ['buffer_size' ] = len (stream_buffers .get (stream_id , []))
709+
710+ # Add buffer statistics
711+ if stream_id in stream_buffers and stream_buffers [stream_id ]:
712+ buffer = stream_buffers [stream_id ]
713+ stream_info ['buffer_stats' ] = {
714+ 'oldest_frame' : buffer [0 ]['timestamp' ] if buffer else None ,
715+ 'newest_frame' : buffer [- 1 ]['timestamp' ] if buffer else None ,
716+ 'total_frames' : len (buffer )
717+ }
718+
719+ return {"success" : True , "stream" : stream_info }
720+ except Exception as e :
721+ logger .error (f"Error getting stream info for { stream_id } : { str (e )} " )
722+ return {"success" : False , "error" : str (e )}
723+
724+ @app .get ("/webrtc/sessions" )
725+ def get_active_sessions ():
726+ """Get list of all active WebRTC sessions"""
727+ try :
728+ from .webrtc_signaling import active_sessions
729+
730+ sessions = []
731+ for webrtc_id , session_data in active_sessions .items ():
732+ session_copy = session_data .copy ()
733+ # Remove sensitive client IDs from public endpoint
734+ session_copy .pop ('desktop_client' , None )
735+ session_copy .pop ('phone_client' , None )
736+ sessions .append (session_copy )
737+
738+ return {"success" : True , "sessions" : sessions }
739+ except Exception as e :
740+ logger .error (f"Error getting active sessions: { str (e )} " )
741+ return {"success" : False , "error" : str (e )}
742+
743+ @app .post ("/webrtc/test-stream" )
744+ def create_test_stream ():
745+ """Create a test stream for development/testing purposes"""
746+ try :
747+ import uuid
748+ from datetime import datetime
749+ from .webrtc_signaling import active_streams , stream_buffers
750+
751+ # Generate test stream
752+ stream_id = str (uuid .uuid4 ())
753+ test_webrtc_id = f"test_{ int (time .time ())} "
754+
755+ # Create test stream entry
756+ active_streams [stream_id ] = {
757+ 'stream_id' : stream_id ,
758+ 'webrtc_id' : test_webrtc_id ,
759+ 'created_at' : datetime .now ().isoformat (),
760+ 'status' : 'test_active' ,
761+ 'metadata' : {
762+ 'width' : 640 ,
763+ 'height' : 480 ,
764+ 'fps' : 30 ,
765+ 'codec' : 'h264' ,
766+ 'test' : True
767+ }
768+ }
769+
770+ # Initialize with test frame data
771+ stream_buffers [stream_id ] = [
772+ {
773+ 'timestamp' : time .time (),
774+ 'data' : 'test_frame_data_placeholder' ,
775+ 'sequence' : 0
776+ }
777+ ]
778+
779+ logger .info (f"Created test stream: { stream_id } " )
780+
781+ return {
782+ "success" : True ,
783+ "stream_id" : stream_id ,
784+ "webrtc_id" : test_webrtc_id ,
785+ "message" : "Test stream created successfully"
786+ }
787+
788+ except Exception as e :
789+ logger .error (f"Error creating test stream: { str (e )} " )
790+ return {"success" : False , "error" : str (e )}
791+
792+ @app .delete ("/webrtc/streams/{stream_id}" )
793+ def delete_stream (stream_id : str ):
794+ """Delete a specific stream and its buffer"""
795+ try :
796+ from .webrtc_signaling import active_streams , stream_buffers
797+
798+ if stream_id not in active_streams :
799+ return {"success" : False , "error" : "Stream not found" }
800+
801+ # Remove stream and buffer
802+ del active_streams [stream_id ]
803+ if stream_id in stream_buffers :
804+ del stream_buffers [stream_id ]
805+
806+ logger .info (f"Deleted stream: { stream_id } " )
807+ return {"success" : True , "message" : "Stream deleted successfully" }
808+
809+ except Exception as e :
810+ logger .error (f"Error deleting stream { stream_id } : { str (e )} " )
811+ return {"success" : False , "error" : str (e )}
812+
813+
668814@app .on_event ("shutdown" )
669815async def shutdown_event ():
670816 """Clean up resources when FastAPI shuts down"""
@@ -680,3 +826,7 @@ async def shutdown_event():
680826 if manager :
681827 manager .stop_broadcast_thread ()
682828 logger .info ("✅ Cleanup completed" )
829+
830+ # Create a combined app that serves both FastAPI and Socket.IO
831+ # The socket_app already wraps the FastAPI app, so we use it as the main app
832+ main_app = socket_app
0 commit comments