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.
Files changed (2) hide show
  1. package/hub/src/server.js +142 -16
  2. 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
- for (const ws of frameClients) {
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
- for (const client of frameClients) {
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.liveDeskFrameBackpressured = false;
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 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livedesk",
3
- "version": "0.1.115",
3
+ "version": "0.1.116",
4
4
  "description": "LiveDesk Hub and client launcher",
5
5
  "type": "module",
6
6
  "bin": {