Spaces:
Running
Running
Update main.py
Browse files
main.py
CHANGED
|
@@ -4,10 +4,24 @@ from pathlib import Path
|
|
| 4 |
import uvicorn
|
| 5 |
import json
|
| 6 |
import uuid
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
app = FastAPI()
|
| 9 |
rooms = {}
|
| 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
@app.get("/")
|
| 13 |
async def index():
|
|
@@ -19,6 +33,11 @@ async def ping():
|
|
| 19 |
return {"status": "alive"}
|
| 20 |
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
@app.websocket("/ws/{room}")
|
| 23 |
async def ws_endpoint(websocket: WebSocket, room: str):
|
| 24 |
await websocket.accept()
|
|
@@ -27,6 +46,7 @@ async def ws_endpoint(websocket: WebSocket, room: str):
|
|
| 27 |
rooms[room] = {"peers": {}, "broadcaster": None}
|
| 28 |
r = rooms[room]
|
| 29 |
r["peers"][pid] = websocket
|
|
|
|
| 30 |
await websocket.send_text(json.dumps({"type": "assigned", "id": pid}))
|
| 31 |
await broadcast_state(room)
|
| 32 |
try:
|
|
@@ -42,12 +62,14 @@ async def ws_endpoint(websocket: WebSocket, room: str):
|
|
| 42 |
if tag == 2 and r["broadcaster"] != pid:
|
| 43 |
continue
|
| 44 |
await relay_bytes(room, pid, raw)
|
| 45 |
-
except (WebSocketDisconnect, Exception):
|
|
|
|
| 46 |
r["peers"].pop(pid, None)
|
| 47 |
if r["broadcaster"] == pid:
|
| 48 |
r["broadcaster"] = None
|
| 49 |
if not r["peers"]:
|
| 50 |
rooms.pop(room, None)
|
|
|
|
| 51 |
else:
|
| 52 |
await notify_leave(room, pid)
|
| 53 |
await broadcast_state(room)
|
|
@@ -61,6 +83,7 @@ async def handle_text(room, sender, data):
|
|
| 61 |
if t == "set_broadcaster":
|
| 62 |
old = r["broadcaster"]
|
| 63 |
r["broadcaster"] = sender
|
|
|
|
| 64 |
if old and old != sender and old in r["peers"]:
|
| 65 |
try:
|
| 66 |
await r["peers"][old].send_text(json.dumps({"type": "force_viewer"}))
|
|
@@ -70,16 +93,22 @@ async def handle_text(room, sender, data):
|
|
| 70 |
elif t == "set_viewer":
|
| 71 |
if r["broadcaster"] == sender:
|
| 72 |
r["broadcaster"] = None
|
|
|
|
| 73 |
await broadcast_state(room)
|
| 74 |
-
elif t in ("offer", "answer", "ice"):
|
| 75 |
target = data.get("to")
|
| 76 |
-
if
|
|
|
|
|
|
|
| 77 |
fwd = dict(data)
|
| 78 |
fwd["from"] = sender
|
| 79 |
try:
|
| 80 |
await r["peers"][target].send_text(json.dumps(fwd))
|
| 81 |
-
|
| 82 |
-
|
|
|
|
|
|
|
|
|
|
| 83 |
|
| 84 |
|
| 85 |
async def relay_bytes(room, sender, data):
|
|
@@ -95,12 +124,14 @@ async def relay_bytes(room, sender, data):
|
|
| 95 |
dead.append(pid)
|
| 96 |
for d in dead:
|
| 97 |
r["peers"].pop(d, None)
|
|
|
|
| 98 |
|
| 99 |
|
| 100 |
async def broadcast_state(room):
|
| 101 |
r = rooms.get(room)
|
| 102 |
if not r:
|
| 103 |
return
|
|
|
|
| 104 |
for pid, ws in list(r["peers"].items()):
|
| 105 |
try:
|
| 106 |
await ws.send_text(json.dumps({
|
|
@@ -112,7 +143,9 @@ async def broadcast_state(room):
|
|
| 112 |
"is_broadcaster": pid == r["broadcaster"]
|
| 113 |
}))
|
| 114 |
except:
|
| 115 |
-
|
|
|
|
|
|
|
| 116 |
|
| 117 |
|
| 118 |
async def notify_leave(room, pid):
|
|
|
|
| 4 |
import uvicorn
|
| 5 |
import json
|
| 6 |
import uuid
|
| 7 |
+
import logging
|
| 8 |
+
|
| 9 |
+
logging.basicConfig(level=logging.INFO)
|
| 10 |
+
log = logging.getLogger("intercom")
|
| 11 |
|
| 12 |
app = FastAPI()
|
| 13 |
rooms = {}
|
| 14 |
|
| 15 |
+
ICE_SERVERS = [
|
| 16 |
+
{"urls": "stun:stun.l.google.com:19302"},
|
| 17 |
+
{"urls": "stun:stun1.l.google.com:19302"},
|
| 18 |
+
{"urls": "stun:stun2.l.google.com:19302"},
|
| 19 |
+
{"urls": "stun:stun3.l.google.com:19302"},
|
| 20 |
+
{"urls": "turn:openrelay.metered.ca:80", "username": "openrelayproject", "credential": "openrelayproject"},
|
| 21 |
+
{"urls": "turn:openrelay.metered.ca:443", "username": "openrelayproject", "credential": "openrelayproject"},
|
| 22 |
+
{"urls": "turn:openrelay.metered.ca:443?transport=tcp", "username": "openrelayproject", "credential": "openrelayproject"},
|
| 23 |
+
]
|
| 24 |
+
|
| 25 |
|
| 26 |
@app.get("/")
|
| 27 |
async def index():
|
|
|
|
| 33 |
return {"status": "alive"}
|
| 34 |
|
| 35 |
|
| 36 |
+
@app.get("/ice")
|
| 37 |
+
async def get_ice():
|
| 38 |
+
return {"iceServers": ICE_SERVERS}
|
| 39 |
+
|
| 40 |
+
|
| 41 |
@app.websocket("/ws/{room}")
|
| 42 |
async def ws_endpoint(websocket: WebSocket, room: str):
|
| 43 |
await websocket.accept()
|
|
|
|
| 46 |
rooms[room] = {"peers": {}, "broadcaster": None}
|
| 47 |
r = rooms[room]
|
| 48 |
r["peers"][pid] = websocket
|
| 49 |
+
log.info(f"[+] {pid} joined '{room}' ({len(r['peers'])} peers)")
|
| 50 |
await websocket.send_text(json.dumps({"type": "assigned", "id": pid}))
|
| 51 |
await broadcast_state(room)
|
| 52 |
try:
|
|
|
|
| 62 |
if tag == 2 and r["broadcaster"] != pid:
|
| 63 |
continue
|
| 64 |
await relay_bytes(room, pid, raw)
|
| 65 |
+
except (WebSocketDisconnect, Exception) as e:
|
| 66 |
+
log.info(f"[-] {pid} left '{room}': {type(e).__name__}")
|
| 67 |
r["peers"].pop(pid, None)
|
| 68 |
if r["broadcaster"] == pid:
|
| 69 |
r["broadcaster"] = None
|
| 70 |
if not r["peers"]:
|
| 71 |
rooms.pop(room, None)
|
| 72 |
+
log.info(f"[x] Room '{room}' removed (empty)")
|
| 73 |
else:
|
| 74 |
await notify_leave(room, pid)
|
| 75 |
await broadcast_state(room)
|
|
|
|
| 83 |
if t == "set_broadcaster":
|
| 84 |
old = r["broadcaster"]
|
| 85 |
r["broadcaster"] = sender
|
| 86 |
+
log.info(f"[B] {sender} broadcasting in '{room}'")
|
| 87 |
if old and old != sender and old in r["peers"]:
|
| 88 |
try:
|
| 89 |
await r["peers"][old].send_text(json.dumps({"type": "force_viewer"}))
|
|
|
|
| 93 |
elif t == "set_viewer":
|
| 94 |
if r["broadcaster"] == sender:
|
| 95 |
r["broadcaster"] = None
|
| 96 |
+
log.info(f"[V] {sender} stopped broadcasting in '{room}'")
|
| 97 |
await broadcast_state(room)
|
| 98 |
+
elif t in ("offer", "answer", "ice", "negotiate"):
|
| 99 |
target = data.get("to")
|
| 100 |
+
if not target:
|
| 101 |
+
return
|
| 102 |
+
if target in r["peers"]:
|
| 103 |
fwd = dict(data)
|
| 104 |
fwd["from"] = sender
|
| 105 |
try:
|
| 106 |
await r["peers"][target].send_text(json.dumps(fwd))
|
| 107 |
+
log.info(f"[S] {t} from {sender} -> {target}")
|
| 108 |
+
except Exception as e:
|
| 109 |
+
log.warning(f"[!] Signal fail {t} {sender}->{target}: {e}")
|
| 110 |
+
elif t == "p2p_failed":
|
| 111 |
+
log.info(f"[!] P2P failed for {sender} in '{room}', falling back to relay")
|
| 112 |
|
| 113 |
|
| 114 |
async def relay_bytes(room, sender, data):
|
|
|
|
| 124 |
dead.append(pid)
|
| 125 |
for d in dead:
|
| 126 |
r["peers"].pop(d, None)
|
| 127 |
+
log.info(f"[-] {d} dead connection removed from '{room}'")
|
| 128 |
|
| 129 |
|
| 130 |
async def broadcast_state(room):
|
| 131 |
r = rooms.get(room)
|
| 132 |
if not r:
|
| 133 |
return
|
| 134 |
+
dead = []
|
| 135 |
for pid, ws in list(r["peers"].items()):
|
| 136 |
try:
|
| 137 |
await ws.send_text(json.dumps({
|
|
|
|
| 143 |
"is_broadcaster": pid == r["broadcaster"]
|
| 144 |
}))
|
| 145 |
except:
|
| 146 |
+
dead.append(pid)
|
| 147 |
+
for d in dead:
|
| 148 |
+
r["peers"].pop(d, None)
|
| 149 |
|
| 150 |
|
| 151 |
async def notify_leave(room, pid):
|