Skip to content

Commit 16ea1b2

Browse files
author
weston
committed
init
0 parents  commit 16ea1b2

35 files changed

+8015
-0
lines changed

.gitignore

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Python
2+
*.pyc
3+
__pycache__/
4+
*.pyo
5+
*.pyd
6+
*.pyw
7+
*.pyz
8+
*.pyzw
9+
*.pyc
10+
*.pyo
11+
*.pyd
12+
*.pyw
13+
*.pyz
14+
*.pyzw
15+
*.egg-info/
16+
*.egg
17+
*.whl
18+
19+
# Web
20+
node_modules/
21+
yarn.lock
22+
yarn-error.log

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 weston wei
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

PythonStreamRender.unitypackage

11.9 KB
Binary file not shown.

app.py

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
4+
# app.py
5+
import os
6+
import time
7+
from bottle import abort, route, run, request,Bottle, response, static_file, template
8+
# import pymysql.cursors
9+
import json
10+
from datetime import datetime, timedelta
11+
import subprocess
12+
app = Bottle()
13+
14+
15+
#
16+
handlers = {}
17+
18+
19+
PORT = 9000
20+
21+
isPrivate = False
22+
23+
@app.route("/video")
24+
def get_video():
25+
# Return limit exceeded if 4 applications are already open
26+
appid = -1
27+
for key in handlers.keys():
28+
if(handlers[key].getClientCount()==0):
29+
appid = key
30+
break
31+
32+
if(appid == -1):
33+
for i in range(1,5):
34+
if(i not in handlers.keys()):
35+
appid = i
36+
break
37+
if(appid == -1):
38+
return "Connection limit exceeded, only a maximum of 4 connections can be made simultaneously"
39+
40+
# Launch the Unity executable file
41+
unity_exe_path = 'E:/Projects/SteamingTest/Build/Window/SteamingTest.exe' # Replace with the path to your Unity executable file
42+
unity_args = ["-appid",str(appid)]
43+
subprocess.Popen([unity_exe_path] + unity_args)
44+
# appid = 1
45+
46+
context = {
47+
'appid': appid
48+
}
49+
html = template('receiver/index.html', **context)
50+
return html
51+
52+
@app.route('/static/<filepath:path>')
53+
def serve_static(filepath):
54+
current_dir = os.path.dirname(os.path.abspath(__file__))
55+
static_folder = os.path.join(current_dir, 'static')
56+
return static_file(filepath, root=static_folder)
57+
58+
@app.route('/config')
59+
def server_config():
60+
data ={"useWebSocket":True,"startupMode":"public","logging":"dev"}
61+
json_data = json.dumps(data)
62+
return json_data
63+
64+
65+
66+
@app.route('/<appid:int>')
67+
def handle_websocket(appid):
68+
wsock = request.environ.get('wsgi.websocket')
69+
if not wsock:
70+
html = template('index/index.html')
71+
return html
72+
# Handle the connection establishment event
73+
handler = getOrCreateHandler(appid)
74+
handler.addClient(wsock)
75+
print("WebSocket connected")
76+
while True:
77+
try:
78+
message = wsock.receive()
79+
print(message)
80+
if(message==None):
81+
break
82+
else:
83+
msg = json.loads(message)
84+
msg_type = msg["type"]
85+
86+
if msg_type == "connect":
87+
handler.onConnect(wsock, msg["connectionId"])
88+
elif msg_type == "disconnect":
89+
handler.onDisconnect(wsock, msg["connectionId"])
90+
elif msg_type == "offer":
91+
handler.onOffer(wsock, msg["data"])
92+
elif msg_type == "answer":
93+
handler.onAnswer(wsock, msg["data"])
94+
elif msg_type == "candidate":
95+
handler.onCandidate(wsock, msg["data"])
96+
else:
97+
pass
98+
except WebSocketError:
99+
break
100+
101+
# Handle the connection closure event
102+
handler.removeClient(wsock)
103+
print("WebSocket disconnected")
104+
105+
def getOrCreateHandler(key):
106+
if key in handlers:
107+
return handlers[key]
108+
else:
109+
handlers[key] = Handler()
110+
return handlers[key]
111+
112+
class Handler:
113+
def __init__(self):
114+
# [{sessonId:[connectionId,...]}]
115+
self.clients = {}
116+
# [{connectionId:[sessionId1, sessionId2]}]
117+
self.connectionPair = {}
118+
119+
def onConnect(self, ws, connectionId):
120+
polite = True
121+
if isPrivate:
122+
if connectionId in self.connectionPair:
123+
pair = self.connectionPair[connectionId]
124+
if pair[0] is not None and pair[1] is not None:
125+
ws.send(json.dumps({ "type": "error", "message": f"{connectionId}: This connection id is already used." }))
126+
return
127+
elif pair[0] is not None:
128+
self.connectionPair[connectionId] = [pair[0], ws]
129+
else:
130+
self.connectionPair[connectionId] = [ws, None]
131+
polite = False
132+
connectionIds = self.getOrCreateConnectionIds(ws)
133+
connectionIds.add(connectionId)
134+
data = { "type": "connect", "connectionId": connectionId, "polite": polite}
135+
ws.send(json.dumps(data))
136+
137+
def onDisconnect(self, ws, connectionId):
138+
connectionIds = self.clients[ws]
139+
connectionIds.remove(connectionId)
140+
data = { "type": "disconnect", "connectionId": connectionId }
141+
if connectionId in self.connectionPair:
142+
pair = self.connectionPair[connectionId]
143+
otherSessionWs = pair[0] if pair[0] != ws else pair[1]
144+
if otherSessionWs:
145+
otherSessionWs.send(json.dumps(data))
146+
del self.connectionPair[connectionId]
147+
ws.send(json.dumps(data))
148+
149+
def onOffer(self, ws,message):
150+
connectionId = message["connectionId"]
151+
time_now = int(time.time() * 1000)
152+
newOffer = {"sdp":message["sdp"],"datetime":time_now,"polite":False}
153+
sendData = { "from": connectionId, "to": "", "type": "offer", "data": newOffer }
154+
155+
if isPrivate:
156+
if connectionId in self.connectionPair:
157+
pair = self.connectionPair[connectionId]
158+
otherSessionWs = pair[0] if pair[0] != ws else pair[1]
159+
if otherSessionWs:
160+
newOffer["polite"] = True
161+
otherSessionWs.send(json.dumps(sendData))
162+
else:
163+
self.connectionPair[connectionId] = [ws, None]
164+
for k in self.clients.keys():
165+
if k != ws:
166+
k.send(json.dumps(sendData))
167+
168+
def onAnswer(self, ws,message):
169+
connectionId = message["connectionId"]
170+
time_now = int(time.time() * 1000)
171+
newAnswer = {"sdp":message["sdp"],"datetime":time_now}
172+
sendData = { "from": connectionId, "to": "", "type": "answer", "data": newAnswer }
173+
if connectionId in self.connectionPair:
174+
pair = self.connectionPair[connectionId]
175+
otherSessionWs = pair[0] if pair[0] != ws else pair[1]
176+
if not isPrivate:
177+
self.connectionPair[connectionId] = [otherSessionWs, ws]
178+
otherSessionWs.send(json.dumps(sendData))
179+
180+
def onCandidate(self, ws,message):
181+
connectionId = message["connectionId"]
182+
time_now = int(time.time() * 1000)
183+
candidate = {"candidate":message["candidate"], "sdpMLineIndex":message["sdpMLineIndex"], "sdpMid":message["sdpMid"], "datetime":time_now}
184+
sendData = { "from": connectionId, "to": "", "type": "candidate", "data": candidate }
185+
if isPrivate:
186+
if connectionId in self.connectionPair:
187+
pair = self.connectionPair[connectionId]
188+
otherSessionWs = pair[0] if pair[0] != ws else pair[1]
189+
if otherSessionWs:
190+
otherSessionWs.send(json.dumps(sendData))
191+
else:
192+
for k in self.clients.keys():
193+
if k != ws:
194+
k.send(json.dumps(sendData))
195+
196+
def getOrCreateConnectionIds(self, ws):
197+
if ws in self.clients:
198+
return self.clients[ws]
199+
else:
200+
self.clients[ws] = set()
201+
return self.clients[ws]
202+
def addClient(self,ws):
203+
self.clients[ws] = set()
204+
205+
def removeClient(self,ws):
206+
self.clients.pop(ws)
207+
#Send a message to the Unity executable to invoke Application.Quit()
208+
sendData = {"type": "killApp"}
209+
for session in self.clients.keys():
210+
session.send(json.dumps(sendData))
211+
212+
def getClientCount(self):
213+
return len(self.clients)
214+
215+
from gevent.pywsgi import WSGIServer
216+
from geventwebsocket import WebSocketError
217+
from geventwebsocket.handler import WebSocketHandler
218+
server = WSGIServer(('127.0.0.1', PORT), app,
219+
handler_class=WebSocketHandler)
220+
server.serve_forever()

0 commit comments

Comments
 (0)