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