Hanzo03 commited on
Commit
5a3a451
Β·
1 Parent(s): 308313f
Files changed (4) hide show
  1. .gitignore +0 -0
  2. Dockerfile +1 -0
  3. __pycache__/main.cpython-312.pyc +0 -0
  4. main.py +91 -46
.gitignore ADDED
File without changes
Dockerfile CHANGED
@@ -12,4 +12,5 @@ COPY --chown=user ./requirements.txt requirements.txt
12
  RUN pip install --no-cache-dir --upgrade -r requirements.txt
13
 
14
  COPY --chown=user . /app
 
15
  CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
 
12
  RUN pip install --no-cache-dir --upgrade -r requirements.txt
13
 
14
  COPY --chown=user . /app
15
+
16
  CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
__pycache__/main.cpython-312.pyc DELETED
Binary file (4.5 kB)
 
main.py CHANGED
@@ -1,7 +1,7 @@
1
  # main.py
2
- from fastapi import FastAPI, Request
3
  from fastapi.middleware.cors import CORSMiddleware
4
- from aiortc import RTCPeerConnection, RTCSessionDescription, RTCConfiguration, RTCIceServer
5
  import json
6
  import re
7
 
@@ -15,9 +15,11 @@ app.add_middleware(
15
  allow_headers=["*"],
16
  )
17
 
18
- peer_connections = {}
 
 
19
 
