teleportxr 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/launch.json +18 -0
- package/.vscode/settings.json +12 -0
- package/LICENSE +21 -0
- package/LICENSES/Apache-2.0.txt +73 -0
- package/LICENSES/BSD-3-Clause.txt +11 -0
- package/LICENSES/BSL-1.0.txt +7 -0
- package/LICENSES/CC-BY-4.0.txt +156 -0
- package/LICENSES/CC0-1.0.txt +121 -0
- package/LICENSES/MIT.txt +9 -0
- package/README.md +6 -0
- package/assets/scene.json +8 -0
- package/client/client.js +305 -0
- package/client/client_manager.js +114 -0
- package/client/geometry_service.js +422 -0
- package/connections/connection.js +0 -0
- package/connections/connectionmanager.js +0 -0
- package/connections/webrtcconnection.js +384 -0
- package/connections/webrtcconnectionmanager.js +86 -0
- package/core/core.js +447 -0
- package/index.js +13 -0
- package/package.json +36 -0
- package/protocol/command.js +127 -0
- package/protocol/encoders/node_encoder.js +28 -0
- package/protocol/encoders/resource_encoder.js +28 -0
- package/protocol/message.js +145 -0
- package/scene/material.js +105 -0
- package/scene/node.js +255 -0
- package/scene/resources.js +62 -0
- package/scene/scene.js +83 -0
- package/server.js +35 -0
- package/signaling.js +255 -0
- package/temp.js +1 -0
- package/test.json +2 -0
package/client/client.js
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const core= require("../core/core.js");
|
|
4
|
+
const command= require("../protocol/command.js");
|
|
5
|
+
const message= require("../protocol/message.js");
|
|
6
|
+
const gs= require("./geometry_service.js");
|
|
7
|
+
const node_encoder= require("../protocol/encoders/node_encoder.js");
|
|
8
|
+
const resource_encoder= require("../protocol/encoders/resource_encoder.js");
|
|
9
|
+
const WebRtcConnectionManager = require('../connections/webrtcconnectionmanager');
|
|
10
|
+
const resources= require("../scene/resources.js");
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class OriginState
|
|
14
|
+
{
|
|
15
|
+
constructor() {
|
|
16
|
+
this.sent=false;
|
|
17
|
+
this.originClientHas=BigInt(0);
|
|
18
|
+
this.ackId=0;
|
|
19
|
+
this.acknowledged=false;
|
|
20
|
+
this.serverTimeSentUs=BigInt(0);
|
|
21
|
+
this.valid_counter=BigInt(0);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
class Client {
|
|
26
|
+
constructor(cid,sigSend) {
|
|
27
|
+
this.signalingSend=sigSend;
|
|
28
|
+
this.clientID=cid;
|
|
29
|
+
this.origin_uid=0;
|
|
30
|
+
this.handshakeMessage=new message.HandshakeMessage();
|
|
31
|
+
this.geometryService=new gs.GeometryService(cid);
|
|
32
|
+
this.webRtcConnected=false;
|
|
33
|
+
this.webRtcConnection=null;
|
|
34
|
+
this.currentOriginState=new OriginState();
|
|
35
|
+
this.next_ack_id=BigInt(1);
|
|
36
|
+
}
|
|
37
|
+
tick(timestamp){
|
|
38
|
+
this.geometryService.GetNodesToSend();
|
|
39
|
+
}
|
|
40
|
+
streamingConnectionStateChanged(wrtcConn,newState)
|
|
41
|
+
{
|
|
42
|
+
//this.webRtcConnection=wrtcConn;
|
|
43
|
+
// This should have come from our own existing webRtcConnection and nowhere else.
|
|
44
|
+
console.log("Connection state is "+newState.toString());
|
|
45
|
+
if(newState=="connected")
|
|
46
|
+
{
|
|
47
|
+
//this.webRtcConnection.sendGeometry("test");
|
|
48
|
+
this.webRtcConnected=true;
|
|
49
|
+
}
|
|
50
|
+
else
|
|
51
|
+
{
|
|
52
|
+
this.webRtcConnected=false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
receivedMessageReliable(id,data)
|
|
56
|
+
{
|
|
57
|
+
console.log('Client receivedMessage unreliable ch.'+id+' received: '+data+'.');
|
|
58
|
+
}
|
|
59
|
+
// Is the Buffer bf too small to contain a type tp?
|
|
60
|
+
checkTooSmall(tp,bf) {
|
|
61
|
+
if(bf.byteLength<tp.sizeof()) {
|
|
62
|
+
// NOTE: we use log() here rather than warn() or error() because this is a problem with
|
|
63
|
+
// the message we were SENT, not with the local server code.
|
|
64
|
+
console.log("Binary message from "+this.clientID+" is too small to be a "+tp.toString()+": "+bf.byteLength+"<"+tp.sizeof());
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
receiveReceivedResourcesMessage(bf)
|
|
70
|
+
{
|
|
71
|
+
if(!this.checkTooSmall(message.ReceivedResourcesMessage,bf)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
var msg =new message.ReceivedResourcesMessage();
|
|
75
|
+
var uia =new Uint8Array(bf);
|
|
76
|
+
var dataView =new DataView(bf,0,bf.length);
|
|
77
|
+
core.decodeFromDataView(msg,dataView,0);
|
|
78
|
+
const excess =uia.length-message.ReceivedResourcesMessage.sizeof();
|
|
79
|
+
const numReceived =excess/core.UID_SIZE;
|
|
80
|
+
if(numReceived!=msg.uint64_receivedResourcesCount) {
|
|
81
|
+
console.log("ReceivedResourcesMessage claims to have "<<msg.resourceCount<<" resources but has only enough data for "+numReceived);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
var offset =message.ReceivedResourcesMessage.sizeof();
|
|
85
|
+
for(let i=0;i<msg.uint64_receivedResourcesCount;i++ ) {
|
|
86
|
+
var uid=dataView.getBigUint64(offset,core.endian);
|
|
87
|
+
this.geometryService.ConfirmResource(uid);
|
|
88
|
+
offset+=core.UID_SIZE;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
receivedMessageUnreliable(id,pkt)
|
|
92
|
+
{
|
|
93
|
+
|
|
94
|
+
var dataView=new DataView(pkt.data,0,1);
|
|
95
|
+
const messageType=dataView.getUint8(0);
|
|
96
|
+
//console.log('Client receivedMessage reliable ch.'+id+' received: '+messageType+'.');
|
|
97
|
+
switch(messageType){
|
|
98
|
+
case message.MessagePayloadType.ReceivedResources:
|
|
99
|
+
this.receiveReceivedResourcesMessage(pkt.data);
|
|
100
|
+
return;
|
|
101
|
+
case message.MessagePayloadType.Acknowledgement:
|
|
102
|
+
this.ReceiveAcknowledgement(pkt.data);
|
|
103
|
+
return;
|
|
104
|
+
default:
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// We call Start() when the signaling server accepts the client.
|
|
109
|
+
// In Start() we send the SetupCommand.
|
|
110
|
+
Start()
|
|
111
|
+
{
|
|
112
|
+
this.setupCommand=new command.SetupCommand();
|
|
113
|
+
this.SendCommand(this.setupCommand);
|
|
114
|
+
}
|
|
115
|
+
SendCommand(command){
|
|
116
|
+
let array=core.encodeToUint8Array(command);
|
|
117
|
+
this.signalingSend(array);
|
|
118
|
+
}
|
|
119
|
+
// We call StartStreaming once the SetupCommand has been acknowledged.
|
|
120
|
+
StartStreaming()
|
|
121
|
+
{
|
|
122
|
+
this.webRtcConnectionManager=WebRtcConnectionManager.getInstance();
|
|
123
|
+
// We make sure WebRTC has a connection for this client.
|
|
124
|
+
this.webRtcConnection = this. webRtcConnectionManager.createConnection(
|
|
125
|
+
this.clientID
|
|
126
|
+
,this.streamingConnectionStateChanged.bind(this)
|
|
127
|
+
,this.receivedMessageReliable.bind(this)
|
|
128
|
+
,this.receivedMessageUnreliable.bind(this));
|
|
129
|
+
//.then(
|
|
130
|
+
// function(value) {myDisplayer(value);},
|
|
131
|
+
// function(error) {myDisplayer(error);}
|
|
132
|
+
}
|
|
133
|
+
// Generic message acknowledgement. Certain kinds of message are expected to be ack'ed.
|
|
134
|
+
ReceiveAcknowledgement(data)
|
|
135
|
+
{
|
|
136
|
+
if (data.byteLength!=message.AcknowledgementMessage.sizeof())
|
|
137
|
+
{
|
|
138
|
+
console.log("Client: Received malformed AcknowledgementMessage packet of length: ",data.length);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
var msg =new message.AcknowledgementMessage();
|
|
142
|
+
var bf =data;
|
|
143
|
+
var uia =new Uint8Array(bf);
|
|
144
|
+
var dataView =new DataView(data,0,data.length);
|
|
145
|
+
core.decodeFromDataView(msg,dataView,0);
|
|
146
|
+
if(msg.uint64_ackId==this.currentOriginState.ackId)
|
|
147
|
+
{
|
|
148
|
+
this.currentOriginState.acknowledged=true;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
UpdateStreaming()
|
|
152
|
+
{
|
|
153
|
+
if(!this.scene)
|
|
154
|
+
return;
|
|
155
|
+
if(!this.webRtcConnected)
|
|
156
|
+
return;
|
|
157
|
+
var timestamp=core.getTimestampUs();
|
|
158
|
+
// Establish which nodes the client should have, and their resources.
|
|
159
|
+
// Then: which resources we think it does not yet have. Send those.
|
|
160
|
+
var node_uids=this.scene.GetAllNodeUids();
|
|
161
|
+
for (let uid of node_uids)
|
|
162
|
+
{
|
|
163
|
+
this.geometryService.StreamNode(uid);
|
|
164
|
+
}
|
|
165
|
+
var nodes_to_stream_now_uids=this.geometryService.GetNodesToSend();
|
|
166
|
+
for (const uid of nodes_to_stream_now_uids)
|
|
167
|
+
{
|
|
168
|
+
this.SendNode(uid);
|
|
169
|
+
}
|
|
170
|
+
var mesh_uids=this.geometryService.GetMeshesToStream();
|
|
171
|
+
for (const uid of mesh_uids)
|
|
172
|
+
{
|
|
173
|
+
this.SendMesh(uid);
|
|
174
|
+
}
|
|
175
|
+
var textures_to_send_now_uids=this.geometryService.GetTexturesToSend();
|
|
176
|
+
for (const uid of textures_to_send_now_uids)
|
|
177
|
+
{
|
|
178
|
+
this.SendTexture(uid);
|
|
179
|
+
}
|
|
180
|
+
if(!this.currentOriginState.acknowledged)
|
|
181
|
+
this.SendOrigin();
|
|
182
|
+
}
|
|
183
|
+
SendOrigin()
|
|
184
|
+
{
|
|
185
|
+
let time_now_us=core.getTimestampUs();
|
|
186
|
+
let originAckWaitTimeUs=BigInt(3000000);// three seconds
|
|
187
|
+
if(this.setupCommand.startTimestamp_utc_unix_us==0)
|
|
188
|
+
return ;
|
|
189
|
+
// If we sent it, and haven't timed out waiting for ack...
|
|
190
|
+
if(this.currentOriginState.serverTimeSentUs!=BigInt(0)
|
|
191
|
+
&&(time_now_us-this.currentOriginState.serverTimeSentUs)<originAckWaitTimeUs)
|
|
192
|
+
{
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (!this.webRtcConnection)
|
|
196
|
+
{
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if(this.currentOriginState.originClientHas==BigInt(0))
|
|
200
|
+
return;
|
|
201
|
+
this.currentOriginState.valid_counter++;
|
|
202
|
+
this.geometryService.SetOriginNode(this.currentOriginState.originClientHas);
|
|
203
|
+
var setp=new command.SetOriginNodeCommand();
|
|
204
|
+
setp.uint64_ackId=this.next_ack_id++;
|
|
205
|
+
setp.uint64_originNodeUid=this.currentOriginState.originClientHas;
|
|
206
|
+
setp.uint64_validCounter = this.currentOriginState.valid_counter;
|
|
207
|
+
|
|
208
|
+
// This is now the valid origin.
|
|
209
|
+
this.currentOriginState.sent=true;
|
|
210
|
+
this.currentOriginState.ackId=setp.uint64_ackId;
|
|
211
|
+
this.currentOriginState.acknowledged=false;
|
|
212
|
+
this.currentOriginState.serverTimeSentUs=core.getTimestampUs();
|
|
213
|
+
this.SendCommand(setp);
|
|
214
|
+
}
|
|
215
|
+
SendNode(uid)
|
|
216
|
+
{
|
|
217
|
+
var node=this.scene.GetNode(uid);
|
|
218
|
+
const MAX_NODE_SIZE=500;
|
|
219
|
+
const buffer = new ArrayBuffer(MAX_NODE_SIZE);
|
|
220
|
+
const nodeSize=node_encoder.encodeNode(node,buffer);
|
|
221
|
+
this.geometryService.EncodedResource(uid);
|
|
222
|
+
const view2 = new DataView(buffer, 0, nodeSize);
|
|
223
|
+
console.log("Sending node "+uid+" "+node.name+" to Client "+this.clientID+", size: "+nodeSize+" bytes");
|
|
224
|
+
this.webRtcConnection.sendGeometry(view2);
|
|
225
|
+
}
|
|
226
|
+
SendMesh(uid)
|
|
227
|
+
{
|
|
228
|
+
var mesh=resources.GetResource(uid);
|
|
229
|
+
}
|
|
230
|
+
SendTexture(uid)
|
|
231
|
+
{
|
|
232
|
+
var texture=resources.GetResourceFromUid(uid);
|
|
233
|
+
if(!texture)
|
|
234
|
+
{
|
|
235
|
+
console.warn("No texture of uid ",uid," was found.")
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const MAX_TEXTURE_SIZE=500;
|
|
239
|
+
const buffer = new ArrayBuffer(MAX_TEXTURE_SIZE);
|
|
240
|
+
const textureSize=resource_encoder.EncodeResource(texture,buffer);
|
|
241
|
+
this.geometryService.EncodedResource(uid);
|
|
242
|
+
const view2 = new DataView(buffer, 0, textureSize);
|
|
243
|
+
console.log("Sending texture "+uid+" "+texture.url+" to Client "+this.clientID+", size: "+textureSize+" bytes");
|
|
244
|
+
this.webRtcConnection.sendGeometry(view2);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
receiveHandshake(data)
|
|
248
|
+
{
|
|
249
|
+
if(data.length<message.HandshakeMessage.sizeof()) {
|
|
250
|
+
// NOTE: we use log() here rather than warn() or error() because this is a problem with
|
|
251
|
+
// the message we were SENT, not with the local server code.
|
|
252
|
+
console.log("Binary message from "+this.clientID+" is too small to be a Handshake: "+data.length+"<"+message.HandshakeMessage.sizeof());
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
var handshakeMessage=new message.HandshakeMessage();
|
|
256
|
+
core.decodeFromUint8Array(handshakeMessage,data);
|
|
257
|
+
const excess =data.length-message.HandshakeMessage.sizeof();
|
|
258
|
+
const numReceived =excess/core.UID_SIZE;
|
|
259
|
+
if(numReceived!=handshakeMessage.uint64_resourceCount) {
|
|
260
|
+
console.log("Handshake claims to have "<<handshakeMessage.uint64_resourceCount<<" resources but has only enough data for "+numReceived);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
var offset =message.HandshakeMessage.sizeof();
|
|
264
|
+
var dataView =new DataView(data.buffer,offset,data.offset,data.length);
|
|
265
|
+
for(let i=0;i<this.handshakeMessage.resourceCount;i++ ) {
|
|
266
|
+
var uid=dataView.getBigUint64(offset,core.endian);
|
|
267
|
+
this.geometryService.ConfirmResource(uid);
|
|
268
|
+
offset+=core.UID_SIZE;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
var acknowledgeHandshakeCommand=new command.AcknowledgeHandshakeCommand;
|
|
272
|
+
this.SendCommand(acknowledgeHandshakeCommand);
|
|
273
|
+
// And now, setup is complete. On the next geometry update, we can send nodes/resources.
|
|
274
|
+
this.StartStreaming();
|
|
275
|
+
}
|
|
276
|
+
receiveReliableBinaryMessage(data){
|
|
277
|
+
const messageType=data[0];
|
|
278
|
+
switch(messageType){
|
|
279
|
+
case message.MessagePayloadType.Handshake:
|
|
280
|
+
this.receiveHandshake(data);
|
|
281
|
+
return;
|
|
282
|
+
case message.MessagePayloadType.Acknowledgement:
|
|
283
|
+
this.ReceiveAcknowledgement(data);
|
|
284
|
+
return;
|
|
285
|
+
default:
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
SetScene(sc){
|
|
290
|
+
this.scene=sc;
|
|
291
|
+
this.geometryService.SetScene(sc);
|
|
292
|
+
}
|
|
293
|
+
setOrigin(origin_node_uid)
|
|
294
|
+
{
|
|
295
|
+
if(origin_node_uid==0)
|
|
296
|
+
return;
|
|
297
|
+
if(this.currentOriginState.originClientHas==origin_node_uid)
|
|
298
|
+
return;
|
|
299
|
+
// It's a different origin. So we reset the time sent.
|
|
300
|
+
this.currentOriginState.serverTimeSentUs=BigInt(0);
|
|
301
|
+
this.currentOriginState.originClientHas=origin_node_uid;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
module.exports = { Client };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const core=require("../core/core.js");
|
|
3
|
+
const client=require("./client.js");
|
|
4
|
+
const signaling=require("../signaling.js");
|
|
5
|
+
var _ = require('underscore');
|
|
6
|
+
const WebRtcConnectionManager = require('../connections/webrtcconnectionmanager');
|
|
7
|
+
|
|
8
|
+
class ClientManager
|
|
9
|
+
{
|
|
10
|
+
static clientManager = null;
|
|
11
|
+
constructor()
|
|
12
|
+
{
|
|
13
|
+
this.clients= new Map();
|
|
14
|
+
this.addNewClientAndReturnOriginUid=null;
|
|
15
|
+
this.onClientPostCreate=null;
|
|
16
|
+
this.geometryIntervalId=0;
|
|
17
|
+
let unixt_us=core.getStartTimeUnixUs();
|
|
18
|
+
console.log("Start Time: "+unixt_us+" us = "+core.unixTimeToUTCString(unixt_us)+"\n");
|
|
19
|
+
//console.log("From Date: "+Date.now()*1000+"\n");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
StartStreaming(){
|
|
23
|
+
this.geometryIntervalId = setInterval(_.bind( function() {
|
|
24
|
+
console.log("Streaming Update at "+core.getTimestampUs()/1000000.0);
|
|
25
|
+
this.UpdateStreaming();
|
|
26
|
+
},this), 5000);
|
|
27
|
+
}
|
|
28
|
+
StopStreaming(){
|
|
29
|
+
if(this.geometryIntervalId!=0)
|
|
30
|
+
clearInterval(this.geometryIntervalId);
|
|
31
|
+
}
|
|
32
|
+
UpdateStreaming(){
|
|
33
|
+
for (let [cl_id,cl] of this.clients) {
|
|
34
|
+
cl.UpdateStreaming();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
GetOrCreateClient(clientID)
|
|
38
|
+
{
|
|
39
|
+
if(!this.clients.has(clientID))
|
|
40
|
+
{
|
|
41
|
+
if(this.addNewClientAndReturnOriginUid==null){
|
|
42
|
+
error("No callback has been set to create the client origin.");
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
var origin_uid=this.addNewClientAndReturnOriginUid(clientID);
|
|
46
|
+
if(origin_uid==0){
|
|
47
|
+
error("Failed to create a root node for client "+clientID);
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
var sigCli=signaling.signalingClients[clientID];
|
|
51
|
+
var sigSend=sigCli.sendToClient.bind(sigCli);
|
|
52
|
+
var c=new client.Client(clientID,sigSend);
|
|
53
|
+
c.setOrigin(origin_uid);
|
|
54
|
+
if(this.clients.size==0)
|
|
55
|
+
this.StartStreaming();
|
|
56
|
+
this.clients.set(clientID,c);
|
|
57
|
+
if(this.onClientPostCreate!=null)
|
|
58
|
+
this.onClientPostCreate(clientID);
|
|
59
|
+
return c;
|
|
60
|
+
}
|
|
61
|
+
var c=this.clients.get(clientID);
|
|
62
|
+
return c;
|
|
63
|
+
}
|
|
64
|
+
RemoveClient(clientID){
|
|
65
|
+
if(this.clients.has(clientID)) {
|
|
66
|
+
this.clients.delete(clientID);
|
|
67
|
+
if(this.clients.size==0)
|
|
68
|
+
this.StopStreaming();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
GetClient(clientID)
|
|
72
|
+
{
|
|
73
|
+
if(!this.clients.has(clientID))
|
|
74
|
+
{
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
var c=this.clients.get(clientID);
|
|
78
|
+
return c;
|
|
79
|
+
}
|
|
80
|
+
SetNewClientCallback(cb)
|
|
81
|
+
{
|
|
82
|
+
this.addNewClientAndReturnOriginUid=cb;
|
|
83
|
+
}
|
|
84
|
+
SetClientPostCreationCallback(cb)
|
|
85
|
+
{
|
|
86
|
+
this.onClientPostCreate=cb;
|
|
87
|
+
}
|
|
88
|
+
// This is a callback, signaling service calls this when the client has signalled.
|
|
89
|
+
newClient(clientID,signalingClient) {
|
|
90
|
+
// then we tell the client manager to start this client.
|
|
91
|
+
var c=this.GetOrCreateClient(clientID);
|
|
92
|
+
signalingClient.receiveReliableBinaryMessage=c.receiveReliableBinaryMessage.bind(c);
|
|
93
|
+
//c.SetScene(this.scene);
|
|
94
|
+
c.Start();
|
|
95
|
+
return c;
|
|
96
|
+
}
|
|
97
|
+
writeState() {
|
|
98
|
+
var content="<table><tr><th>Client Id</th><th>IP Address</th><th>Signalling State</th></tr>";
|
|
99
|
+
for (let [cl_id,cl] of this.clients) {
|
|
100
|
+
var sigCli=signaling.signalingClients[cl_id];
|
|
101
|
+
content+="\n<tr><td>"+cl_id+"</td> <td>" + sigCli.ip + "</td> <td>" + sigCli.signalingState + "</td></tr>";
|
|
102
|
+
};
|
|
103
|
+
content+="\n</table>";
|
|
104
|
+
return content;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
exports.getInstance=()=>
|
|
109
|
+
{
|
|
110
|
+
if(ClientManager.clientManager==null)
|
|
111
|
+
ClientManager.clientManager = new ClientManager();
|
|
112
|
+
return ClientManager.clientManager;
|
|
113
|
+
}
|
|
114
|
+
exports.ClientManager=ClientManager;
|