livedesk 0.1.114 → 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
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, {
|
|
@@ -347,6 +473,8 @@ function buildRemoteFrameBinaryPacket(frameEvent) {
|
|
|
347
473
|
return null;
|
|
348
474
|
}
|
|
349
475
|
const frame = frameEvent.frame || {};
|
|
476
|
+
const hubPacketEpochMs = Date.now();
|
|
477
|
+
const hubPacketAt = new Date(hubPacketEpochMs).toISOString();
|
|
350
478
|
const metadata = {
|
|
351
479
|
type: 'remote.frame.binary',
|
|
352
480
|
kind: frameEvent.kind || 'live',
|
|
@@ -380,6 +508,8 @@ function buildRemoteFrameBinaryPacket(frameEvent) {
|
|
|
380
508
|
fps: Number(frame.fps || 0) || 0,
|
|
381
509
|
capturedAt: frame.capturedAt || '',
|
|
382
510
|
receivedAt: frame.receivedAt || '',
|
|
511
|
+
hubPacketAt,
|
|
512
|
+
hubPacketEpochMs,
|
|
383
513
|
byteLength: Number(frameEvent.byteLength || payload.length) || payload.length,
|
|
384
514
|
contentHash: frame.contentHash || '',
|
|
385
515
|
captureMs: Number(frame.captureMs || 0) || 0,
|
|
@@ -420,6 +550,13 @@ function broadcastRemoteBinaryFrame(frameEvent) {
|
|
|
420
550
|
if (!deviceId || frameClients.size === 0) {
|
|
421
551
|
return;
|
|
422
552
|
}
|
|
553
|
+
const targetClients = new Set([
|
|
554
|
+
...(frameClientsByDeviceId.get(deviceId) || []),
|
|
555
|
+
...frameWildcardClients
|
|
556
|
+
]);
|
|
557
|
+
if (targetClients.size === 0) {
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
423
560
|
const frame = frameEvent.frame || {};
|
|
424
561
|
const isH264 = String(frame.codec || frame.mimeType || frame.format || '').toLowerCase().includes('h264')
|
|
425
562
|
|| String(frame.frameMode || frame.mode || '').toLowerCase() === 'mode3-h264-hw';
|
|
@@ -428,24 +565,16 @@ function broadcastRemoteBinaryFrame(frameEvent) {
|
|
|
428
565
|
if (!packet) {
|
|
429
566
|
return;
|
|
430
567
|
}
|
|
431
|
-
|
|
568
|
+
const frameSeq = Number(frame.frameSeq || 0) || 0;
|
|
569
|
+
for (const client of targetClients) {
|
|
432
570
|
if (client.readyState !== client.OPEN) {
|
|
433
571
|
continue;
|
|
434
572
|
}
|
|
435
|
-
if (client.liveDeskDeviceIds?.size > 0 && !client.liveDeskDeviceIds.has(deviceId)) {
|
|
436
|
-
continue;
|
|
437
|
-
}
|
|
438
|
-
if (client.bufferedAmount > frameBackpressureBytes) {
|
|
439
|
-
client.liveDeskFrameBackpressured = true;
|
|
440
|
-
client.liveDeskFrameBackpressureDrops = Number(client.liveDeskFrameBackpressureDrops || 0) + 1;
|
|
441
|
-
continue;
|
|
442
|
-
}
|
|
443
573
|
if (client.liveDeskFrameBackpressured && isH264 && !isKeyFrame) {
|
|
444
574
|
client.liveDeskFrameBackpressureDrops = Number(client.liveDeskFrameBackpressureDrops || 0) + 1;
|
|
445
575
|
continue;
|
|
446
576
|
}
|
|
447
|
-
client
|
|
448
|
-
safeWebSocketSend(client, packet, { binary: true });
|
|
577
|
+
enqueueFramePacketForClient(client, packet, { deviceId, frameSeq, isH264, isKeyFrame });
|
|
449
578
|
}
|
|
450
579
|
}
|
|
451
580
|
|
|
@@ -795,6 +924,7 @@ frameWss.on('connection', (ws, req) => {
|
|
|
795
924
|
// Best-effort latency hint for browser frame sockets.
|
|
796
925
|
}
|
|
797
926
|
const cleanup = () => {
|
|
927
|
+
unregisterFrameClient(ws);
|
|
798
928
|
frameClients.delete(ws);
|
|
799
929
|
};
|
|
800
930
|
try {
|