20
- # βœ… Add STUN + TURN servers here
21
  ice_servers = [
22
  RTCIceServer(urls="stun:stun.l.google.com:19302"),
23
  RTCIceServer(
@@ -29,16 +31,17 @@ ice_servers = [
29
 
30
  rtc_config = RTCConfiguration(iceServers=ice_servers)
31
 
 
 
 
 
 
32
 
33
- @app.post("/signal")
34
- async def signal(request: Request):
35
- body = await request.json()
36
- offer = body["offer"]
37
-
38
- # βœ… Peer connection with TURN support
39
  pc = RTCPeerConnection(rtc_config)
40
- peer_connections[id(pc)] = pc # prevent GC
41
 
 
42
  @pc.on("datachannel")
43
  def on_datachannel(channel):
44
  print("βœ… Data channel opened")
@@ -46,42 +49,84 @@ async def signal(request: Request):
46
  @channel.on("message")
47
  def on_message(message):
48
  print("πŸ“© Received:", message)
49
-
50
- rpc = json.loads(message)
51
- result = handle_rpc(rpc)
52
-
53
- response = json.dumps({
54
- "jsonrpc": "2.0",
55
- "id": rpc["id"],
56
- "result": result,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  })
58
 
59
- print("πŸ“€ Sending:", response)
60
- channel.send(response)
61
-
62
- # Apply remote SDP
63
- await pc.setRemoteDescription(
64
- RTCSessionDescription(
65
- sdp=offer["sdp"],
66
- type=offer["type"]
67
- )
68
- )
69
-
70
- # Create & return answer
71
- answer = await pc.createAnswer()
72
- await pc.setLocalDescription(answer)
73
-
74
- print("βœ… Answer generated and sent")
75
-
76
- return {
77
- "answer": {
78
- "sdp": pc.localDescription.sdp,
79
- "type": pc.localDescription.type
80
- }
81
- }
82
-
83
-
84
- # βœ… Your RPC logic stays the same
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  def handle_rpc(rpc):
86
  method = rpc.get("method")
87
  params = rpc.get("params", {})
@@ -122,4 +167,4 @@ def handle_rpc(rpc):
122
  return "Unknown method"
123
 
124
 
125
- print("βœ… FastAPI WebRTC backend with TURN is ready")
 
1
  # main.py
2
+ from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect
3
  from fastapi.middleware.cors import CORSMiddleware
4
+ from aiortc import RTCPeerConnection, RTCSessionDescription, RTCConfiguration, RTCIceServer, RTCIceCandidate
5
  import json
6
  import re
7
 
 
15
  allow_headers=["*"],
16
  )
17
 
18
+ # NOTE: The peer_connections dictionary is less critical with WebSockets
19
+ # because the WebSocket lifecycle can manage the PeerConnection.
20
+ peer_connections = {}
21
 
22
+ # βœ… STUN + TURN servers remain for the peer-to-peer connection
23
  ice_servers = [
24
  RTCIceServer(urls="stun:stun.l.google.com:19302"),
25
  RTCIceServer(
 
31
 
32
  rtc_config = RTCConfiguration(iceServers=ice_servers)
33
 
34
+ # πŸš€ NEW: WebSocket endpoint for Signaling
35
+ @app.websocket("/ws")
36
+ async def websocket_endpoint(websocket: WebSocket):
37
+ await websocket.accept()
38
+ print("πŸ”Œ WebSocket accepted for signaling")
39
 
40
+ # 1. Create a new Peer Connection for this session
 
 
 
 
 
41
  pc = RTCPeerConnection(rtc_config)
42
+ peer_connections[id(pc)] = pc # Keep reference to prevent GC
43
 
44
+ # 2. Add handlers for DataChannel messages (RPC logic remains)
45
  @pc.on("datachannel")
46
  def on_datachannel(channel):
47
  print("βœ… Data channel opened")
 
49
  @channel.on("message")
50
  def on_message(message):
51
  print("πŸ“© Received:", message)
52
+ try:
53
+ rpc = json.loads(message)
54
+ result = handle_rpc(rpc)
55
+
56
+ response = json.dumps({
57
+ "jsonrpc": "2.0",
58
+ "id": rpc["id"],
59
+ "result": result,
60
+ })
61
+ print("πŸ“€ Sending:", response)
62
+ channel.send(response)
63
+ except json.JSONDecodeError:
64
+ print(f"Error decoding RPC message: {message}")
65
+
66
+
67
+ # 3. Handle ICE Candidates and send them over the WebSocket
68
+ @pc.on("icecandidate")
69
+ async def on_icecandidate(candidate):
70
+ if candidate:
71
+ print(f"πŸ“€ Sending ICE candidate: {candidate.sdpMid}")
72
+ await websocket.send_json({
73
+ "type": "candidate",
74
+ # aiortc candidates need to be converted to a dictionary for safe JSON transfer
75
+ "candidate": {
76
+ "candidate": candidate.candidate,
77
+ "sdpMid": candidate.sdpMid,
78
+ "sdpMLineIndex": candidate.sdpMLineIndex,
79
+ "usernameFragment": candidate.usernameFragment,
80
+ }
81
  })
82
 
83
+ try:
84
+ # 4. Main loop to receive signaling messages (Offer, Answer, Candidates)
85
+ while True:
86
+ data = await websocket.receive_json()
87
+ message_type = data.get("type")
88
+
89
+ if message_type == "offer":
90
+ # Handle initial Offer
91
+ offer = RTCSessionDescription(sdp=data["sdp"], type="offer")
92
+ await pc.setRemoteDescription(offer)
93
+
94
+ # Create and send Answer
95
+ answer = await pc.createAnswer()
96
+ await pc.setLocalDescription(answer)
97
+
98
+ print("βœ… Sending Answer")
99
+ await websocket.send_json({
100
+ "type": "answer",
101
+ "sdp": pc.localDescription.sdp
102
+ })
103
+
104
+ elif message_type == "candidate":
105
+ # Handle incoming ICE candidates
106
+ try:
107
+ candidate_init = data["candidate"]
108
+ candidate = RTCIceCandidate(
109
+ candidate=candidate_init["candidate"],
110
+ sdpMid=candidate_init["sdpMid"],
111
+ sdpMLineIndex=candidate_init["sdpMLineIndex"],
112
+ usernameFragment=candidate_init.get("usernameFragment"),
113
+ )
114
+ await pc.addIceCandidate(candidate)
115
+ print(f"πŸ“₯ Added remote ICE candidate: {candidate.sdpMid}")
116
+ except Exception as e:
117
+ print(f"Error adding ICE candidate: {e}")
118
+
119
+
120
+ except WebSocketDisconnect:
121
+ print("🚫 WebSocket disconnected. Closing PeerConnection.")
122
+ finally:
123
+ # Clean up
124
+ await pc.close()
125
+ if id(pc) in peer_connections:
126
+ del peer_connections[id(pc)]
127
+
128
+
129
+ # βœ… Your RPC logic remains the same
130
  def handle_rpc(rpc):
131
  method = rpc.get("method")
132
  params = rpc.get("params", {})
 
167
  return "Unknown method"
168
 
169
 
170
+ print("βœ… FastAPI WebRTC backend with WebSocket signaling is ready")