livedesk 0.1.115 → 0.1.116
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/hub/src/server.js +142 -16
- package/package.json +1 -1
package/hub/src/server.js
CHANGED
|
@@ -22,7 +22,11 @@ const packageInfo = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.js
|
|
|
22
22
|
const httpHost = process.env.LIVEDESK_HUB_HTTP_HOST || '0.0.0.0';
|
|
23
23
|
const httpPort = Number(process.env.LIVEDESK_HUB_HTTP_PORT || process.env.PORT || 5179);
|
|
24
24
|
const frameBackpressureBytes = readPositiveIntegerEnv('LIVEDESK_FRAME_WS_BACKPRESSURE_BYTES', 512 * 1024);
|
|
25
|
+
const frameClientQueuePackets = readPositiveIntegerEnv('LIVEDESK_FRAME_WS_QUEUE_PACKETS', 3);
|
|
26
|
+
const frameClientDrainBudgetPackets = readPositiveIntegerEnv('LIVEDESK_FRAME_WS_DRAIN_BUDGET_PACKETS', 2);
|
|
25
27
|
const frameClients = new Set();
|
|
28
|
+
const frameClientsByDeviceId = new Map();
|
|
29
|
+
const frameWildcardClients = new Set();
|
|
26
30
|
const inputClients = new Set();
|
|
27
31
|
const audioClients = new Set();
|
|
28
32
|
let frameClientSeq = 0;
|
|
@@ -42,10 +46,14 @@ function handleRemoteHubEvent(type, event) {
|
|
|
42
46
|
if (!deviceId) {
|
|
43
47
|
return;
|
|
44
48
|
}
|
|
45
|
-
|
|
49
|
+
const clients = new Set([
|
|
50
|
+
...(frameClientsByDeviceId.get(deviceId) || []),
|
|
51
|
+
...frameWildcardClients
|
|
52
|
+
]);
|
|
53
|
+
for (const ws of clients) {
|
|
46
54
|
if (ws.readyState === 1
|
|
47
55
|
&& ws.liveDeskAutoStart
|
|
48
|
-
&& ws.liveDeskDeviceIds?.has(deviceId)) {
|
|
56
|
+
&& (!ws.liveDeskDeviceIds?.size || ws.liveDeskDeviceIds.has(deviceId))) {
|
|
49
57
|
startFrameSubscriptionLive(ws, 'device-reconnected', deviceId);
|
|
50
58
|
}
|
|
51
59
|
}
|
|
@@ -68,9 +76,9 @@ const remoteHub = createRemoteHub({
|
|
|
68
76
|
|
|
69
77
|
const app = express();
|
|
70
78
|
const httpServer = createServer(app);
|
|
71
|
-
const frameWss = new WebSocketServer({ noServer: true });
|
|
72
|
-
const inputWss = new WebSocketServer({ noServer: true });
|
|
73
|
-
const audioWss = new WebSocketServer({ noServer: true });
|
|
79
|
+
const frameWss = new WebSocketServer({ noServer: true, perMessageDeflate: false });
|
|
80
|
+
const inputWss = new WebSocketServer({ noServer: true, perMessageDeflate: false });
|
|
81
|
+
const audioWss = new WebSocketServer({ noServer: true, perMessageDeflate: false });
|
|
74
82
|
|
|
75
83
|
app.use((req, res, next) => {
|
|
76
84
|
if (req.headers.origin) {
|
|
@@ -200,6 +208,7 @@ function sendJson(ws, payload) {
|
|
|
200
208
|
}
|
|
201
209
|
|
|
202
210
|
function forgetWebSocketClient(ws) {
|
|
211
|
+
unregisterFrameClient(ws);
|
|
203
212
|
frameClients.delete(ws);
|
|
204
213
|
audioClients.delete(ws);
|
|
205
214
|
inputClients.delete(ws);
|
|
@@ -228,12 +237,129 @@ function safeWebSocketSend(ws, payload, options = undefined) {
|
|
|
228
237
|
}
|
|
229
238
|
}
|
|
230
239
|
|
|
240
|
+
function unregisterFrameClient(ws) {
|
|
241
|
+
if (!ws) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const ids = ws.liveDeskDeviceIds instanceof Set ? ws.liveDeskDeviceIds : new Set();
|
|
245
|
+
for (const deviceId of ids) {
|
|
246
|
+
const clients = frameClientsByDeviceId.get(deviceId);
|
|
247
|
+
if (!clients) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
clients.delete(ws);
|
|
251
|
+
if (clients.size === 0) {
|
|
252
|
+
frameClientsByDeviceId.delete(deviceId);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
frameWildcardClients.delete(ws);
|
|
256
|
+
ws.liveDeskFrameSendLane?.queue?.splice?.(0);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function registerFrameClient(ws) {
|
|
260
|
+
if (!ws) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const ids = ws.liveDeskDeviceIds instanceof Set ? ws.liveDeskDeviceIds : new Set();
|
|
264
|
+
if (ids.size === 0) {
|
|
265
|
+
frameWildcardClients.add(ws);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
for (const deviceId of ids) {
|
|
269
|
+
let clients = frameClientsByDeviceId.get(deviceId);
|
|
270
|
+
if (!clients) {
|
|
271
|
+
clients = new Set();
|
|
272
|
+
frameClientsByDeviceId.set(deviceId, clients);
|
|
273
|
+
}
|
|
274
|
+
clients.add(ws);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function ensureFrameClientSendLane(ws) {
|
|
279
|
+
if (!ws.liveDeskFrameSendLane) {
|
|
280
|
+
ws.liveDeskFrameSendLane = {
|
|
281
|
+
queue: [],
|
|
282
|
+
draining: false,
|
|
283
|
+
dropped: 0
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
return ws.liveDeskFrameSendLane;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function dropQueuedFrameForLane(lane) {
|
|
290
|
+
const deltaIndex = lane.queue.findIndex(item => item?.isKeyFrame !== true);
|
|
291
|
+
const dropIndex = deltaIndex >= 0 ? deltaIndex : 0;
|
|
292
|
+
const dropped = lane.queue.splice(dropIndex, 1);
|
|
293
|
+
lane.dropped += dropped.length;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function drainFrameClientSendLane(ws) {
|
|
297
|
+
const lane = ws.liveDeskFrameSendLane;
|
|
298
|
+
if (!lane || lane.draining) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
lane.draining = true;
|
|
302
|
+
setImmediate(() => {
|
|
303
|
+
lane.draining = false;
|
|
304
|
+
if (!ws || ws.readyState !== ws.OPEN) {
|
|
305
|
+
lane.queue.length = 0;
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
let sent = 0;
|
|
310
|
+
while (lane.queue.length > 0 && sent < frameClientDrainBudgetPackets) {
|
|
311
|
+
if (ws.bufferedAmount > frameBackpressureBytes) {
|
|
312
|
+
ws.liveDeskFrameBackpressured = true;
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
const item = lane.queue.shift();
|
|
316
|
+
if (!item) {
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
ws.liveDeskFrameBackpressured = false;
|
|
320
|
+
safeWebSocketSend(ws, item.packet, { binary: true });
|
|
321
|
+
sent += 1;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (lane.queue.length > 0 && ws.readyState === ws.OPEN) {
|
|
325
|
+
drainFrameClientSendLane(ws);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function enqueueFramePacketForClient(ws, packet, meta) {
|
|
331
|
+
if (!ws || ws.readyState !== ws.OPEN) {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
const lane = ensureFrameClientSendLane(ws);
|
|
335
|
+
if (ws.bufferedAmount > frameBackpressureBytes) {
|
|
336
|
+
ws.liveDeskFrameBackpressured = true;
|
|
337
|
+
ws.liveDeskFrameBackpressureDrops = Number(ws.liveDeskFrameBackpressureDrops || 0) + 1;
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
while (lane.queue.length >= frameClientQueuePackets) {
|
|
341
|
+
dropQueuedFrameForLane(lane);
|
|
342
|
+
ws.liveDeskFrameBackpressureDrops = Number(ws.liveDeskFrameBackpressureDrops || 0) + 1;
|
|
343
|
+
}
|
|
344
|
+
lane.queue.push({
|
|
345
|
+
packet,
|
|
346
|
+
deviceId: meta.deviceId,
|
|
347
|
+
frameSeq: meta.frameSeq,
|
|
348
|
+
isH264: meta.isH264,
|
|
349
|
+
isKeyFrame: meta.isKeyFrame
|
|
350
|
+
});
|
|
351
|
+
drainFrameClientSendLane(ws);
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
|
|
231
355
|
function updateFrameSubscription(ws, payload = {}) {
|
|
232
356
|
const previousDeviceIds = ws.liveDeskDeviceIds instanceof Set
|
|
233
357
|
? new Set(ws.liveDeskDeviceIds)
|
|
234
358
|
: new Set();
|
|
359
|
+
unregisterFrameClient(ws);
|
|
235
360
|
const deviceIds = normalizeDeviceIds(payload.deviceIds ?? payload.devices ?? payload.deviceId);
|
|
236
361
|
ws.liveDeskDeviceIds = new Set(deviceIds);
|
|
362
|
+
registerFrameClient(ws);
|
|
237
363
|
ws.liveDeskAutoStart = /^(1|true|yes|on|live)$/i.test(String(payload.autoStartLive ?? payload.startLive ?? ''));
|
|
238
364
|
ws.liveDeskLiveOptions = normalizeLiveOptions(payload);
|
|
239
365
|
sendJson(ws, {
|
|
@@ -424,6 +550,13 @@ function broadcastRemoteBinaryFrame(frameEvent) {
|
|
|
424
550
|
if (!deviceId || frameClients.size === 0) {
|
|
425
551
|
return;
|
|
426
552
|
}
|
|
553
|
+
const targetClients = new Set([
|
|
554
|
+
...(frameClientsByDeviceId.get(deviceId) || []),
|
|
555
|
+
...frameWildcardClients
|
|
556
|
+
]);
|
|
557
|
+
if (targetClients.size === 0) {
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
427
560
|
const frame = frameEvent.frame || {};
|
|
428
561
|
const isH264 = String(frame.codec || frame.mimeType || frame.format || '').toLowerCase().includes('h264')
|
|
429
562
|
|| String(frame.frameMode || frame.mode || '').toLowerCase() === 'mode3-h264-hw';
|
|
@@ -432,24 +565,16 @@ function broadcastRemoteBinaryFrame(frameEvent) {
|
|
|
432
565
|
if (!packet) {
|
|
433
566
|
return;
|
|
434
567
|
}
|
|
435
|
-
|
|
568
|
+
const frameSeq = Number(frame.frameSeq || 0) || 0;
|
|
569
|
+
for (const client of targetClients) {
|
|
436
570
|
if (client.readyState !== client.OPEN) {
|
|
437
571
|
continue;
|
|
438
572
|
}
|
|
439
|
-
if (client.liveDeskDeviceIds?.size > 0 && !client.liveDeskDeviceIds.has(deviceId)) {
|
|
440
|
-
continue;
|
|
441
|
-
}
|
|
442
|
-
if (client.bufferedAmount > frameBackpressureBytes) {
|
|
443
|
-
client.liveDeskFrameBackpressured = true;
|
|
444
|
-
client.liveDeskFrameBackpressureDrops = Number(client.liveDeskFrameBackpressureDrops || 0) + 1;
|
|
445
|
-
continue;
|
|
446
|
-
}
|
|
447
573
|
if (client.liveDeskFrameBackpressured && isH264 && !isKeyFrame) {
|
|
448
574
|
client.liveDeskFrameBackpressureDrops = Number(client.liveDeskFrameBackpressureDrops || 0) + 1;
|
|
449
575
|
continue;
|
|
450
576
|
}
|
|
451
|
-
client
|
|
452
|
-
safeWebSocketSend(client, packet, { binary: true });
|
|
577
|
+
enqueueFramePacketForClient(client, packet, { deviceId, frameSeq, isH264, isKeyFrame });
|
|
453
578
|
}
|
|
454
579
|
}
|
|
455
580
|
|
|
@@ -799,6 +924,7 @@ frameWss.on('connection', (ws, req) => {
|
|
|
799
924
|
// Best-effort latency hint for browser frame sockets.
|
|
800
925
|
}
|
|
801
926
|
const cleanup = () => {
|
|
927
|
+
unregisterFrameClient(ws);
|
|
802
928
|
frameClients.delete(ws);
|
|
803
929
|
};
|
|
804
930
|
try {
|